{"id":24755155,"url":"https://github.com/gravity-ui/i18n","last_synced_at":"2026-02-13T10:02:44.148Z","repository":{"id":38302765,"uuid":"433750944","full_name":"gravity-ui/i18n","owner":"gravity-ui","description":null,"archived":false,"fork":false,"pushed_at":"2025-04-08T13:44:00.000Z","size":575,"stargazers_count":13,"open_issues_count":0,"forks_count":5,"subscribers_count":9,"default_branch":"main","last_synced_at":"2025-09-30T23:12:41.347Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/gravity-ui.png","metadata":{"files":{"readme":"README-ru.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":"CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":"AUTHORS","dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-12-01T08:53:00.000Z","updated_at":"2025-06-25T02:33:42.000Z","dependencies_parsed_at":"2023-11-10T11:23:57.295Z","dependency_job_id":"9e01be7d-b366-4fc3-a5e4-28564bf9f03f","html_url":"https://github.com/gravity-ui/i18n","commit_stats":{"total_commits":31,"total_committers":10,"mean_commits":3.1,"dds":0.6774193548387097,"last_synced_commit":"666dd337e6f7d5e3f4c05d5b2d000564bc411788"},"previous_names":["yandex-cloud/i18n"],"tags_count":19,"template":false,"template_full_name":null,"purl":"pkg:github/gravity-ui/i18n","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gravity-ui%2Fi18n","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gravity-ui%2Fi18n/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gravity-ui%2Fi18n/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gravity-ui%2Fi18n/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gravity-ui","download_url":"https://codeload.github.com/gravity-ui/i18n/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gravity-ui%2Fi18n/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279002667,"owners_count":26083442,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-10T02:00:06.843Z","response_time":62,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-01-28T12:36:36.014Z","updated_at":"2025-10-11T01:31:24.333Z","avatar_url":"https://github.com/gravity-ui.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# @gravity-ui/i18n \u0026middot; [![npm package](https://img.shields.io/npm/v/@gravity-ui/i18n)](https://www.npmjs.com/package/@gravity-ui/i18n) [![CI](https://img.shields.io/github/actions/workflow/status/gravity-ui/i18n/.github/workflows/ci.yml?branch=main\u0026label=CI\u0026logo=github)](https://github.com/gravity-ui/i18n/actions/workflows/ci.yml?query=branch:main)\n\n## Утилиты I18N\n\nУтилиты пакета `I18N` разработаны для интернационализации компонентов Gravity UI.\n\n### Установка\n\n`npm install --save @gravity-ui/i18n`\n\n### API\n\n#### Конструктор (параметры)\n\nПринимает объект `options`, включающий необязательный параметр `logger` для логирования предупреждений библиотеки.\n\n##### Логгер\n\nЛоггер должен содержать явно определенный метод `log` со следующей сигнатурой:\n\n* `message` — строка сообщения, которое будет записано в лог;\n* `options` — объект параметров логирования:\n  * `severity` — уровень логирования сообщения, всегда принимает значение `level`.\n  * `logger` — определяет место для записи сообщений библиотеки.\n  * `extra` — дополнительные параметры с единственным строковым полем `type`, которое всегда принимает значение `i18n`.\n\n### Примеры использования\n\n#### `keysets/en.json`\n\n```json\n{\n  \"wizard\": {\n    \"label_error-widget-no-access\": \"No access to the chart\"\n  }\n}\n```\n\n#### `keysets/ru.json`\n\n```json\n{\n  \"wizard\": {\n    \"label_error-widget-no-access\": \"Нет доступа к чарту\"\n  }\n}\n```\n\n#### `index.js`\n\n```js\nconst ru = require('./keysets/ru.json');\nconst en = require('./keysets/en.json');\n\nconst {I18N} = require('@gravity-ui/i18n');\n\nconst i18n = new I18N();\ni18n.registerKeysets('ru', ru);\ni18n.registerKeysets('en', en);\n\ni18n.setLang('ru');\nconsole.log(\n    i18n.i18n('wizard', 'label_error-widget-no-access')\n); // -\u003e \"Нет доступа к чарту\"\n\ni18n.setLang('en');\nconsole.log(\n    i18n.i18n('wizard', 'label_error-widget-no-access')\n); // -\u003e \"No access to the chart\n\n// Keyset allows for a simpler translations retrieval\nconst keyset = i18n.keyset('wizard');\nconsole.log(\n    keyset('label_error-widget-no-access')\n); // -\u003e \"No access to the chart\"\n\n\ni18n.setLang('ru');\nconsole.log(\n    keyset('label_error-widget-no-access')\n); // -\u003e \"Нет доступа к чарту\"\n\n// Checking if keyset has a key\nif (i18n.has('wizard', 'label_error-widget-no-access')) {\n    i18n.i18n('wizard', 'label_error-widget-no-access')\n}\n```\n\n### Шаблонизация\n\nБиблиотека поддерживает шаблонизацию. Шаблонизируемые переменные заключаются в двойные фигурные скобки, а значения передаются в функцию i18n в форме словаря с парами «ключ-значение»:\n\n#### `keysets.json`\n\n```json\n{\n  \"label_template\": \"No matches found for '{{inputValue}}' in '{{folderName}}'\"\n}\n```\n\n#### `index.js`\n\n```js\ni18n('label_template', {inputValue: 'something', folderName: 'somewhere'});  // =\u003e No matches found for \"something\" in \"somewhere\"\n```\n\n### Плюрализация\n\nДля удобной локализации ключей, зависящих от числового значения, можно использовать плюрализацию. Текущая библиотека использует [правила плюрализации CLDR](https://unicode-org.github.io/cldr-staging/charts/latest/supplemental/language_plural_rules.html) через [API `Intl.PluralRules`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/PluralRules).\n\nМожет потребоваться добавление [полифила](https://github.com/eemeli/intl-pluralrules) для [API `Intl.Plural Rules`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/PluralRules), если он недоступен в браузере.\n\nСуществует 6 форм множественного числа (см. [`resolvedOptions`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/PluralRules/resolvedOptions)):\n\n* `zero` (также используется, когда `count = 0`, даже если форма не поддерживается в языке);\n* `one` (единственное число);\n* `two` (двойственное число);\n* `few` (паукальное число для обозначения нескольких предметов);\n* `many` (множественное; также используется для дробей, если у них есть отдельный класс);\n* `other` (общая форма множественного числа, обязательная для всех языков; также используется, если язык поддерживает только одну форму).\n\n#### Пример `keysets.json` с ключом для плюрализации\n\n```json\n{\n  \"label_seconds\": {\n    \"one\": \"{{count}} second is left\",\n    \"other\":\"{{count}} seconds are left\",\n    \"zero\": \"No time left\"\n  }\n}\n```\n\n#### Использование в JavaScript\n\n```js\ni18n('label_seconds', {count: 1});  // =\u003e 1 second\ni18n('label_seconds', {count: 3});  // =\u003e 3 seconds\ni18n('label_seconds', {count: 7});  // =\u003e 7 seconds\ni18n('label_seconds', {count: 10}); // =\u003e 10 seconds\ni18n('label_seconds', {count: 0});  // =\u003e No time left\n```\n\n#### Старый формат плюрализации (устаревший формат)\n\nСтарый формат будет удален в версии 2.\n\n```json\n{\n  \"label_seconds\": [\"{{count}} second is left\", \"{{count}} seconds are left\", \"{{count}} seconds are left\", \"No time left\"]\n}\n```\n\nКлюч плюрализации содержит 4 значения, каждое из которых соответствует значению перечисления `PluralForm`.| Значения перечисления: `One`, `Few`, `Many` и `None` соответственно. Имя переменной шаблона плюрализации — `count`.\n\n#### Пользовательская плюрализация (устаревшее свойство)\n\nТак как у каждого языка свои правила плюрализации, библиотека предоставляет метод для настройки этих правил для любого выбранного языка.\n\nФункция конфигурации принимает объект с языками в качестве ключей и функциями плюрализации в качестве значений.\n\nФункция плюрализации принимает число и перечисление `PluralForm` и должна возвращать одно из значений перечисления в зависимости от переданного числа.\n\n```js\nconst {I18N} = require('@gravity-ui/i18n');\n\nconst i18n = new I18N();\n\ni18n.configurePluralization({\n  en: (count, pluralForms) =\u003e {\n    if (!count) return pluralForms.None;\n    if (count === 1) return pluralForms.One;\n    return pluralForms.Many;\n  },\n});\n```\n\n#### Предустановленные наборы правил плюрализации (устаревшие правила)\n\nБиблиотека изначально поддерживает два языка: английский и русский.\n\n##### Английский\n\nКлюч языка — `en`.\n\n* `One` соответствует 1 и -1.\n* `Few` не используется.\n* `Many` соответствует любому числу, кроме 0.\n* `None` соответствует 0.\n\n##### Русский\n\nКлюч языка — `ru`.\n\n* `One` соответствует любому числу, оканчивающемуся на 1, кроме ±11.\n* `Few` соответствует любому числу, оканчивающемуся на 2, 3 или 4, кроме ±12, ±13 и ±14.\n* `Many` соответствует любому прочему числу, кроме 0.\n* `None` соответствует 0.\n\n##### Значение по умолчанию\n\nЕсли для языка не настроена функция плюрализации, используется набор правил для английского языка.\n\n### Вложенность\n\n\u003c!--GITHUB_BLOCK--\u003e\n\u003cspan style=\"color:red\"\u003e\n\u003c!--/GITHUB_BLOCK--\u003e\n\n\u003c!--LANDING_BLOCK\n\u003cspan style={{color: 'red'}}\u003e\nLANDING_BLOCK--\u003e\n\nГлубина вложенности ключей ограничена одним уровнем (для глоссария).\n\u003c/span\u003e\n\nВложенность позволяет ссылаться на другие ключи в переводе, что удобно для формирования глоссариев.\n\n#### Базовый уровень\n\nКлючи\n\n```json\n{\n  \"nesting1\": \"1 $t{nesting2}\",\n  \"nesting2\": \"2\",\n}\n```\n\nПример\n\n```ts\ni18n('nesting1'); // -\u003e \"1 2\"\n```\n\nНа ключи из других наборов можно ссылаться, добавляя в качестве префикса необходимо значение `keysetName`.\n\n```json\n// global/en.json\n{\n  \"app\": \"App\"\n}\n\n// service/en.json\n{\n  \"app-service\": \"$t{global::app} service\"\n}\n```\n\n### Типизация\n\nДля типизации функции `i18nInstance.i18n` нужно выполнить несколько шагов.\n\n#### Подготовка\n\nСоздайте JSON-файл с набором ключей, чтобы процедура типизации могла получать данные. Добавьте создание дополнительного файла `data.json` в месте получения наборов ключей. Для уменьшения размера файла и ускорения парсинга в IDE замените все значения на `'str'`.\n\n```ts\nasync function createFiles(keysets: Record\u003cLang, LangKeysets\u003e) {\n    await mkdirp(DEST_PATH);\n\n    const createFilePromises = Object.keys(keysets).map((lang) =\u003e {\n        const keysetsJSON = JSON.stringify(keysets[lang as Lang], null, 4);\n        const content = umdTemplate(keysetsJSON);\n        const hash = getContentHash(content);\n        const filePath = path.resolve(DEST_PATH, `${lang}.${hash.slice(0, 8)}.js`);\n\n        // \u003cNew lines\u003e\n        let typesPromise;\n\n        if (lang === 'ru') {\n            const keyset = keysets[lang as Lang];\n            Object.keys(keyset).forEach((keysetName) =\u003e {\n                const keyPhrases = keyset[keysetName];\n                Object.keys(keyPhrases).forEach((keyName) =\u003e {\n                    // mutate object!\n                    keyPhrases[keyName] = 'str';\n                });\n            });\n\n            const JSONForTypes = JSON.stringify(keyset, null, 4);\n            typesPromise = writeFile(path.resolve(DEST_PATH, `data.json`), JSONForTypes, 'utf-8');\n        }\n        // \u003c/New lines\u003e\n\n        return Promise.all([typesPromise, writeFile(filePath, content, 'utf-8')]);\n    });\n\n    await Promise.all(createFilePromises);\n}\n```\n\n#### Подключение\n\nВ директории `ui/utils/i18n` (место настройки и экспорта `i18n` для дальнейшего использования всеми интерфейсами) импортируйте функцию типизации `I18NFn` с вашим `Keysets`. После настройки `i18n` верните функцию с заданным типом.\n\n```ts\nimport {I18NFn} from '@gravity-ui/i18n';\n// This must be a typed import!\nimport type Keysets from '../../../dist/public/build/i18n/data.json';\n\nconst i18nInstance = new I18N();\ntype TypedI18n = I18NFn\u003ctypeof Keysets\u003e;\n// ...\nexport const ci18n = (i18nInstance.i18n as TypedI18n).bind(i18nInstance, 'common');\nexport const cui18n = (i18nInstance.i18n as TypedI18n).bind(i18nInstance, 'common.units');\nexport const i18n = i18nInstance.i18n.bind(i18nInstance) as TypedI18n;\n```\n\n#### Дополнительные аспекты\n\n**Логика работы типизации**\n\nПримеры использования:\n\n* Вызов функции с передачей ключей литералами строк:\n\n```ts\ni18n('common', 'label_subnet'); // ok\ni18n('dcommon', 'label_dsubnet'); // error: Argument of type '\"dcommon\"' is not assignable to parameter of type ...\ni18n('common', 'label_dsubnet'); // error: Argument of type '\"label_dsubnet\"' is not assignable to parameter of type ...\n```\n\n* Вызов функции с передачей строк, которые нельзя вычислить в литералы (если `ts` не может распознать тип строки, он не выдает ошибку):\n\n```ts\nconst someUncomputebleString = `label_random-index-${Math.floor(Math.random() * 4)}`;\ni18n('some_service', someUncomputebleString); // ok\n\nfor (let i = 0; i \u003c 4; i++) {\n    i18n('some_service', `label_random-index-${i}`); // ok\n}\n```\n\n* Вызов функции с передачей строк, которые можно вычислить в литералы:\n\n```ts\nconst labelColors = ['red', 'green', 'yelllow', 'white'] as const;\nfor (let i = 0; i \u003c 4; i++) {\n    i18n('some_service', `label_color-${labelColors[i]}`); // ok\n}\n\nconst labelWrongColors = ['red', 'not-existing', 'yelllow', 'white'] as const;\nfor (let i = 0; i \u003c 4; i++) {\n    i18n('some_service', `label_color-${labelWrongColors[i]}`); // error: Argument of type '\"not-existing\"' is not assignable to parameter of type ...\n}\n```\n\n**Почему нет типизации через класс**\n\nДанная функция может поломать или усложнить некоторые сценарии использования i18n, поэтому была добавлена в качестве дополнительной функциональности. Если она хорошо себя проявит, то в будущем можно будет добавить ее в класс, чтобы не вызывать экспортируемые функции.\n\n**Почему могут не работать встроенные методы**\n\nТипизация встроенных методов функций достаточно сложна для реализации обхода вложенных структур и условных типов. Именно поэтому типизация работает только в случае использования непосредственного вызова функции и вызова `bind`до третьего аргумента.\n\n**Почему нельзя генерировать сразу файл `.ts`, чтобы типизация выполнялась и для значений ключей**\n\nЭто можно сделать, передав результирующий тип в I18NFn. Однако при больших объемах файла `ts` начинает есть столько ресурсов, что это сильно тормозит IDE, чего не происходит с JSON-файлом.\n\n**Почему не типизированы остальные методы класса I18N**\n\nВ принципе, их можно типизировать, и мы будем рады, если вы нам поможете это осуществить. Дело в том, что эти методы используются в 1% случаев.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgravity-ui%2Fi18n","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgravity-ui%2Fi18n","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgravity-ui%2Fi18n/lists"}