Skip to content

Commit 878bc44

Browse files
Handle Unicode character casing and require Node.js 10 (#62)
Co-authored-by: Sindre Sorhus <[email protected]>
1 parent adfe007 commit 878bc44

File tree

7 files changed

+37
-16
lines changed

7 files changed

+37
-16
lines changed

.travis.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,3 @@ language: node_js
22
node_js:
33
- '12'
44
- '10'
5-
- '8'
6-
- '6'

index.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ declare const camelcase: {
1313
/**
1414
Convert a dash/dot/underscore/space separated string to camelCase or PascalCase: `foo-bar` → `fooBar`.
1515
16+
Correctly handles Unicode strings.
17+
1618
@param input - String to convert to camel case.
1719
1820
@example
@@ -27,6 +29,9 @@ declare const camelcase: {
2729
2830
camelCase('Foo-Bar');
2931
//=> 'fooBar'
32+
33+
camelCase('розовый_пушистый_единороги');
34+
//=> 'розовыйПушистыйЕдинороги'
3035
3136
camelCase('Foo-Bar', {pascalCase: true});
3237
//=> 'FooBar'

index.js

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,21 @@ const preserveCamelCase = string => {
88
for (let i = 0; i < string.length; i++) {
99
const character = string[i];
1010

11-
if (isLastCharLower && /[a-zA-Z]/.test(character) && character.toUpperCase() === character) {
11+
if (isLastCharLower && /[\p{Lu}]/u.test(character)) {
1212
string = string.slice(0, i) + '-' + string.slice(i);
1313
isLastCharLower = false;
1414
isLastLastCharUpper = isLastCharUpper;
1515
isLastCharUpper = true;
1616
i++;
17-
} else if (isLastCharUpper && isLastLastCharUpper && /[a-zA-Z]/.test(character) && character.toLowerCase() === character) {
17+
} else if (isLastCharUpper && isLastLastCharUpper && /[\p{Ll}]/u.test(character)) {
1818
string = string.slice(0, i - 1) + '-' + string.slice(i - 1);
1919
isLastLastCharUpper = isLastCharUpper;
2020
isLastCharUpper = false;
2121
isLastCharLower = true;
2222
} else {
23-
isLastCharLower = character.toLowerCase() === character && character.toUpperCase() !== character;
23+
isLastCharLower = character.toLocaleLowerCase() === character && character.toLocaleUpperCase() !== character;
2424
isLastLastCharUpper = isLastCharUpper;
25-
isLastCharUpper = character.toUpperCase() === character && character.toLowerCase() !== character;
25+
isLastCharUpper = character.toLocaleUpperCase() === character && character.toLocaleLowerCase() !== character;
2626
}
2727
}
2828

@@ -34,11 +34,12 @@ const camelCase = (input, options) => {
3434
throw new TypeError('Expected the input to be `string | string[]`');
3535
}
3636

37-
options = Object.assign({
38-
pascalCase: false
39-
}, options);
37+
options = {
38+
...{pascalCase: false},
39+
...options
40+
};
4041

41-
const postProcess = x => options.pascalCase ? x.charAt(0).toUpperCase() + x.slice(1) : x;
42+
const postProcess = x => options.pascalCase ? x.charAt(0).toLocaleUpperCase() + x.slice(1) : x;
4243

4344
if (Array.isArray(input)) {
4445
input = input.map(x => x.trim())
@@ -53,20 +54,20 @@ const camelCase = (input, options) => {
5354
}
5455

5556
if (input.length === 1) {
56-
return options.pascalCase ? input.toUpperCase() : input.toLowerCase();
57+
return options.pascalCase ? input.toLocaleUpperCase() : input.toLocaleLowerCase();
5758
}
5859

59-
const hasUpperCase = input !== input.toLowerCase();
60+
const hasUpperCase = input !== input.toLocaleLowerCase();
6061

6162
if (hasUpperCase) {
6263
input = preserveCamelCase(input);
6364
}
6465

6566
input = input
6667
.replace(/^[_.\- ]+/, '')
67-
.toLowerCase()
68-
.replace(/[_.\- ]+(\w|$)/g, (_, p1) => p1.toUpperCase())
69-
.replace(/\d+(\w|$)/g, m => m.toUpperCase());
68+
.toLocaleLowerCase()
69+
.replace(/[_.\- ]+([\p{Alpha}\p{N}_]|$)/gu, (_, p1) => p1.toLocaleUpperCase())
70+
.replace(/\d+([\p{Alpha}\p{N}_]|$)/gu, m => m.toLocaleUpperCase());
7071

7172
return postProcess(input);
7273
};

index.test-d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {expectType} from 'tsd';
22
import camelCase = require('.');
33

44
expectType<string>(camelCase('foo-bar'));
5+
expectType<string>(camelCase('розовый_пушистый_единороги'));
56
expectType<string>(camelCase('Foo-Bar', {pascalCase: true}));
67
expectType<string>(camelCase(['foo', 'bar']));
78
expectType<string>(camelCase(['__foo__', '--bar'], {pascalCase: true}));

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"url": "sindresorhus.com"
1111
},
1212
"engines": {
13-
"node": ">=6"
13+
"node": ">=10"
1414
},
1515
"scripts": {
1616
"test": "xo && ava && tsd"

readme.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
> Convert a dash/dot/underscore/space separated string to camelCase or PascalCase: `foo-bar``fooBar`
44
5+
Correctly handles Unicode strings.
6+
57

68
## Install
79

@@ -24,6 +26,9 @@ camelCase('foo_bar');
2426
camelCase('Foo-Bar');
2527
//=> 'fooBar'
2628

29+
camelCase('розовый_пушистый_единороги');
30+
//=> 'розовыйПушистыйЕдинороги'
31+
2732
camelCase('Foo-Bar', {pascalCase: true});
2833
//=> 'FooBar'
2934

test.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,12 @@ test('camelCase', t => {
5858
t.is(camelCase('1Hello'), '1Hello');
5959
t.is(camelCase('1hello'), '1Hello');
6060
t.is(camelCase('h2w'), 'h2W');
61+
t.is(camelCase('розовый_пушистый-единороги'), 'розовыйПушистыйЕдинороги');
62+
t.is(camelCase('розовый_пушистый-единороги'), 'розовыйПушистыйЕдинороги');
63+
t.is(camelCase('РОЗОВЫЙ_ПУШИСТЫЙ-ЕДИНОРОГИ'), 'розовыйПушистыйЕдинороги');
64+
t.is(camelCase('桑德在这里。'), '桑德在这里。');
65+
t.is(camelCase('桑德在这里。'), '桑德在这里。');
66+
t.is(camelCase('桑德_在这里。'), '桑德在这里。');
6167
});
6268

6369
test('camelCase with pascalCase option', t => {
@@ -117,6 +123,11 @@ test('camelCase with pascalCase option', t => {
117123
t.is(camelCase('1hello', {pascalCase: true}), '1Hello');
118124
t.is(camelCase('1Hello', {pascalCase: true}), '1Hello');
119125
t.is(camelCase('h1W', {pascalCase: true}), 'H1W');
126+
t.is(camelCase('РозовыйПушистыйЕдинороги', {pascalCase: true}), 'РозовыйПушистыйЕдинороги');
127+
t.is(camelCase('розовый_пушистый-единороги', {pascalCase: true}), 'РозовыйПушистыйЕдинороги');
128+
t.is(camelCase('РОЗОВЫЙ_ПУШИСТЫЙ-ЕДИНОРОГИ', {pascalCase: true}), 'РозовыйПушистыйЕдинороги');
129+
t.is(camelCase('桑德在这里。', {pascalCase: true}), '桑德在这里。');
130+
t.is(camelCase('桑德_在这里。', {pascalCase: true}), '桑德在这里。');
120131
});
121132

122133
test('invalid input', t => {

0 commit comments

Comments
 (0)