Skip to content

Commit

Permalink
i18n interpolation and plurals support (#83)
Browse files Browse the repository at this point in the history
* Add string interpolation and support for plurals to i18n module

* Add installmentOptions translation

* Use arrow function in language replaceTranslationValues
  • Loading branch information
marcperez authored Jul 6, 2020
1 parent 29bbd51 commit 9946759
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ function Installments(props: InstallmentsProps) {

const installmentItemsMapper = (value: number): InstallmentsItem => ({
id: value,
name: amount.value ? `${value}x ${getPartialAmount(value)}` : `${value}`
name: amount.value
? i18n.get('installmentOption', { count: value, values: { times: value, partialValue: getPartialAmount(value) } })
: `${value}`
});

useEffect(() => {
Expand Down
5 changes: 3 additions & 2 deletions src/language/Language.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,11 @@ export class Language {
/**
* Returns a translated string from a key in the current {@link Language.locale}
* @param key - Translation key
* @param options - Translation options
* @returns Translated string
*/
get(key: string): string {
const translation = getTranslation(this.translations, key);
get(key: string, options?): string {
const translation = getTranslation(this.translations, key, options);
if (translation !== null) {
return translation;
}
Expand Down
1 change: 1 addition & 0 deletions src/language/locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"creditCard.cvcField.placeholder.4digits": "4 digits",
"creditCard.cvcField.placeholder.3digits": "3 digits",
"installments": "Number of installments",
"installmentOption": "%{times}x %{partialValue}",
"sepaDirectDebit.ibanField.invalid": "Invalid account number",
"sepaDirectDebit.nameField.placeholder": "J. Smith",
"sepa.ownerName": "Holder Name",
Expand Down
32 changes: 29 additions & 3 deletions src/language/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,13 +82,39 @@ describe('formatLocale()', () => {
});

describe('getTranslation()', () => {
const translations = {
myTranslation: 'My translation'
};
const translations = { myTranslation: 'My translation', myTranslation__plural: 'My translations', myTranslation__2: 'My two translations' };

test('should get a translation with a matching key', () => {
expect(getTranslation(translations, 'myTranslation')).toBe('My translation');
});

test('should get a translation with values', () => {
expect(getTranslation({ myTranslation: 'My %{type} translation' }, 'myTranslation', { values: { type: 'custom' } })).toBe(
'My custom translation'
);
});

test('should get a translation with empty values', () => {
expect(getTranslation({ myTranslation: 'My %{type} translation' }, 'myTranslation')).toBe('My translation');
});

test('should get a plural translation if available', () => {
expect(getTranslation(translations, 'myTranslation', { count: 3 })).toBe('My translations');
});

test('should get a specific count translation if available', () => {
expect(getTranslation(translations, 'myTranslation', { count: 2 })).toBe('My two translations');
});

test('should get the default translation if count is not greater than 1', () => {
expect(getTranslation(translations, 'myTranslation', { count: 1 })).toBe('My translation');
expect(getTranslation(translations, 'myTranslation', { count: 0 })).toBe('My translation');
expect(getTranslation(translations, 'myTranslation', { count: -1 })).toBe('My translation');
});

test('should get the default translation if count is not provided', () => {
expect(getTranslation(translations, 'myTranslation')).toBe('My translation');
});
});

describe('formatCustomTranslations()', () => {
Expand Down
21 changes: 18 additions & 3 deletions src/language/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,16 +82,31 @@ export function formatCustomTranslations(customTranslations: object = {}, suppor
}, {});
}

const replaceTranslationValues = (translation, values) => {
return translation.replace(/%{(\w+)}/g, (_, k) => values[k] || '');
};

/**
* Returns a translation string by key
* @param translations -
* @param key -
* @param options -
*
* @internal
*/
export const getTranslation = (translations: object, key: string): string => {
if (Object.prototype.hasOwnProperty.call(translations, key)) {
return translations[key];
export const getTranslation = (translations: object, key: string, options: { [key: string]: any } = { values: {}, count: 0 }): string => {
const keyPlural = `${key}__plural`;
const keyForCount = count => `${key}__${count}`;

if (Object.prototype.hasOwnProperty.call(translations, keyForCount(options.count))) {
// Find key__count translation key
return replaceTranslationValues(translations[keyForCount(options.count)], options.values);
} else if (Object.prototype.hasOwnProperty.call(translations, keyPlural) && options.count > 1) {
// Find key__plural translation key, if count greater than 1 (e.g. myTranslation__plural)
return replaceTranslationValues(translations[keyPlural], options.values);
} else if (Object.prototype.hasOwnProperty.call(translations, key)) {
// Find key translation key (e.g. myTranslation)
return replaceTranslationValues(translations[key], options.values);
}

return null;
Expand Down

0 comments on commit 9946759

Please sign in to comment.