From 65ccea6ac473f0757f938f9ec413a5b0cedd071e Mon Sep 17 00:00:00 2001 From: Johannes Ewald Date: Sat, 1 Jan 2022 21:28:23 +0100 Subject: [PATCH] Add option to disable the platform locale (#92) Co-authored-by: Sindre Sorhus --- index.d.ts | 4 +++- index.js | 37 ++++++++++++++++++++++--------------- readme.md | 16 +++++++++++++++- test.js | 35 +++++++++++++++++++++++++++++++++++ 4 files changed, 75 insertions(+), 17 deletions(-) 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. 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 */