Repository: sindresorhus/camelcase
Pull request #92 (2021-12-19 16:37:31)

Add option to disable the platform locale
As discussed in https://github.com/sindresorhus/camelcase-keys/issues/81

sindresorhus: You also need to update index.d.ts
jhnns: I've updated the PR, ready to be reviewed again :)

Patch:
diff --git a/index.d.ts b/index.d.ts
index a78f60b..9db94e5 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -17,6 +17,8 @@ declare namespace camelcase {
 		/**
 		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.
 
+		Setting `locale: false` ignores the platform locale and uses the [Unicode Default Case Conversion](https://unicode-org.github.io/icu/userguide/transforms/casemappings.html#simple-single-character-case-mapping) algorithm.
+
 		Default: The host environment’s current locale.
 
 		@example
@@ -33,7 +35,7 @@ declare namespace camelcase {
 		//=> 'loremİpsum'
 		```
 		*/
-		readonly locale?: string | readonly string[];
+		readonly locale?: false | string | readonly string[];
 	}
 }
 
diff --git a/index.js b/index.js
index b501478..6ff4ee8 100644
--- a/index.js
+++ b/index.js
@@ -10,7 +10,7 @@ const LEADING_SEPARATORS = new RegExp('^' + SEPARATORS.source);
 const SEPARATORS_AND_IDENTIFIER = new RegExp(SEPARATORS.source + IDENTIFIER.source, 'gu');
 const NUMBERS_AND_IDENTIFIER = new RegExp('\\d+' + IDENTIFIER.source, 'gu');
 
-const preserveCamelCase = (string, locale) => {
+const preserveCamelCase = (string, toLowerCase, toUpperCase) => {
 	let isLastCharLower = false;
 	let isLastCharUpper = false;
 	let isLastLastCharUpper = false;
@@ -30,27 +30,27 @@ const preserveCamelCase = (string, locale) => {
 			isLastCharUpper = false;
 			isLastCharLower = true;
 		} else {
-			isLastCharLower = character.toLocaleLowerCase(locale) === character && character.toLocaleUpperCase(locale) !== character;
+			isLastCharLower = toLowerCase(character) === character && toUpperCase(character) !== character;
 			isLastLastCharUpper = isLastCharUpper;
-			isLastCharUpper = character.toLocaleUpperCase(locale) === character && character.toLocaleLowerCase(locale) !== character;
+			isLastCharUpper = toUpperCase(character) === character && toLowerCase(character) !== character;
 		}
 	}
 
 	return string;
 };
 
