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

fix: fix logging in i18n v4 #446

Merged
merged 9 commits into from
Oct 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ Changelog

_Note: Gaps between patch versions are faulty, broken or test releases._

## v4.0.0-alpha.49 (2024-10-31)

#### :bug: Bug Fix

* `core/prelude/i18n/helpers`
* Fix logging bug in `pluralizeText`.
* Add logging info in i18n helpers.

## v4.0.0-alpha.48 (2024-10-30)

#### :bug: Bug Fix
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"main": "lib/core/index.js",
"typings": "index.d.ts",
"license": "MIT",
"version": "4.0.0-alpha.48",
"version": "4.0.0-alpha.49",
"author": "kobezzza <[email protected]> (https://github.com/kobezzza)",
"repository": {
"type": "git",
Expand Down
7 changes: 7 additions & 0 deletions src/core/prelude/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ Changelog
> - :house: [Internal]
> - :nail_care: [Polish]

## v4.0.0-alpha.49 (2024-10-31)

#### :bug: Bug Fix

* Fix logging bug in `pluralizeText`.
* Add logging info in i18n helpers.

## v4.0.0-alpha.47.speedup (2024-10-01)

#### :rocket: New Feature
Expand Down
28 changes: 15 additions & 13 deletions src/core/prelude/i18n/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,30 +89,32 @@ i18n('my-component')('My name is {name}', {name: 'John'});
## Pluralization of translations

Some keys may have multiple translations depending on some numeric value. For example, "1 apple" or "5 apples".
To specify such translations, a special macro `{count}` is used, and translations are specified as a tuple `[one, some, many, none]`.
To specify such translations, a special macro `{count}` is used, and translations are specified as a dictionary `{zero, one, two, few, many, other}`.

```js
export default {
ru: {
"my-component": {
"time": "время",
"{count} product": [
"{count} продукт",
"{count} продукта",
"{count} продуктов",
"{count} продуктов"
]
"{count} product": {
"one": "{count} product",
"few": "{count} products",
"many": "{count} products",
"zero": "{count} products",
"other": "{count} products",
}
}
},

en: {
"my-component": {
"{count} product": [
"{count} product",
"{count} products",
"{count} products",
"{count} products"
]
"{count} product": {
"one": "{count} product",
"few": "{count} products",
"many": "{count} products",
"zero": "{count} products",
"other": "{count} products",
}
}
}
};
Expand Down
56 changes: 35 additions & 21 deletions src/core/prelude/i18n/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import log from 'core/log';
import langPacs, { Translation, PluralTranslation } from 'lang';

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

const
logger = log.namespace('i18n');
Expand Down Expand Up @@ -50,18 +50,19 @@ export function i18nFactory(
const
key = Object.isString(value) ? value : value[0],
correctKeyset = keysetNames.find((keysetName) => langPacs[resolvedLocale]?.[keysetName]?.[key]),
translateValue = langPacs[resolvedLocale]?.[correctKeyset ?? '']?.[key];
translateValue = langPacs[resolvedLocale]?.[correctKeyset ?? '']?.[key],
shining-mind marked this conversation as resolved.
Show resolved Hide resolved
meta: I18nMeta = {language: resolvedLocale, keyset: correctKeyset, key};

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

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, {pluralRules});
return resolveTemplate(key, params, {pluralRules, meta});
};
}

Expand All @@ -70,26 +71,27 @@ export function i18nFactory(
*
* @param value - a string for the default case, or an array of strings for the plural case
* @param params - a dictionary with parameters for internationalization
* @params [opts] - additional options for current translation
*
* @example
* ```typescript
* const example = resolveTemplate('My name is {name}, I live in {city}', {name: 'John', city: 'Denver'});
*
* console.log(example); // 'My name is John, I live in Denver'
*
* const examplePluralize = resolveTemplate([
* {count} product, // One
* {count} products, // Some
* {count} products, // Many
* {count} products, // None
* ], {count: 5});
* const examplePluralize = resolveTemplate({
* one: {count} product,
* few: {count} products,
* many: {count} products,
* zero: {count} products,
* }, {count: 5});
*
* console.log(examplePluralize); // '5 products'
* ```
*/
export function resolveTemplate(value: Translation, params?: I18nParams, opts: I18nOpts = {}): string {
const
template = Object.isPlainObject(value) ? pluralizeText(value, params?.count, opts.pluralRules) : value;
template = Object.isPlainObject(value) ? pluralizeText(value, params?.count, opts) : value;

return template.replace(/{([^}]+)}/g, (_, key) => {
if (params?.[key] == null) {
Expand All @@ -106,24 +108,28 @@ export function resolveTemplate(value: Translation, params?: I18nParams, opts: I
*
* @param pluralTranslation - list of translation variants
* @param count - the value on the basis of which the form of pluralization will be selected
* @params [opts] - additional options for current translation
*
* @example
* ```typescript
* const result = pluralizeText([
* {count} product, // One
* {count} products, // Some
* {count} products, // Many
* {count} products, // None
* ], 5);
* const result = pluralizeText({
* one: {count} product,
* few: {count} products,
* many: {count} products,
* zero: {count} products,
* other: {count} products,
* }, 5, {pluralRules: new Intl.PluralRulse('en')});
*
* console.log(result); // '{count} products'
* ```
*/
export function pluralizeText(
pluralTranslation: PluralTranslation,
count: CanUndef<PluralizationCount>,
rules: CanUndef<Intl.PluralRules>
opts: I18nOpts = {}
): string {
const {pluralRules, meta} = opts;

let normalizedCount;

if (Object.isNumber(count)) {
Expand All @@ -138,16 +144,24 @@ export function pluralizeText(
}

if (normalizedCount == null) {
logger.error('Invalid value of the `count` parameter for string pluralization', `String: ${pluralTranslation[0]}`);
logger.error(
'Invalid value of the `count` parameter for string pluralization',
`Count: ${count}, Key: ${meta?.key}, Language: ${meta?.language}, Keyset: ${meta?.keyset}`
);

normalizedCount = 1;
}

const
pluralFormName = getPluralFormName(normalizedCount, rules),
pluralFormName = getPluralFormName(normalizedCount, pluralRules),
translation = pluralTranslation[pluralFormName];

if (translation == null) {
logger.error(`Plural form ${pluralFormName} doesn't exist.`, `String: ${pluralTranslation[0]}`);
logger.error(
`Plural form ${pluralFormName} doesn't exist.`,
`Key: ${meta?.key}, Language: ${meta?.language}, Keyset: ${meta?.keyset}`
);

return pluralTranslation.one;
}

