@@ -36,7 +36,7 @@ type SeparatorWord string
3636
3737// String implementations
3838func (w SingleCaseWord ) String () string { return strings .ToLower (string (w )) }
39- func (w FirstUpperCaseWord ) String () string { return UpperCaseFirst ( strings . ToLower ( string (w ) )) }
39+ func (w FirstUpperCaseWord ) String () string { return upperCaseFirstLower ( string (w )) }
4040func (w AcronymWord ) String () string { return string (w ) }
4141func (w UpperCaseWord ) String () string { return strings .ToUpper (string (w )) }
4242func (w SeparatorWord ) String () string { return string (w ) }
@@ -118,6 +118,46 @@ func MustLowerCaseFirst(s string) string {
118118 return res
119119}
120120
121+ // upperCaseFirstLower capitalizes the first character and lowercases the rest.
122+ func upperCaseFirstLower (s string ) string {
123+ if s == "" {
124+ return ""
125+ }
126+ r , size := utf8 .DecodeRuneInString (s )
127+ if r == utf8 .RuneError && size == 1 {
128+ // Invalid UTF-8 start byte.
129+ // We want to replace it with RuneError (like strings.ToLower/ToUpper do).
130+ // So we force needChange.
131+ } else if r == utf8 .RuneError {
132+ // Valid RuneError (U+FFFD)
133+ }
134+
135+ u := unicode .ToUpper (r )
136+
137+ // Check if changes are needed
138+ needChange := (r != u ) || (r == utf8 .RuneError && size == 1 )
139+ if ! needChange {
140+ for _ , rc := range s [size :] {
141+ if unicode .ToLower (rc ) != rc {
142+ needChange = true
143+ break
144+ }
145+ }
146+ }
147+
148+ if ! needChange {
149+ return s
150+ }
151+
152+ var b strings.Builder
153+ b .Grow (len (s ))
154+ b .WriteRune (u )
155+ for _ , rc := range s [size :] {
156+ b .WriteRune (unicode .ToLower (rc ))
157+ }
158+ return b .String ()
159+ }
160+
121161func (w ExactCaseWord ) String () string { return string (w ) }
122162
123163// Options
@@ -231,7 +271,7 @@ func WordsToFormattedCase(words []Word, opts ...any) (string, error) {
231271 } else if cfg .allLower || cfg .whispering {
232272 w = strings .ToLower (w )
233273 } else if cfg .caseMode == CMAllTitle {
234- w = UpperCaseFirst ( strings . ToLower ( w ) )
274+ w = upperCaseFirstLower ( w )
235275 } else {
236276 w = strings .ToLower (w )
237277 }
@@ -262,7 +302,7 @@ func WordsToFormattedCase(words []Word, opts ...any) (string, error) {
262302 } else if cfg .whispering {
263303 w = strings .ToLower (w )
264304 } else if cfg .caseMode == CMAllTitle {
265- w = UpperCaseFirst ( strings . ToLower ( w ) )
305+ w = upperCaseFirstLower ( w )
266306 }
267307 case UpperCaseWord :
268308 w = word .String ()
@@ -271,7 +311,7 @@ func WordsToFormattedCase(words []Word, opts ...any) (string, error) {
271311 } else if cfg .allLower || cfg .whispering {
272312 w = strings .ToLower (w )
273313 } else if cfg .caseMode == CMAllTitle {
274- w = UpperCaseFirst ( strings . ToLower ( w ) )
314+ w = upperCaseFirstLower ( w )
275315 } else {
276316 w = strings .ToLower (w )
277317 }
0 commit comments