-const preserveConsecutiveUppercase = input => {
+const preserveConsecutiveUppercase = (input, toLowerCase) => {
 	LEADING_CAPITAL.lastIndex = 0;
 
-	return input.replace(LEADING_CAPITAL, m1 => m1.toLowerCase());
+	return input.replace(LEADING_CAPITAL, m1 => toLowerCase(m1));
 };
 
-const postProcess = (input, options) => {
+const postProcess = (input, toUpperCase) => {
 	SEPARATORS_AND_IDENTIFIER.lastIndex = 0;
 	NUMBERS_AND_IDENTIFIER.lastIndex = 0;
 
-	return input.replace(SEPARATORS_AND_IDENTIFIER, (_, identifier) => identifier.toLocaleUpperCase(options.locale))
-		.replace(NUMBERS_AND_IDENTIFIER, m => m.toLocaleUpperCase(options.locale));
+	return input.replace(SEPARATORS_AND_IDENTIFIER, (_, identifier) => toUpperCase(identifier))
+		.replace(NUMBERS_AND_IDENTIFIER, m => toUpperCase(m));
 };
 
 const camelCase = (input, options) => {
@@ -76,29 +76,36 @@ const camelCase = (input, options) => {
 		return '';
 	}
 
+	const toLowerCase = options.locale === false ?
+		string => string.toLowerCase() :
+		string => string.toLocaleLowerCase(options.locale);
+	const toUpperCase = options.locale === false ?
+		string => string.toUpperCase() :
+		string => string.toLocaleUpperCase(options.locale);
+
 	if (input.length === 1) {
-		return options.pascalCase ? input.toLocaleUpperCase(options.locale) : input.toLocaleLowerCase(options.locale);
+		return options.pascalCase ? toUpperCase(input) : toLowerCase(input);
 	}
 
-	const hasUpperCase = input !== input.toLocaleLowerCase(options.locale);
+	const hasUpperCase = input !== toLowerCase(input);
 
 	if (hasUpperCase) {
-		input = preserveCamelCase(input, options.locale);
+		input = preserveCamelCase(input, toLowerCase, toUpperCase);
 	}
 
 	input = input.replace(LEADING_SEPARATORS, '');
 
 	if (options.preserveConsecutiveUppercase) {
-		input = preserveConsecutiveUppercase(input);
+		input = preserveConsecutiveUppercase(input, toLowerCase);
 	} else {
-		input = input.toLocaleLowerCase();
+		input = toLowerCase(input);
 	}
 
 	if (options.pascalCase) {
-		input = input.charAt(0).toLocaleUpperCase(options.locale) + input.slice(1);
+		input = toUpperCase(input.charAt(0)) + input.slice(1);
 	}
 
-	return postProcess(input, options);
+	return postProcess(input, toUpperCase);
 };
 
 module.exports = camelCase;
diff --git a/readme.md b/readme.md
index 7d0d610..0ff8f9e 100644
--- a/readme.md
+++ b/readme.md
@@ -94,7 +94,7 @@ Preserve the consecutive uppercase characters: `foo-BAR` → `FooBAR`.
 
 ##### locale
 
-Type: `string | string[]`\
+Type: `false | 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.
@@ -115,6 +115,20 @@ camelCase('lorem-ipsum', {locale: ['tr', 'TR', 'tr-TR']});
 //=> 'loremİpsum'
 ```
 
+Setting `locale: false` ignores the platform locale and uses the [Unicode Default Case Conversion](https://unicode-org.github.io/icu/userguide/transforms/casemappings.html#simple-single-character-case-mapping) algorithm:
+
+```js
+const camelCase = require('camelcase');
+
+// On a platform with 'tr-TR'
+
+camelCase('lorem-ipsum');
+//=> 'loremİpsum'
+
+camelCase('lorem-ipsum', {locale: false});
+//=> 'loremIpsum'
+```
+
 ## camelcase for enterprise
 
 Available as part of the Tidelift Subscription.


Test patch:
diff --git a/test.js b/test.js
index 56dfee1..0e4e857 100644
--- a/test.js
+++ b/test.js
@@ -204,8 +204,43 @@ test('camelCase with locale option', t => {
 	t.is(camelCase('ipsum-dolor', {pascalCase: true, locale: ['en-EN', 'en-GB']}), 'IpsumDolor');
 });
 
+test('camelCase with disabled locale', t => {
+	withLocaleCaseFunctionsMocked(() => {
+		t.is(camelCase('lorem-ipsum', {locale: false}), 'loremIpsum');
+		t.is(camelCase('ipsum-dolor', {pascalCase: true, locale: false}), 'IpsumDolor');
+		t.is(camelCase('ipsum-DOLOR', {pascalCase: true, locale: false, preserveConsecutiveUppercase: true}), 'IpsumDOLOR');
+	});
+});
+
 test('invalid input', t => {
 	t.throws(() => {
 		camelCase(1);
 	}, /Expected the input to be/);
 });
+
+/* eslint-disable no-extend-native */
+const withLocaleCaseFunctionsMocked = fn => {
+	const throwWhenBeingCalled = () => {
+		throw new Error('Should not be called');
+	};
+
+	const toLocaleUpperCase = Object.getOwnPropertyDescriptor(String.prototype, 'toLocaleUpperCase');
+	const toLocaleLowerCase = Object.getOwnPropertyDescriptor(String.prototype, 'toLocaleLowerCase');
+
+	Object.defineProperty(String.prototype, 'toLocaleUpperCase', {
+		...toLocaleUpperCase,
+		value: throwWhenBeingCalled
+	});
+	Object.defineProperty(String.prototype, 'toLocaleLowerCase', {
+		...toLocaleLowerCase,
+		value: throwWhenBeingCalled
+	});
+
+	try {
+		fn();
+	} finally {
+		Object.defineProperty(String.prototype, 'toLocaleUpperCase', toLocaleUpperCase);
+		Object.defineProperty(String.prototype, 'toLocaleLowerCase', toLocaleLowerCase);
+	}
+};
+/* eslint-enable no-extend-native */