@mirta/i18n 0.0.1 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +24 -0
- package/README.md +224 -27
- package/README.ru.md +240 -0
- package/dist/index.d.mts +353 -0
- package/dist/index.mjs +815 -0
- package/package.json +51 -7
package/LICENSE
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
This is free and unencumbered software released into the public domain.
|
|
2
|
+
|
|
3
|
+
Anyone is free to copy, modify, publish, use, compile, sell, or
|
|
4
|
+
distribute this software, either in source code form or as a compiled
|
|
5
|
+
binary, for any purpose, commercial or non-commercial, and by any
|
|
6
|
+
means.
|
|
7
|
+
|
|
8
|
+
In jurisdictions that recognize copyright laws, the author or authors
|
|
9
|
+
of this software dedicate any and all copyright interest in the
|
|
10
|
+
software to the public domain. We make this dedication for the benefit
|
|
11
|
+
of the public at large and to the detriment of our heirs and
|
|
12
|
+
successors. We intend this dedication to be an overt act of
|
|
13
|
+
relinquishment in perpetuity of all present and future rights to this
|
|
14
|
+
software under copyright law.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
19
|
+
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
20
|
+
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
21
|
+
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
|
23
|
+
|
|
24
|
+
For more information, please refer to <https://unlicense.org>
|
package/README.md
CHANGED
|
@@ -1,45 +1,242 @@
|
|
|
1
1
|
# @mirta/i18n
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://github.com/wb-mirta/core/blob/latest/packages/mirta-i18n/README.md)
|
|
4
|
+
[](https://github.com/wb-mirta/core/blob/latest/packages/mirta-i18n/README.ru.md)
|
|
5
|
+
[](https://npmjs.com/package/@mirta/i18n)
|
|
6
|
+
[](https://npmjs.com/package/@mirta/i18n)
|
|
4
7
|
|
|
5
|
-
|
|
8
|
+
> Localization library for Mirta Framework CLI tools, with ICU-compatible syntax.
|
|
6
9
|
|
|
7
|
-
|
|
10
|
+
The `@mirta/i18n` package is intended exclusively for **Node.js tools** (≥ 20.6.0) and is not used in the Duktape runtime.
|
|
8
11
|
|
|
9
|
-
##
|
|
12
|
+
## Features
|
|
10
13
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
- ✔️ Type-safe keys and variables — when a `LocaleShape` is provided
|
|
15
|
+
- ✔️ ICU-compatible syntax: `{var}`, `{count, plural, ...}`, `offset`, `=n`, `#`
|
|
16
|
+
- ✔️ Asynchronous loading and caching of `.json` files
|
|
17
|
+
- ✔️ Zero dependencies — minimal footprint
|
|
18
|
+
- ✔️ Configurable fallback (en-US by default)
|
|
19
|
+
- ✔️ Unified translation contract `t(key, vars)`
|
|
15
20
|
|
|
16
|
-
##
|
|
21
|
+
## 📦 Installation
|
|
17
22
|
|
|
18
|
-
|
|
23
|
+
```sh
|
|
24
|
+
pnpm add @mirta/i18n
|
|
25
|
+
```
|
|
19
26
|
|
|
20
|
-
|
|
27
|
+
⚠️ This package is part of Mirta Framework's internal infrastructure. It is typically not used directly.
|
|
21
28
|
|
|
22
|
-
|
|
29
|
+
## Usage
|
|
23
30
|
|
|
24
|
-
1.
|
|
25
|
-
2. Configure the trusted publisher (e.g., GitHub Actions)
|
|
26
|
-
3. Specify the repository and workflow that should be allowed to publish
|
|
27
|
-
4. Use the configured workflow to publish your actual package
|
|
31
|
+
### 1. Organize the locale structure
|
|
28
32
|
|
|
29
|
-
|
|
33
|
+
Set up the localization structure for your package:
|
|
30
34
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
-
|
|
35
|
-
-
|
|
35
|
+
```txt
|
|
36
|
+
<package>/
|
|
37
|
+
locales/
|
|
38
|
+
en-US.json
|
|
39
|
+
ru-RU.json
|
|
40
|
+
```
|
|
36
41
|
|
|
37
|
-
|
|
42
|
+
Example `en-US.json`:
|
|
38
43
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
44
|
+
```json
|
|
45
|
+
{
|
|
46
|
+
"title": "Welcome",
|
|
47
|
+
"files.plural": "{count, plural, =0{No files} one{One file} other{# files}}",
|
|
48
|
+
"greeting": "Hello, {name}!"
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### 2. Define the `LocaleShape`
|
|
53
|
+
|
|
54
|
+
To enable type safety, define an interface compatible with `GenericShape`:
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
interface LocaleShape {
|
|
58
|
+
messages: {
|
|
59
|
+
'title': string;
|
|
60
|
+
'files.plural': string;
|
|
61
|
+
'greeting': string;
|
|
62
|
+
};
|
|
63
|
+
variables: {
|
|
64
|
+
'files.plural': { count: number };
|
|
65
|
+
'greeting': { name: string };
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
💡 The `LocaleShape` type must be defined in the project.
|
|
71
|
+
Mirta Framework uses an internal script to generate it from `locales/en-US.json`.
|
|
72
|
+
|
|
73
|
+
⚠️ If no locale shape is provided, `GenericShape` is used — keys and variables are not type-checked.
|
|
74
|
+
|
|
75
|
+
### 3. Initialize localization
|
|
76
|
+
|
|
77
|
+
In the package to be localized:
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
// src/i18n/index.ts
|
|
81
|
+
import { initLocalizationAsync } from '@mirta/i18n'
|
|
82
|
+
|
|
83
|
+
export const { t, setLocaleAsync } = await initLocalizationAsync<LocaleShape>()
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### 4. Use translation
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
console.log(t('title')) // → "Welcome"
|
|
90
|
+
console.log(t('greeting', { name: 'Alice' })) // → "Hello, Alice!"
|
|
91
|
+
console.log(t('files.plural', { count: 5 })) // → "5 files"
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Changing locale:
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
await setLocaleAsync('ru-RU')
|
|
98
|
+
console.log(t('title')) // → "Добро пожаловать"
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## 📚 API
|
|
102
|
+
|
|
103
|
+
### `initLocalizationAsync<TShape>(options)`
|
|
104
|
+
|
|
105
|
+
Initializes the localization subsystem.
|
|
106
|
+
|
|
107
|
+
#### Parameters
|
|
108
|
+
|
|
109
|
+
| Field | Type | Description |
|
|
110
|
+
|------|-----|----------|
|
|
111
|
+
| `cwd` | `string` | Working directory (default: `process.cwd()`) |
|
|
112
|
+
| `fallbackLocale` | `string` | Fallback locale (default: `'en-US'`) |
|
|
113
|
+
|
|
114
|
+
#### Returns
|
|
115
|
+
|
|
116
|
+
`Promise<Localization<TShape>>`
|
|
117
|
+
|
|
118
|
+
#### Errors
|
|
119
|
+
|
|
120
|
+
- `fallback.LoadFailed` — if the fallback locale cannot be loaded.
|
|
42
121
|
|
|
43
122
|
---
|
|
44
123
|
|
|
45
|
-
|
|
124
|
+
### `Localization<TShape>`
|
|
125
|
+
|
|
126
|
+
| Method | Type | Description |
|
|
127
|
+
|------|-----|----------|
|
|
128
|
+
| `t(key, vars?)` | `(key: K, vars?: VariablesOf<TShape, K>) => string` | Type-safe translation function |
|
|
129
|
+
| `getLocale()` | `() => Locale` | Returns current locale |
|
|
130
|
+
| `setLocaleAsync(locale)` | `(locale: string) => Promise<void>` | Changes locale (normalizes and caches) |
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
### `Locale`
|
|
135
|
+
|
|
136
|
+
Type: `Branded<string, 'Locale'>` — branded locale type.
|
|
137
|
+
|
|
138
|
+
### `Lang`
|
|
139
|
+
|
|
140
|
+
Type: `Branded<string, 'Lang'>` — branded language type.
|
|
141
|
+
|
|
142
|
+
## Key Features
|
|
143
|
+
|
|
144
|
+
### ✅ Optional type safety
|
|
145
|
+
|
|
146
|
+
The `t()` function ensures type safety **only when `LocaleShape` is provided**:
|
|
147
|
+
|
|
148
|
+
- Validates key existence
|
|
149
|
+
- Enforces required variables
|
|
150
|
+
- Prevents extra fields
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
t('files.plural', { count: 2 }) // ✅
|
|
154
|
+
t('files.plural', {}) // ❌ Error: missing `count`
|
|
155
|
+
t('title', { name: 'John' }) // ❌ Error: `title` does not accept variables
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### ✅ ICU-compatible syntax
|
|
159
|
+
|
|
160
|
+
Supports a **limited subset of ICU MessageFormat**, sufficient for CLI:
|
|
161
|
+
|
|
162
|
+
- Interpolation: `{name}`, `{user.name}`, `{file-count}` (allowed: `a-z`, `A-Z`, `0-9`, `_`, `.`, `-`)
|
|
163
|
+
- Plural: `{count, plural, one{...} few{...} other{...}}`
|
|
164
|
+
- Offset: `offset:1`, `=0`, `#`
|
|
165
|
+
|
|
166
|
+
⚠️ Not supported:
|
|
167
|
+
- `select`, `selectordinal`, number/date formatting
|
|
168
|
+
- Variables with spaces: `{first name}` → not replaced
|
|
169
|
+
- Nested `plural` or `#` inside `=n`
|
|
170
|
+
|
|
171
|
+
Implemented without external dependencies — only essentials.
|
|
172
|
+
|
|
173
|
+
#### Example with `offset`
|
|
174
|
+
|
|
175
|
+
```json
|
|
176
|
+
"sockets.active": "{count, plural,
|
|
177
|
+
offset:1
|
|
178
|
+
=0 {Only server is on}
|
|
179
|
+
one {One more socket connected}
|
|
180
|
+
other {# more sockets connected}
|
|
181
|
+
}"
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
- `count = 1` → `# = 0` → "Only server is on"
|
|
185
|
+
- `count = 2` → `# = 1` → "One more socket connected"
|
|
186
|
+
- `count = 5` → `# = 4` → "4 more sockets connected"
|
|
187
|
+
|
|
188
|
+
Allows excluding persistent elements from the count.
|
|
189
|
+
|
|
190
|
+
### ✅ Asynchronous initialization and caching
|
|
191
|
+
|
|
192
|
+
- `initLocalizationAsync` loads and caches both fallback and system locale.
|
|
193
|
+
- `setLocaleAsync` caches loaded locales — no redundant reloads.
|
|
194
|
+
- Fallback chain: `current → fallback → {{key}}` if translation is missing.
|
|
195
|
+
|
|
196
|
+
### ✅ Locale normalization
|
|
197
|
+
|
|
198
|
+
The `setLocaleAsync` function accepts any string, but:
|
|
199
|
+
- Automatically normalizes format (e.g. `ru_RU` → `ru-RU`)
|
|
200
|
+
- Falls back to `fallbackLocale` on invalid input
|
|
201
|
+
- Supports only `en-US` and `ru-RU`
|
|
202
|
+
|
|
203
|
+
No manual locale validation required.
|
|
204
|
+
|
|
205
|
+
### ✅ Language support
|
|
206
|
+
|
|
207
|
+
- `ru`: full support for `one` / `few` / `many` (per [CLDR](https://cldr.unicode.org/))
|
|
208
|
+
- `en` and others: `one` (if 1), otherwise `other`
|
|
209
|
+
|
|
210
|
+
> For languages with special plural forms (e.g. `pl`, `ar`), extend `getPluralForm`.
|
|
211
|
+
|
|
212
|
+
#### Language-specific behavior
|
|
213
|
+
|
|
214
|
+
For Russian (`ru-RU`), fractional numbers (e.g. `36.6`) always use the `few` plural form (e.g. `36.6 градуса`), regardless of the integer part.
|
|
215
|
+
|
|
216
|
+
This follows Russian grammatical rules: in mixed numbers, the fractional part governs the noun, requiring the genitive singular case (e.g. _"одна целая пять десятых градуса"_).
|
|
217
|
+
|
|
218
|
+
Since ICU does not define a dedicated plural category for fractional numbers, `few` is used as the closest available match.
|
|
219
|
+
|
|
220
|
+
## When to use?
|
|
221
|
+
|
|
222
|
+
Use `@mirta/i18n` if:
|
|
223
|
+
- Your tool supports multiple languages,
|
|
224
|
+
- You need accurate plural forms (especially for Russian),
|
|
225
|
+
- Locales are stored in `.json` and loaded asynchronously,
|
|
226
|
+
- Small bundle size and zero dependencies are important.
|
|
227
|
+
|
|
228
|
+
## Limitations
|
|
229
|
+
|
|
230
|
+
- The `LocaleShape` type must be declared before use.
|
|
231
|
+
- Localization instances are cached — avoid creating thousands of dynamic locales.
|
|
232
|
+
- No support for `select`, `selectordinal`, number/date formatting.
|
|
233
|
+
- Variables with spaces (`{first name}`) are not replaced.
|
|
234
|
+
- `#` respects `offset` but does not support formatting.
|
|
235
|
+
|
|
236
|
+
## Testing
|
|
237
|
+
|
|
238
|
+
The package is covered with unit tests (`vitest`, `@mirta/testing`) verifying:
|
|
239
|
+
- Initialization
|
|
240
|
+
- Translation with variables and plural forms
|
|
241
|
+
- Locale switching
|
|
242
|
+
- Fallback logic
|
package/README.ru.md
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
# @mirta/i18n
|
|
2
|
+
|
|
3
|
+
[](https://github.com/wb-mirta/core/blob/latest/packages/mirta-i18n/README.md)
|
|
4
|
+
[](https://github.com/wb-mirta/core/blob/latest/packages/mirta-i18n/README.ru.md)
|
|
5
|
+
[](https://npmjs.com/package/@mirta/i18n)
|
|
6
|
+
[](https://npmjs.com/package/@mirta/i18n)
|
|
7
|
+
|
|
8
|
+
> Библиотека локализации для CLI-инструментов Mirta Framework, с ICU-совместимым синтаксисом.
|
|
9
|
+
|
|
10
|
+
Пакет `@mirta/i18n` предназначен исключительно для **Node.js-инструментов** (≥ 20.6.0), не используется в рантайме Duktape.
|
|
11
|
+
|
|
12
|
+
## Особенности
|
|
13
|
+
|
|
14
|
+
- ✔️ Типобезопасность ключей и переменных — при определении `LocaleShape`
|
|
15
|
+
- ✔️ ICU-совместимый синтаксис: `{var}`, `{count, plural, ...}`, `offset`, `=n`, `#`
|
|
16
|
+
- ✔️ Асинхронная загрузка и кэширование `.json`
|
|
17
|
+
- ✔️ Zero dependencies — только необходимый минимум
|
|
18
|
+
- ✔️ Настраиваемый fallback (en-US по умолчанию)
|
|
19
|
+
- ✔️ Контракт `t(key, vars)` — единый интерфейс перевода
|
|
20
|
+
|
|
21
|
+
## 📦 Установка
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
pnpm add @mirta/i18n
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
⚠️ Этот пакет — часть внутренней инфраструктуры фреймворка Mirta. Обычно не используется напрямую.
|
|
28
|
+
|
|
29
|
+
## Использование
|
|
30
|
+
|
|
31
|
+
### 1. Подготовьте файлы локалей
|
|
32
|
+
|
|
33
|
+
Организуйте папку `locales` следующим образом:
|
|
34
|
+
|
|
35
|
+
```txt
|
|
36
|
+
<пакет>/
|
|
37
|
+
locales/
|
|
38
|
+
en-US.json
|
|
39
|
+
ru-RU.json
|
|
40
|
+
```
|
|
41
|
+
Пример `en-US.json`:
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
{
|
|
45
|
+
"title": "Welcome",
|
|
46
|
+
"files.plural": "{count, plural, =0{No files} one{One file} other{# files}}",
|
|
47
|
+
"greeting": "Hello, {name}!"
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### 2. Опишите LocaleShape
|
|
52
|
+
|
|
53
|
+
Для включения типобезопасности определите интерфейс, совместимый с `GenericShape`:
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
interface LocaleShape {
|
|
57
|
+
messages: {
|
|
58
|
+
'title': string;
|
|
59
|
+
'files.plural': string;
|
|
60
|
+
'greeting': string;
|
|
61
|
+
};
|
|
62
|
+
variables: {
|
|
63
|
+
'files.plural': { count: number };
|
|
64
|
+
'greeting': { name: string };
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
💡 Тип `LocaleShape` должен быть определён в проекте.
|
|
69
|
+
Mirta Framework использует внутренний скрипт генерации на основе `locales/en-US.json`.
|
|
70
|
+
|
|
71
|
+
⚠️ При отсутствии `LocaleShape` используется `GenericShape` — типы ключей и переменных не проверяются.
|
|
72
|
+
|
|
73
|
+
### 3. Инициализируйте локализацию
|
|
74
|
+
|
|
75
|
+
В пакете, который подлежит локализации:
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
// src/i18n/index.ts
|
|
79
|
+
import { initLocalizationAsync } from '@mirta/i18n'
|
|
80
|
+
|
|
81
|
+
export const { t, setLocaleAsync } = await initLocalizationAsync<LocaleShape>()
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### 4. Используйте перевод
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
console.log(t('title')) // → "Welcome"
|
|
88
|
+
console.log(t('greeting', { name: 'Alice' })) // → "Hello, Alice!"
|
|
89
|
+
console.log(t('files.plural', { count: 5 })) // → "5 files"
|
|
90
|
+
```
|
|
91
|
+
Смена локали:
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
await setLocaleAsync('ru-RU')
|
|
95
|
+
console.log(t('title')) // → "Добро пожаловать"
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## 📚 API
|
|
99
|
+
|
|
100
|
+
### `initLocalizationAsync<TShape>(options)`
|
|
101
|
+
|
|
102
|
+
Подготавливает подсистему локализации - загружает fallback-локаль, осуществляет попытку определения и установки системной локали.
|
|
103
|
+
|
|
104
|
+
#### Параметры
|
|
105
|
+
|
|
106
|
+
| Поле | Тип | Описание |
|
|
107
|
+
|------|-----|----------|
|
|
108
|
+
| `cwd` | `string` | Рабочая директория (по умолчанию: `process.cwd()`) |
|
|
109
|
+
| `fallbackLocale` | `string` | Резервная локаль (по умолчанию: `'en-US'`) |
|
|
110
|
+
|
|
111
|
+
#### Возвращает
|
|
112
|
+
|
|
113
|
+
`Promise<Localization<TShape>>`
|
|
114
|
+
|
|
115
|
+
#### Ошибки
|
|
116
|
+
|
|
117
|
+
- `fallbackLoadFailed` — если не удалось загрузить fallback-локаль.
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
### `Localization<TShape>`
|
|
122
|
+
|
|
123
|
+
| Метод | Тип | Описание |
|
|
124
|
+
|------|-----|----------|
|
|
125
|
+
| `t(key, vars?)` | `(key: K, vars?: VariablesOf<TShape, K>) => string` | Типобезопасный перевод |
|
|
126
|
+
| `getLocale()` | `() => Locale` | Текущая локаль |
|
|
127
|
+
| `setLocaleAsync(locale)` | `(locale: string) => Promise<void>` | Смена локали (нормализует и кэширует) |
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
### `Locale`
|
|
132
|
+
|
|
133
|
+
Тип: `Branded<string, 'Locale'>` - брендированный тип локали.
|
|
134
|
+
|
|
135
|
+
### `Lang`
|
|
136
|
+
|
|
137
|
+
Тип: `Branded<string, 'Lang'>` - брендированный тип языка.
|
|
138
|
+
|
|
139
|
+
## Ключевые возможности
|
|
140
|
+
|
|
141
|
+
### ✅ Опциональная типобезопасность
|
|
142
|
+
|
|
143
|
+
Функция `t()` обеспечивает типобезопасность **только при указании `LocaleShape`**:
|
|
144
|
+
|
|
145
|
+
- Проверка существования ключей
|
|
146
|
+
- Требование обязательных переменных
|
|
147
|
+
- Запрет лишних полей
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
t('files.plural', { count: 2 }) // ✅
|
|
151
|
+
t('files.plural', {}) // ❌ Ошибка: нет `count`
|
|
152
|
+
t('title', { name: 'John' }) // ❌ Ошибка: `title` не принимает переменные
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### ✅ ICU-совместимый синтаксис
|
|
156
|
+
|
|
157
|
+
Поддерживает **ограниченный набор ICU MessageFormat**, достаточный для CLI:
|
|
158
|
+
|
|
159
|
+
- Интерполяция: `{name}`, `{user.name}`, `{file-count}` (разрешены: `a-z`, `A-Z`, `0-9`, `_`, `.`, `-`)
|
|
160
|
+
- Plural: `{count, plural, one{...} few{...} other{...}}`
|
|
161
|
+
- Offset: `offset:1`, `=0`, `#`
|
|
162
|
+
|
|
163
|
+
⚠️ Не поддерживает:
|
|
164
|
+
- `select`, `selectordinal`, форматирование чисел/дат
|
|
165
|
+
- Переменные с пробелами: `{first name}` → не заменяется
|
|
166
|
+
- Вложенные `plural` или `#` внутри `=n`
|
|
167
|
+
|
|
168
|
+
Реализовано без внешних зависимостей — только необходимый минимум.
|
|
169
|
+
|
|
170
|
+
#### Пример с `offset`
|
|
171
|
+
|
|
172
|
+
```json
|
|
173
|
+
"sockets.active": "{count, plural,
|
|
174
|
+
offset:1
|
|
175
|
+
=0 {Только сервер включён}
|
|
176
|
+
one {Подключено ещё одно устройство}
|
|
177
|
+
other {Подключены ещё # устройства}
|
|
178
|
+
}"
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
- `count = 1` → `# = 0` → "Только сервер включён"
|
|
182
|
+
- `count = 2` → `# = 1` → "Подключено ещё одно устройство"
|
|
183
|
+
- `count = 5` → `# = 4` → "Подключены ещё 4 устройства"
|
|
184
|
+
|
|
185
|
+
Позволяет исключить постоянные элементы из счётчика.
|
|
186
|
+
|
|
187
|
+
### ✅ Асинхронная инициализация и кэширование
|
|
188
|
+
|
|
189
|
+
- `initLocalizationAsync` загружает и кэширует `fallbackLocale` и системную локаль.
|
|
190
|
+
- `setLocaleAsync` кэширует уже загруженные локали — повторная загрузка не выполняется.
|
|
191
|
+
- Fallback при отсутствии перевода: `current → fallback → {{key}}`.
|
|
192
|
+
|
|
193
|
+
### ✅ Нормализация локалей
|
|
194
|
+
|
|
195
|
+
Функция `setLocaleAsync` принимает любую строку, но:
|
|
196
|
+
- Автоматически нормализует формат (например, `ru_RU` → `ru-RU`)
|
|
197
|
+
- При некорректном значении — использует `fallbackLocale`
|
|
198
|
+
- Поддерживает `en-US` и `ru-RU`
|
|
199
|
+
|
|
200
|
+
Нет необходимости вручную валидировать локаль.
|
|
201
|
+
|
|
202
|
+
### ✅ Поддержка языков
|
|
203
|
+
|
|
204
|
+
- `ru`: полная поддержка `one` / `few` / `many` (по [CLDR](https://cldr.unicode.org/))
|
|
205
|
+
- `en` и другие: `one` (если 1), иначе `other`
|
|
206
|
+
|
|
207
|
+
> Для языков с особыми формами множественного числа (например, `pl`, `ar`) требуется расширение `getPluralForm`.
|
|
208
|
+
|
|
209
|
+
#### Языковые особенности
|
|
210
|
+
|
|
211
|
+
В русском языке (`ru-RU`) дробные числа всегда используют форму `few` (например, `36.6 градуса`), независимо от целой части.
|
|
212
|
+
|
|
213
|
+
> При смешанном числе существительным управляет дробь, а не целое число.<br/>
|
|
214
|
+
> — Д. Э. Розенталь. *Справочник по правописанию и литературной правке*, § 164, п. 8
|
|
215
|
+
|
|
216
|
+
Форма `few` используется как ближайшая доступная в ICU, поскольку стандарт не предусматривает отдельной категории для дробных числительных.
|
|
217
|
+
|
|
218
|
+
## Когда использовать?
|
|
219
|
+
|
|
220
|
+
Используйте `@mirta/i18n`, если:
|
|
221
|
+
- Инструмент требует поддержки нескольких языков,
|
|
222
|
+
- Нужны точные формы множественного числа (особенно для русского),
|
|
223
|
+
- Локали хранятся в `.json` и загружаются асинхронно,
|
|
224
|
+
- Важны малый размер и отсутствие внешних зависимостей.
|
|
225
|
+
|
|
226
|
+
## Ограничения
|
|
227
|
+
|
|
228
|
+
- Тип `LocaleShape` должен быть объявлен до использования.
|
|
229
|
+
- Наборы локалей кэшируются — избегайте создания тысяч динамических локалей.
|
|
230
|
+
- Нет поддержки `select`, `selectordinal`, форматирования чисел и дат.
|
|
231
|
+
- Переменные с пробелами (`{first name}`) — не заменяются.
|
|
232
|
+
- Подстановка `#` учитывает `offset`, но не поддерживает форматирование.
|
|
233
|
+
|
|
234
|
+
## Тестирование
|
|
235
|
+
|
|
236
|
+
Пакет покрыт модульными тестами (`vitest`, `@mirta/testing`), проверяющими:
|
|
237
|
+
- Инициализацию,
|
|
238
|
+
- Перевод с переменными и plural,
|
|
239
|
+
- Смену локали,
|
|
240
|
+
- Fallback-логику.
|