Skip to content

Commit

Permalink
refactor logic
Browse files Browse the repository at this point in the history
  • Loading branch information
Maksim Sinelnikov committed Sep 24, 2024
1 parent 99684ee commit f684b73
Show file tree
Hide file tree
Showing 6 changed files with 50 additions and 51 deletions.
2 changes: 0 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ _Note: Gaps between patch versions are faulty, broken or test releases._
* changed `i18n` translations format.
* added `intl` support for pluralization.
* now `i18n` prefer to use `intl` api for pluralization if it's possible, otherwise fallback to old plural form logic.
* now variables for template passed in `vars` field of `i18nparams` object.
* for the `count` variable, you don't have to duplicate the value in the `vars` field. it will work like shortcut.

## v3.100.1 (2024-09-10)

Expand Down
8 changes: 4 additions & 4 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,11 +173,11 @@ declare function i18n(
/**
* Parameters for the internationalization function
*/
interface I18nParams {
type I18nParams = {
count?: number | StringPluralizationForms;
rules?: Intl.PluralRules;
vars?: {[key: string]: string | number};
}
} & {
[key: string]: string | number;
};

/**
* String pluralization constants that can be used instead of numbers
Expand Down
2 changes: 0 additions & 2 deletions src/core/prelude/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ Changelog
* changed `i18n` translations format.
* added `intl` support for pluralization.
* now `i18n` prefer to use `intl` api for pluralization if it's possible, otherwise fallback to old plural form logic.
* now variables for template passed in `vars` field of `i18nparams` object.
* for the `count` variable, you don't have to duplicate the value in the `vars` field. it will work like shortcut.

## v3.99.0 (2024-04-25)

Expand Down
56 changes: 33 additions & 23 deletions src/core/prelude/i18n/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import extend from 'core/prelude/extend';
import langPacs, { Translation, PluralTranslation } from 'lang';

import { locale } from 'core/prelude/i18n/const';
import type { PluralizationCount } from 'core/prelude/i18n/interface';
import type { I18nOpts, PluralizationCount } from 'core/prelude/i18n/interface';

/** @see [[i18n]] */
extend(globalThis, 'i18n', i18nFactory);
Expand Down Expand Up @@ -44,32 +44,28 @@ export function i18nFactory(
throw new ReferenceError('The locale for internationalization is not defined');
}

const pluralRules: CanUndef<Intl.PluralRules> = 'PluralRules' in globalThis['Intl'] ? new globalThis['Intl'].PluralRules(resolvedLocale) : undefined;
const pluralRules: CanUndef<Intl.PluralRules> = getPluralRules(resolvedLocale);

return function i18n(value: string | TemplateStringsArray, params?: I18nParams) {
if (Object.isArray(value) && value.length !== 1) {
throw new SyntaxError('Using i18n with template literals is allowed only without variables');
}

if (params?.rules == null) {
params = Object.mixin({deep: true}, params, {rules: pluralRules});
}

const
key = Object.isString(value) ? value : value[0],
correctKeyset = keysetNames.find((keysetName) => langPacs[resolvedLocale]?.[keysetName]?.[key]),
translateValue = langPacs[resolvedLocale]?.[correctKeyset ?? '']?.[key];

if (translateValue != null && translateValue !== '') {
return resolveTemplate(translateValue, params);
return resolveTemplate(translateValue, params, {pluralRules});
}

logger.error(
'Translation for the given key is not found',
`Key: ${key}, KeysetNames: ${keysetNames.join(', ')}, LocaleName: ${resolvedLocale}, available locales: ${Object.keys(langPacs).join(', ')}`
);

return resolveTemplate(key, params);
return resolveTemplate(key, params, {pluralRules});
};
}

