Repository: sindresorhus/camelcase (js)

Issue:
Case conversion with specific locale
Case conversion are done with `String.prototype.toLocaleLowerCase()` and `String.prototype.toLocaleUpperCase()` without explicit locale parameter and this causes host dependent output.
Example:
```js
camelCase('lorem-ipsum')
//=> 'loremIpsum' if host locale is en-US
//=> 'loremİpsum' if host locale is tr-TR
```
This causes unexpected results int hosts with non en-US environment.

I'd suggest to extend `options` parameter to have a `locale` property to be passed to aforementioned methods.

```js
camelCase('lorem-ipsum', {locale: 'en-US'});
//=> 'loremIpsum' if host locale is en-US
//=> 'loremIpsum' if host locale is tr-TR
```

Pull request description:
Add `locale` option
Fixes #72 
Added locale property to option parameter along with tests and doc.


Patch:
diff --git a/index.d.ts b/index.d.ts
index 51d2ba2..90a208a 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -6,6 +6,30 @@ declare namespace camelcase {
 		@default false
 		*/
 		readonly pascalCase?: boolean;
+
+		/**
+		The locale parameter indicates the locale to be used to convert to upper/lower case according to any locale-specific case mappings. If multiple locales are given in an array, the best available locale is used.
+
+		Default: The host environment’s current locale.
+
+		@example
+		```
+		import camelCase = require('camelcase');
+
+		camelCase('lorem-ipsum', {locale: 'en-US'});
+		//=> 'loremIpsum'
+
+		camelCase('lorem-ipsum', {locale: 'tr-TR'});
+		//=> 'loremİpsum'
+
+		camelCase('lorem-ipsum', {locale: ['en-US', 'en-GB']});
+		//=> 'loremIpsum'
+
+		camelCase('lorem-ipsum', {locale: ['tr', 'TR', 'tr-TR']});
+		//=> 'loremİpsum'
+		```
+		*/
+		readonly locale?: string | readonly string[];
 	}
 }
 
@@ -51,6 +75,9 @@ camelCase(['foo', 'bar']);
 
 camelCase(['__foo__', '--bar'], {pascalCase: true});
 //=> 'FooBar'
+
+camelCase('lorem-ipsum', {locale: 'en-US'});
+//=> 'loremIpsum'
 ```
 */
 declare function camelcase(
diff --git a/index.js b/index.js
index dd2c490..3c7ae42 100644
--- a/index.js
+++ b/index.js
@@ -1,6 +1,6 @@
 'use strict';
 
-const preserveCamelCase = string => {
+const preserveCamelCase = (string, locale) => {
 	let isLastCharLower = false;
 	let isLastCharUpper = false;
 	let isLastLastCharUpper = false;
@@ -20,9 +20,9 @@ const preserveCamelCase = string => {
 			isLastCharUpper = false;
 			isLastCharLower = true;
 		} else {
-			isLastCharLower = character.toLocaleLowerCase() === character && character.toLocaleUpperCase() !== character;
+			isLastCharLower = character.toLocaleLowerCase(locale) === character && character.toLocaleUpperCase(locale) !== character;
 			isLastLastCharUpper = isLastCharUpper;
-			isLastCharUpper = character.toLocaleUpperCase() === character && character.toLocaleLowerCase() !== character;
+			isLastCharUpper = character.toLocaleUpperCase(locale) === character && character.toLocaleLowerCase(locale) !== character;
 		}
 	}
 
@@ -39,7 +39,7 @@ const camelCase = (input, options) => {
 		...options
 	};
 
-	const postProcess = x => options.pascalCase ? x.charAt(0).toLocaleUpperCase() + x.slice(1) : x;
+	const postProcess = x => options.pascalCase ? x.charAt(0).toLocaleUpperCase(options.locale) + x.slice(1) : x;
 
 	if (Array.isArray(input)) {
 		input = input.map(x => x.trim())
@@ -54,20 +54,20 @@ const camelCase = (input, options) => {
 	}
 
 	if (input.length === 1) {
-		return options.pascalCase ? input.toLocaleUpperCase() : input.toLocaleLowerCase();
+		return options.pascalCase ? input.toLocaleUpperCase(options.locale) : input.toLocaleLowerCase(options.locale);
 	}
 
-	const hasUpperCase = input !== input.toLocaleLowerCase();
+	const hasUpperCase = input !== input.toLocaleLowerCase(options.locale);
 
 	if (hasUpperCase) {
-		input = preserveCamelCase(input);
+		input = preserveCamelCase(input, options.locale);
 	}
 
 	input = input
 		.replace(/^[_.\- ]+/, '')
-		.toLocaleLowerCase()
-		.replace(/[_.\- ]+([\p{Alpha}\p{N}_]|$)/gu, (_, p1) => p1.toLocaleUpperCase())
-		.replace(/\d+([\p{Alpha}\p{N}_]|$)/gu, m => m.toLocaleUpperCase());
+		.toLocaleLowerCase(options.locale)
+		.replace(/[_.\- ]+([\p{Alpha}\p{N}_]|$)/gu, (_, p1) => p1.toLocaleUpperCase(options.locale))
+		.replace(/\d+([\p{Alpha}\p{N}_]|$)/gu, m => m.toLocaleUpperCase(options.locale));
 
 	return postProcess(input);
 };
diff --git a/readme.md b/readme.md
index da76012..c9b4e08 100644
--- a/readme.md
+++ b/readme.md
@@ -48,6 +48,9 @@ camelCase(['foo', 'bar']);
 
 camelCase(['__foo__', '--bar'], {pascalCase: true});
 //=> 'FooBar'
+
+camelCase('lorem-ipsum', {locale: 'en-US'});
+//=> 'loremIpsum'
 ```
 
 ## API