Expand Down
8 changes: 8 additions & 0 deletions src/core/prelude/i18n/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@ export interface LocaleKVStorage {

export type PluralizationCount = StringPluralizationForms | number;

export interface I18nMeta {
language: string;
key: string;
keyset?: string;
}

export interface I18nOpts {
pluralRules?: Intl.PluralRules;
meta?: I18nMeta;
}

25 changes: 23 additions & 2 deletions src/core/prelude/i18n/spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ describe('core/prelude/i18n', () => {
describe('text pluralization', () => {
it('using pluralization constants to choose the right form', () => {
formNames.forEach((form) => {
expect(pluralizeText(forms, form, rules)).toBe(forms[form]);
expect(pluralizeText(forms, form, {pluralRules: rules})).toBe(forms[form]);
});
});

Expand All @@ -53,7 +53,28 @@ describe('core/prelude/i18n', () => {
};

[forms.one, forms.other, forms.other, forms.other].forEach((form, index) => {
expect(pluralizeText(input.forms, input.count[index], rules)).toBe(form);
expect(pluralizeText(input.forms, input.count[index], {pluralRules: rules})).toBe(form);
});
});

it('returns "one" form when required plural form is missing', () => {
const input = {
forms,
count: [1, 2, 100, 0]
};

[forms.one, forms.one, forms.one, forms.one].forEach((form, index) => {
expect(pluralizeText({one: input.forms.one}, input.count[index], {pluralRules: rules})).toBe(form);
});
});

it('returns "one" form when count is invalid', () => {
const input = {
forms
};

[forms.one, forms.one, forms.one, forms.one].forEach((form) => {
expect(pluralizeText({one: input.forms.one}, undefined, {pluralRules: rules})).toBe(form);
});
});
});
Expand Down
Loading