Expand All @@ -95,27 +91,17 @@ export function i18nFactory(
* console.log(examplePluralize); // '5 products'
* ```
*/
export function resolveTemplate(value: Translation, params?: I18nParams): string {
export function resolveTemplate(value: Translation, params?: I18nParams, opts: I18nOpts = {}): string {
const
template = Object.isPlainObject(value) ? pluralizeText(value, params?.count, params?.rules) : value;
template = Object.isPlainObject(value) ? pluralizeText(value, params?.count, opts.pluralRules) : value;

return template.replace(/{([^}]+)}/g, (_, key) => {
if (params?.vars?.[key] == null && params?.[key] == null) {
if (params?.[key] == null) {
logger.error('Undeclared variable', `Name: "${key}", Template: "${template}"`);
return key;
}

const varsValue = params.vars?.[key];

if (varsValue != null) {
return varsValue;
}

const shortcutValue = params[key];

if (shortcutValue != null) {
return shortcutValue;
}
return params[key];
});
}

Expand Down Expand Up @@ -174,7 +160,21 @@ export function pluralizeText(
return translation;
}

function getPluralFormName(n: number, rules: CanUndef<Intl.PluralRules>): keyof Required<PluralTranslation> {
/**
* Returns the plural form name for a given number `n` based on the specified pluralization rules.
* Otherwise will be used default set of rules.
*
* If a `rules` object implementing `Intl.PluralRules` is provided, it will use that to determine the plural form.
* Otherwise, it will fall back to a custom rule set:
* - Returns 'zero' for `n === 0`.
* - Returns 'one' for `n === 1`.
* - Returns 'few' for `n > 1 && n < 5`.
* - Returns 'many' for all other values of `n`.
*
* @param n - The number to evaluate for pluralization.
* @param rules - Plural rules object. If undefined, a default rule set is used.
*/
function getPluralFormName(n: number, rules?: CanUndef<Intl.PluralRules>): keyof Required<PluralTranslation> {
if (rules != null) {
return <keyof PluralTranslation>rules.select(n);
}
Expand All @@ -194,3 +194,13 @@ function getPluralFormName(n: number, rules: CanUndef<Intl.PluralRules>): keyof
return 'many';
}
}

/**
* Returns an instance of `Intl.PluralRules` for a given locale, if supported.
* @param locale - The locale for which to generate plural rules.
*/
export function getPluralRules(locale: Language): CanUndef<Intl.PluralRules> {
if ('PluralRules' in globalThis['Intl']) {
return new globalThis['Intl'].PluralRules(locale);
}
}
4 changes: 4 additions & 0 deletions src/core/prelude/i18n/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,7 @@ export interface LocaleKVStorage {
}

export type PluralizationCount = StringPluralizationForms | number;

export interface I18nOpts {
pluralRules?: Intl.PluralRules;
}
29 changes: 9 additions & 20 deletions src/core/prelude/i18n/spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,12 @@ describe('core/prelude/i18n', () => {

it('passing variables for template resolving', () => {
const tpl = 'foo {macros} {macros2}';
expect(resolveTemplate(tpl, {vars: {macros: 'bar', macros2: 'baz'}})).toBe('foo bar baz');
expect(resolveTemplate(tpl, {macros: 'bar', macros2: 'baz'})).toBe('foo bar baz');
});

it('if the variable is not set, then it should be displayed as text', () => {
const tpl = 'foo {macros} {macros2}';
expect(resolveTemplate(tpl, {vars: {macros: 'bar'}})).toBe('foo bar macros2');
expect(resolveTemplate(tpl, {macros: 'bar'})).toBe('foo bar macros2');
});

it('passing the `count` parameter for template resolving', () => {
Expand All @@ -62,39 +62,28 @@ describe('core/prelude/i18n', () => {
few: 'few {count}',
many: 'many {count}',
other: 'other {count}'
}, {count: 5, rules});
}, {count: 5}, {pluralRules: rules});

expect(res).toBe('other 5');
});

it('override `count` shortcut variable', () => {
const res = resolveTemplate({
one: 'one {count}',
few: 'few {count}',
many: 'many {count}',
other: 'other {count}'
}, {count: 5, rules, vars: {count: 12}});

expect(res).toBe('other 12');
});
});

describe('pluralization for cyrillic language', () => {
it('russian language', () => {
const
rules = new Intl.PluralRules('ru'),
cyrillicRules = new Intl.PluralRules('ru'),
forms = {
one: '{count} яблоко',
few: '{count} яблока',
many: '{count} яблок',
zero: '{count} яблок'
};

expect(resolveTemplate(forms, {count: 1, rules})).toBe('1 яблоко');
expect(resolveTemplate(forms, {count: 2, rules})).toBe('2 яблока');
expect(resolveTemplate(forms, {count: 0, rules})).toBe('0 яблок');
expect(resolveTemplate(forms, {count: 12, rules})).toBe('12 яблок');
expect(resolveTemplate(forms, {count: 22, rules})).toBe('22 яблока');
expect(resolveTemplate(forms, {count: 1}, {pluralRules: cyrillicRules})).toBe('1 яблоко');
expect(resolveTemplate(forms, {count: 2}, {pluralRules: cyrillicRules})).toBe('2 яблока');
expect(resolveTemplate(forms, {count: 0}, {pluralRules: cyrillicRules})).toBe('0 яблок');
expect(resolveTemplate(forms, {count: 12}, {pluralRules: cyrillicRules})).toBe('12 яблок');
expect(resolveTemplate(forms, {count: 22}, {pluralRules: cyrillicRules})).toBe('22 яблока');
});
});
});

0 comments on commit f684b73

Please sign in to comment.