@@ -71,6 +74,29 @@ Default: `false`
 
 Uppercase the first character: `foo-bar` → `FooBar`
 
+##### locale
+
+Type: `string | string[]`\
+Default: The host environment’s current locale.
+
+The locale parameter indicates the locale to be used to convert to upper/lower case according to any locale-specific case mappings. If multiple locales are given in an array, the best available locale is used.
+
+```js
+const camelCase = require('camelcase');
+
+camelCase('lorem-ipsum', {locale: 'en-US'});
+//=> 'loremIpsum'
+
+camelCase('lorem-ipsum', {locale: 'tr-TR'});
+//=> 'loremİpsum'
+
+camelCase('lorem-ipsum', {locale: ['en-US', 'en-GB']});
+//=> 'loremIpsum'
+
+camelCase('lorem-ipsum', {locale: ['tr', 'TR', 'tr-TR']});
+//=> 'loremİpsum'
+```
+
 ## camelcase for enterprise
 
 Available as part of the Tidelift Subscription.


Test patch:
diff --git a/index.test-d.ts b/index.test-d.ts
index cc565d9..e39b067 100644
--- a/index.test-d.ts
+++ b/index.test-d.ts
@@ -3,6 +3,10 @@ import camelCase = require('.');
 
 expectType<string>(camelCase('foo-bar'));
 expectType<string>(camelCase('розовый_пушистый_единороги'));
+expectType<string>(camelCase('Foo-Bar', {locale: ['tr']}));
+expectType<string>(camelCase('Foo-Bar', {locale: ['tr', 'TR', 'tr-TR']}));
+expectType<string>(camelCase('Foo-Bar', {pascalCase: true, locale: ['tr']}));
+expectType<string>(camelCase('Foo-Bar', {pascalCase: true, locale: ['tr', 'TR', 'tr-TR']}));
 expectType<string>(camelCase('Foo-Bar', {pascalCase: true}));
 expectType<string>(camelCase(['foo', 'bar']));
 expectType<string>(camelCase(['__foo__', '--bar'], {pascalCase: true}));
diff --git a/test.js b/test.js
index 4dea4be..6de410c 100644
--- a/test.js
+++ b/test.js
@@ -130,6 +130,17 @@ test('camelCase with pascalCase option', t => {
 	t.is(camelCase('桑德_在这里。', {pascalCase: true}), '桑德在这里。');
 });
 
+test('camelCase with locale option', t => {
+	t.is(camelCase('lorem-ipsum', {locale: 'tr-TR'}), 'loremİpsum');
+	t.is(camelCase('lorem-ipsum', {locale: 'en-EN'}), 'loremIpsum');
+	t.is(camelCase('lorem-ipsum', {locale: ['tr', 'TR', 'tr-TR']}), 'loremİpsum');
+	t.is(camelCase('lorem-ipsum', {locale: ['en-EN', 'en-GB']}), 'loremIpsum');
+	t.is(camelCase('ipsum-dolor', {pascalCase: true, locale: 'tr-TR'}), 'İpsumDolor');
+	t.is(camelCase('ipsum-dolor', {pascalCase: true, locale: 'en-EN'}), 'IpsumDolor');
+	t.is(camelCase('ipsum-dolor', {pascalCase: true, locale: ['tr', 'TR', 'tr-TR']}), 'İpsumDolor');
+	t.is(camelCase('ipsum-dolor', {pascalCase: true, locale: ['en-EN', 'en-GB']}), 'IpsumDolor');
+});
+
 test('invalid input', t => {
 	t.throws(() => {
 		camelCase(1);