Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add locale option #75

Merged
merged 16 commits into from Oct 10, 2020
19 changes: 19 additions & 0 deletions index.d.ts
Expand Up @@ -6,6 +6,12 @@ declare namespace camelcase {
@default false
*/
readonly pascalCase?: boolean;

/**
Convert characters with given locale(s). See String.prototype.toLocaleLowerCase().
@default undefined. Uses host's current locale when not provided.
*/
readonly locale?: string | string[];
}
}

Expand Down Expand Up @@ -51,6 +57,19 @@ camelCase(['foo', 'bar']);

camelCase(['__foo__', '--bar'], {pascalCase: true});
//=> 'FooBar'

camelCase('lorem-ipsum', {locale: 'en-US'});
//=> 'loremIpsum'
rasgele marked this conversation as resolved.
Show resolved Hide resolved

camelCase('lorem-ipsum', {locale: 'tr-TR'});
//=> 'loremİpsum'

camelCase('lorem-ipsum', {locale: ['en-US', 'en-GB]});
//=> 'loremIpsum'
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The readme and index.d.ts should be fully in sync.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am a little confused about this. I interpreted your next comment as "Keep a single example in index.d.ts and move others to readme.md file", which breaks sync.


camelCase('lorem-ipsum', {locale: ['tr', 'TR', 'tr-TR']});
//=> 'loremİpsum'

```
*/
declare function camelcase(
Expand Down
20 changes: 10 additions & 10 deletions index.js
@@ -1,6 +1,6 @@
'use strict';

const preserveCamelCase = string => {
const preserveCamelCase = (string, locale) => {
let isLastCharLower = false;
let isLastCharUpper = false;
let isLastLastCharUpper = false;
Expand All @@ -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;
}
}

Expand All @@ -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())
Expand All @@ -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);
};
Expand Down
4 changes: 4 additions & 0 deletions index.test-d.ts
Expand Up @@ -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}));
13 changes: 13 additions & 0 deletions readme.md
Expand Up @@ -48,6 +48,12 @@ camelCase(['foo', 'bar']);

camelCase(['__foo__', '--bar'], {pascalCase: true});
//=> 'FooBar'

camelCase('lorem-ipsum', {locale: 'en-US'});
//=> 'loremIpsum'

camelCase('lorem-ipsum', {locale: 'tr-TR'});
//=> 'loremİpsum'
```

## API
Expand All @@ -71,6 +77,13 @@ Default: `false`

Uppercase the first character: `foo-bar` → `FooBar`

##### locale

Type: `string | string[]`\
Default: `undefined`
sindresorhus marked this conversation as resolved.
Show resolved Hide resolved

Locale to be used with lowercase or uppercase conversions.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to explicitly say what the expected string locale format it. (for example, what en-US refers to).

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's also unclear why one would want to specify multiple locales and how that works.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated with an explicit reference to String.prototype.toLocaleUpperCase() and excerpt from that page. It explains format and usage with multiple locale.
Details (like expected format being BCP 47 etc.) seemed too much in this context, but it is up to you.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One example in Usage and all in the API docs for locale.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.


## camelcase for enterprise

Available as part of the Tidelift Subscription.
Expand Down
8 changes: 8 additions & 0 deletions test.js
Expand Up @@ -64,6 +64,10 @@ test('camelCase', t => {
t.is(camelCase('桑德在这里。'), '桑德在这里。');
t.is(camelCase('桑德在这里。'), '桑德在这里。');
t.is(camelCase('桑德_在这里。'), '桑德在这里。');
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');
sindresorhus marked this conversation as resolved.
Show resolved Hide resolved
});

test('camelCase with pascalCase option', t => {
Expand Down Expand Up @@ -128,6 +132,10 @@ test('camelCase with pascalCase option', t => {
t.is(camelCase('РОЗОВЫЙ_ПУШИСТЫЙ-ЕДИНОРОГИ', {pascalCase: true}), 'РозовыйПушистыйЕдинороги');
t.is(camelCase('桑德在这里。', {pascalCase: true}), '桑德在这里。');
t.is(camelCase('桑德_在这里。', {pascalCase: true}), '桑德在这里。');
t.is(camelCase('lorem-ipsum', {pascalCase: true, locale: 'tr-TR'}), 'Loremİpsum');
t.is(camelCase('lorem-ipsum', {pascalCase: true, locale: 'en-EN'}), 'LoremIpsum');
t.is(camelCase('lorem-ipsum', {pascalCase: true, locale: ['tr', 'TR', 'tr-TR']}), 'Loremİpsum');
t.is(camelCase('lorem-ipsum', {pascalCase: true, locale: ['en-EN', 'en-GB']}), 'LoremIpsum');
});

test('invalid input', t => {
Expand Down