From f675d8ba013e6b7ddecf98d62ad7c4c62429f826 Mon Sep 17 00:00:00 2001
From: vvtimofeev <108340247+vvtimofeev@users.noreply.github.com>
Date: Tue, 24 Dec 2024 15:34:24 +0300
Subject: [PATCH] docs: added ru readme for landing (#59)
---
README-ru.md | 363 +++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 363 insertions(+)
create mode 100644 README-ru.md
diff --git a/README-ru.md b/README-ru.md
new file mode 100644
index 0000000..4eb5de6
--- /dev/null
+++ b/README-ru.md
@@ -0,0 +1,363 @@
+# @gravity-ui/i18n · [![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&label=CI&logo=github)](https://github.com/gravity-ui/i18n/actions/workflows/ci.yml?query=branch:main)
+
+## Утилиты I18N
+
+Утилиты пакета `I18N` разработаны для интернационализации компонентов Gravity UI.
+
+### Установка
+
+`npm install --save @gravity-ui/i18n`
+
+### API
+
+#### Конструктор (параметры)
+
+Принимает объект `options`, включающий необязательный параметр `logger` для логирования предупреждений библиотеки.
+
+##### Логгер
+
+Логгер должен содержать явно определенный метод `log` со следующей сигнатурой:
+
+* `message` — строка сообщения, которое будет записано в лог;
+* `options` — объект параметров логирования:
+ * `severity` — уровень логирования сообщения, всегда принимает значение `level`.
+ * `logger` — определяет место для записи сообщений библиотеки.
+ * `extra` — дополнительные параметры с единственным строковым полем `type`, которое всегда принимает значение `i18n`.
+
+### Примеры использования
+
+#### `keysets/en.json`
+
+```json
+{
+ "wizard": {
+ "label_error-widget-no-access": "No access to the chart"
+ }
+}
+```
+
+#### `keysets/ru.json`
+
+```json
+{
+ "wizard": {
+ "label_error-widget-no-access": "Нет доступа к чарту"
+ }
+}
+```
+
+#### `index.js`
+
+```js
+const ru = require('./keysets/ru.json');
+const en = require('./keysets/en.json');
+
+const {I18N} = require('@gravity-ui/i18n');
+
+const i18n = new I18N();
+i18n.registerKeysets('ru', ru);
+i18n.registerKeysets('en', en);
+
+i18n.setLang('ru');
+console.log(
+ i18n.i18n('wizard', 'label_error-widget-no-access')
+); // -> "Нет доступа к чарту"
+
+i18n.setLang('en');
+console.log(
+ i18n.i18n('wizard', 'label_error-widget-no-access')
+); // -> "No access to the chart
+
+// Keyset allows for a simpler translations retrieval
+const keyset = i18n.keyset('wizard');
+console.log(
+ keyset('label_error-widget-no-access')
+); // -> "No access to the chart"
+
+
+i18n.setLang('ru');
+console.log(
+ keyset('label_error-widget-no-access')
+); // -> "Нет доступа к чарту"
+
+// Checking if keyset has a key
+if (i18n.has('wizard', 'label_error-widget-no-access')) {
+ i18n.i18n('wizard', 'label_error-widget-no-access')
+}
+```
+
+### Шаблонизация
+
+Библиотека поддерживает шаблонизацию. Шаблонизируемые переменные заключаются в двойные фигурные скобки, а значения передаются в функцию i18n в форме словаря с парами «ключ-значение»:
+
+#### `keysets.json`
+
+```json
+{
+ "label_template": "No matches found for '{{inputValue}}' in '{{folderName}}'"
+}
+```
+
+#### `index.js`
+
+```js
+i18n('label_template', {inputValue: 'something', folderName: 'somewhere'}); // => No matches found for "something" in "somewhere"
+```
+
+### Плюрализация
+
+Для удобной локализации ключей, зависящих от числового значения, можно использовать плюрализацию. Текущая библиотека использует [правила плюрализации 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).
+
+Может потребоваться добавление [полифила](https://github.com/eemeli/intl-pluralrules) для [API `Intl.Plural Rules`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/PluralRules), если он недоступен в браузере.
+
+Существует 6 форм множественного числа (см. [`resolvedOptions`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/PluralRules/resolvedOptions)):
+
+* `zero` (также используется, когда `count = 0`, даже если форма не поддерживается в языке);
+* `one` (единственное число);
+* `two` (двойственное число);
+* `few` (паукальное число для обозначения нескольких предметов);
+* `many` (множественное; также используется для дробей, если у них есть отдельный класс);
+* `other` (общая форма множественного числа, обязательная для всех языков; также используется, если язык поддерживает только одну форму).
+
+#### Пример `keysets.json` с ключом для плюрализации
+
+```json
+{
+ "label_seconds": {
+ "one": "{{count}} second is left",
+ "other":"{{count}} seconds are left",
+ "zero": "No time left"
+ }
+}
+```
+
+#### Использование в JavaScript
+
+```js
+i18n('label_seconds', {count: 1}); // => 1 second
+i18n('label_seconds', {count: 3}); // => 3 seconds
+i18n('label_seconds', {count: 7}); // => 7 seconds
+i18n('label_seconds', {count: 10}); // => 10 seconds
+i18n('label_seconds', {count: 0}); // => No time left
+```
+
+#### Старый формат плюрализации (устаревший формат)
+
+Старый формат будет удален в версии 2.
+
+```json
+{
+ "label_seconds": ["{{count}} second is left", "{{count}} seconds are left", "{{count}} seconds are left", "No time left"]
+}
+```
+
+Ключ плюрализации содержит 4 значения, каждое из которых соответствует значению перечисления `PluralForm`.| Значения перечисления: `One`, `Few`, `Many` и `None` соответственно. Имя переменной шаблона плюрализации — `count`.
+
+#### Пользовательская плюрализация (устаревшее свойство)
+
+Так как у каждого языка свои правила плюрализации, библиотека предоставляет метод для настройки этих правил для любого выбранного языка.
+
+Функция конфигурации принимает объект с языками в качестве ключей и функциями плюрализации в качестве значений.
+
+Функция плюрализации принимает число и перечисление `PluralForm` и должна возвращать одно из значений перечисления в зависимости от переданного числа.
+
+```js
+const {I18N} = require('@gravity-ui/i18n');
+
+const i18n = new I18N();
+
+i18n.configurePluralization({
+ en: (count, pluralForms) => {
+ if (!count) return pluralForms.None;
+ if (count === 1) return pluralForms.One;
+ return pluralForms.Many;
+ },
+});
+```
+
+#### Предустановленные наборы правил плюрализации (устаревшие правила)
+
+Библиотека изначально поддерживает два языка: английский и русский.
+
+##### Английский
+
+Ключ языка — `en`.
+
+* `One` соответствует 1 и -1.
+* `Few` не используется.
+* `Many` соответствует любому числу, кроме 0.
+* `None` соответствует 0.
+
+##### Русский
+
+Ключ языка — `ru`.
+
+* `One` соответствует любому числу, оканчивающемуся на 1, кроме ±11.
+* `Few` соответствует любому числу, оканчивающемуся на 2, 3 или 4, кроме ±12, ±13 и ±14.
+* `Many` соответствует любому прочему числу, кроме 0.
+* `None` соответствует 0.
+
+##### Значение по умолчанию
+
+Если для языка не настроена функция плюрализации, используется набор правил для английского языка.
+
+### Вложенность
+
+
+
+
+
+
+
+Глубина вложенности ключей ограничена одним уровнем (для глоссария).
+
+
+Вложенность позволяет ссылаться на другие ключи в переводе, что удобно для формирования глоссариев.
+
+#### Базовый уровень
+
+Ключи
+
+```json
+{
+ "nesting1": "1 $t{nesting2}",
+ "nesting2": "2",
+}
+```
+
+Пример
+
+```ts
+i18n('nesting1'); // -> "1 2"
+```
+
+На ключи из других наборов можно ссылаться, добавляя в качестве префикса необходимо значение `keysetName`.
+
+```json
+// global/en.json
+{
+ "app": "App"
+}
+
+// service/en.json
+{
+ "app-service": "$t{global::app} service"
+}
+```
+
+### Типизация
+
+Для типизации функции `i18nInstance.i18n` нужно выполнить несколько шагов.
+
+#### Подготовка
+
+Создайте JSON-файл с набором ключей, чтобы процедура типизации могла получать данные. Добавьте создание дополнительного файла `data.json` в месте получения наборов ключей. Для уменьшения размера файла и ускорения парсинга в IDE замените все значения на `'str'`.
+
+```ts
+async function createFiles(keysets: Record) {
+ await mkdirp(DEST_PATH);
+
+ const createFilePromises = Object.keys(keysets).map((lang) => {
+ const keysetsJSON = JSON.stringify(keysets[lang as Lang], null, 4);
+ const content = umdTemplate(keysetsJSON);
+ const hash = getContentHash(content);
+ const filePath = path.resolve(DEST_PATH, `${lang}.${hash.slice(0, 8)}.js`);
+
+ //
+ let typesPromise;
+
+ if (lang === 'ru') {
+ const keyset = keysets[lang as Lang];
+ Object.keys(keyset).forEach((keysetName) => {
+ const keyPhrases = keyset[keysetName];
+ Object.keys(keyPhrases).forEach((keyName) => {
+ // mutate object!
+ keyPhrases[keyName] = 'str';
+ });
+ });
+
+ const JSONForTypes = JSON.stringify(keyset, null, 4);
+ typesPromise = writeFile(path.resolve(DEST_PATH, `data.json`), JSONForTypes, 'utf-8');
+ }
+ //
+
+ return Promise.all([typesPromise, writeFile(filePath, content, 'utf-8')]);
+ });
+
+ await Promise.all(createFilePromises);
+}
+```
+
+#### Подключение
+
+В директории `ui/utils/i18n` (место настройки и экспорта `i18n` для дальнейшего использования всеми интерфейсами) импортируйте функцию типизации `I18NFn` с вашим `Keysets`. После настройки `i18n` верните функцию с заданным типом.
+
+```ts
+import {I18NFn} from '@gravity-ui/i18n';
+// This must be a typed import!
+import type Keysets from '../../../dist/public/build/i18n/data.json';
+
+const i18nInstance = new I18N();
+type TypedI18n = I18NFn;
+// ...
+export const ci18n = (i18nInstance.i18n as TypedI18n).bind(i18nInstance, 'common');
+export const cui18n = (i18nInstance.i18n as TypedI18n).bind(i18nInstance, 'common.units');
+export const i18n = i18nInstance.i18n.bind(i18nInstance) as TypedI18n;
+```
+
+#### Дополнительные аспекты
+
+**Логика работы типизации**
+
+Примеры использования:
+
+* Вызов функции с передачей ключей литералами строк:
+
+```ts
+i18n('common', 'label_subnet'); // ok
+i18n('dcommon', 'label_dsubnet'); // error: Argument of type '"dcommon"' is not assignable to parameter of type ...
+i18n('common', 'label_dsubnet'); // error: Argument of type '"label_dsubnet"' is not assignable to parameter of type ...
+```
+
+* Вызов функции с передачей строк, которые нельзя вычислить в литералы (если `ts` не может распознать тип строки, он не выдает ошибку):
+
+```ts
+const someUncomputebleString = `label_random-index-${Math.floor(Math.random() * 4)}`;
+i18n('some_service', someUncomputebleString); // ok
+
+for (let i = 0; i < 4; i++) {
+ i18n('some_service', `label_random-index-${i}`); // ok
+}
+```
+
+* Вызов функции с передачей строк, которые можно вычислить в литералы:
+
+```ts
+const labelColors = ['red', 'green', 'yelllow', 'white'] as const;
+for (let i = 0; i < 4; i++) {
+ i18n('some_service', `label_color-${labelColors[i]}`); // ok
+}
+
+const labelWrongColors = ['red', 'not-existing', 'yelllow', 'white'] as const;
+for (let i = 0; i < 4; i++) {
+ i18n('some_service', `label_color-${labelWrongColors[i]}`); // error: Argument of type '"not-existing"' is not assignable to parameter of type ...
+}
+```
+
+**Почему нет типизации через класс**
+
+Данная функция может поломать или усложнить некоторые сценарии использования i18n, поэтому была добавлена в качестве дополнительной функциональности. Если она хорошо себя проявит, то в будущем можно будет добавить ее в класс, чтобы не вызывать экспортируемые функции.
+
+**Почему могут не работать встроенные методы**
+
+Типизация встроенных методов функций достаточно сложна для реализации обхода вложенных структур и условных типов. Именно поэтому типизация работает только в случае использования непосредственного вызова функции и вызова `bind`до третьего аргумента.
+
+**Почему нельзя генерировать сразу файл `.ts`, чтобы типизация выполнялась и для значений ключей**
+
+Это можно сделать, передав результирующий тип в I18NFn. Однако при больших объемах файла `ts` начинает есть столько ресурсов, что это сильно тормозит IDE, чего не происходит с JSON-файлом.
+
+**Почему не типизированы остальные методы класса I18N**
+
+В принципе, их можно типизировать, и мы будем рады, если вы нам поможете это осуществить. Дело в том, что эти методы используются в 1% случаев.