Skip to content
Open
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
1 change: 1 addition & 0 deletions .github/workflows/api.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ jobs:
- uses: ./.github/workflows/actions/setup-node
- uses: ./.github/workflows/actions/build-packages
- run: yarn run check-api
- run: yarn run check-public-api-any
12 changes: 12 additions & 0 deletions apps/docs/src/app/structure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export enum DocsStructureItemId {
Installation = 'installation',
Versioning = 'versioning',
Theming = 'theming',
Localization = 'localization',
Typography = 'typography',
DesignTokens = 'design-tokens',
Schematics = 'schematics',
Expand Down Expand Up @@ -225,6 +226,17 @@ const structure: DocsStructure = makeStructure({
hasApi: false,
hasExamples: false
},
{
id: DocsStructureItemId.Localization,
name: {
ru: 'Локализация',
en: 'Localization'
},
svgPreview: '',
isGuide: true,
hasApi: false,
hasExamples: false
},
{
id: DocsStructureItemId.Typography,
name: {
Expand Down
123 changes: 123 additions & 0 deletions docs/guides/localization.en.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
## Localization

Koobiq components render some strings of their own — the filters menu of the filter bar, the tooltips of
the code block, the accessible names of every icon-only button, the placeholder of a datepicker, and so on.
All of them come from one place: `KbqLocaleService`.

Your own data is never translated. Option labels, filter values, table cells and everything else you pass
in stays exactly as you wrote it.

The library ships five locales: `en-US`, `es-LA`, `pt-BR`, `ru-RU` and `tk-TM`.

### Setting the locale

`KbqLocaleService` is `providedIn: 'root'`, but the components read it through the `KBQ_LOCALE_SERVICE`
token, which has no factory. Nothing is localized until you provide it:

```ts
import { KBQ_LOCALE_SERVICE, KbqLocaleService } from '@koobiq/components/core';

bootstrapApplication(AppComponent, {
providers: [{ provide: KBQ_LOCALE_SERVICE, useClass: KbqLocaleService }]
});
```

Without that provider every component falls back to its own built-in `ru-RU` defaults, and switching the
locale at runtime does nothing.

There are three ways to control which locale is active:

- **`KBQ_DEFAULT_LOCALE_ID`** is the fallback, `ru-RU`. It is a plain exported constant, not an injection
token — it cannot be provided, only read.
- **`KBQ_LOCALE_ID`** fixes the locale once, when `KbqLocaleService` is constructed. It must sit in the
**same `providers` array** as the service itself, because the service reads the token from the injector
that created it.
- **`setLocale(id)`** changes the locale at runtime.

```ts
providers: [
{ provide: KBQ_LOCALE_ID, useValue: 'en-US' },
{ provide: KBQ_LOCALE_SERVICE, useClass: KbqLocaleService }
];
```

Reading the active locale:

```ts
readonly localeService = inject(KBQ_LOCALE_SERVICE);

readonly currentLocale = this.localeService.localeId; // Signal<KbqLocaleIdLike>
readonly localeData = this.localeService.data; // Signal<KbqLocaleData>
readonly available = this.localeService.items; // Signal<KbqLocaleItem[]>, for a locale picker
```

`changes` (a `BehaviorSubject`), `id` and `current` still work and stay in sync. Prefer the signals in new
code: a signal read from a template registers on the reading view, so a runtime `setLocale()` reaches
`OnPush` children that an observable subscribed in the parent could not.

### Overriding the strings of one component

Every localized component exposes a configuration token and a matching provider. Only the keys you pass are
overridden — everything else keeps its default:

```ts
import { kbqCodeBlockLocaleConfigurationProvider } from '@koobiq/components/code-block';

providers: [kbqCodeBlockLocaleConfigurationProvider({ copyTooltip: 'Copy the snippet' })];
```

Because these providers are element-injector friendly, providing one on a component scopes the override to
that component's subtree.

Each helper ships from its own component's package. The exception is `kbqSelectLocaleConfigurationProvider`,
which ships from `@koobiq/components/core`: the `select` section is rendered by three packages that do not
depend on one another — `kbq-select`, `kbq-tree-select` and `kbq-tree-selection`.

An override is applied on top of whatever is active — the locale service when the application provides one,
the token's defaults otherwise. So the keys you pass stay pinned across a runtime `setLocale()`, while every
key you did not pass follows the locale. Override a whole section if you want it to stop following the
locale entirely; register your own locale (see below) if you want the override to switch along with the
others.

### Registering your own locale

`addLocale()` accepts partial data — every section, and every key within a section, is optional. Whatever
you leave out is completed from the shipped locale of the same id, or from `KBQ_DEFAULT_LOCALE_ID` when the
id is new. `getParams()` therefore always returns a complete section, whatever you registered:

```ts
localeService.addLocale('en-GB', {
select: { selectAll: 'Select everything' },
a11y: { close: 'Dismiss' }
});
```

The same shape can be provided up front through `KBQ_LOCALE_DATA`:

```ts
{ provide: KBQ_LOCALE_DATA, useValue: { 'en-GB': { select: { selectAll: 'Select everything' } } } }
```

`KbqLocaleData` is the full contract, so a misspelled section or key is a compile error rather than a
string that silently never appears.

### Reading a section yourself

```ts
const { selectAll } = localeService.getParams('select'); // KbqSelectLocaleConfiguration
const select = localeService.params('select'); // Signal<KbqSelectLocaleConfiguration>
```

The section name is checked against `KbqLocaleSection`, and the return type follows from it.

### Dates and numbers

Date adapters and the number pipes follow the same service, but they need their own providers. Note that
`KbqLocaleServiceModule` — pulled in by the date adapter modules — registers `KBQ_LOCALE_SERVICE` with
`useClass`, which builds a **second instance**, independent of the `providedIn: 'root'` one. If you switch
the locale on one and read it on the other, nothing happens. Always inject the `KBQ_LOCALE_SERVICE` token,
never the `KbqLocaleService` class.

To scope a locale to a subtree that contains dates, provide the adapter and formatter in that same
`providers` array — `imports: [KbqLuxonDateModule]` puts them in the environment injector, where they
resolve the root locale service and render month names in the wrong language.
124 changes: 124 additions & 0 deletions docs/guides/localization.ru.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
## Локализация

Компоненты Koobiq выводят собственные строки — меню фильтров в filter bar, подсказки в code block,
доступные имена всех кнопок-иконок, плейсхолдер поля даты и так далее. Все они приходят из одного места:
`KbqLocaleService`.

Ваши данные не переводятся. Названия опций, значения фильтров, ячейки таблиц и всё остальное, что вы
передаёте в компонент, остаётся ровно таким, как вы его написали.

Доступные идентификаторы локали: `en-US`, `es-LA`, `pt-BR`, `ru-RU` и `tk-TM`.

### Подключение локали

`KbqLocaleService` объявлен как `providedIn: 'root'`, но компоненты читают его через токен
`KBQ_LOCALE_SERVICE`, у которого нет фабрики. Пока вы не предоставите его, локализация не работает:

```ts
import { KBQ_LOCALE_SERVICE, KbqLocaleService } from '@koobiq/components/core';

bootstrapApplication(AppComponent, {
providers: [{ provide: KBQ_LOCALE_SERVICE, useClass: KbqLocaleService }]
});
```

Без этого провайдера каждый компонент использует собственные значения по умолчанию (`ru-RU`), а смена
локали во время работы приложения ничего не меняет.

Управлять активной локалью можно тремя способами:

- **`KBQ_DEFAULT_LOCALE_ID`** — значение по умолчанию, `ru-RU`. Это обычная экспортируемая константа, а не
injection token: её нельзя предоставить, только прочитать.
- **`KBQ_LOCALE_ID`** фиксирует локаль один раз, в момент создания `KbqLocaleService`. Токен должен лежать
в **том же массиве `providers`**, что и сам сервис, потому что сервис читает его из создавшего инжектора.
- **`setLocale(id)`** меняет локаль во время работы приложения.

```ts
providers: [
{ provide: KBQ_LOCALE_ID, useValue: 'en-US' },
{ provide: KBQ_LOCALE_SERVICE, useClass: KbqLocaleService }
];
```

Чтение активной локали:

```ts
readonly localeService = inject(KBQ_LOCALE_SERVICE);

readonly currentLocale = this.localeService.localeId; // Signal<KbqLocaleIdLike>
readonly localeData = this.localeService.data; // Signal<KbqLocaleData>
readonly available = this.localeService.items; // Signal<KbqLocaleItem[]>, для выбора локали
```

`changes` (`BehaviorSubject`), `id` и `current` продолжают работать и синхронизированы с сигналами. В новом
коде используйте сигналы: чтение сигнала в шаблоне регистрируется на читающем представлении, поэтому
`setLocale()` во время работы приложения доходит до `OnPush`-потомков, чего подписка в родителе сделать
не может.

### Переопределение строк одного компонента

У каждого локализованного компонента есть токен конфигурации и соответствующий провайдер. Переопределяются
только переданные ключи — остальные сохраняют значения по умолчанию:

```ts
import { kbqCodeBlockLocaleConfigurationProvider } from '@koobiq/components/code-block';

providers: [kbqCodeBlockLocaleConfigurationProvider({ copyTooltip: 'Скопировать фрагмент' })];
```

Эти провайдеры работают и в element injector, поэтому провайдер на компоненте ограничивает переопределение
его поддеревом.

Каждая такая функция поставляется из пакета своего компонента. Исключение —
`kbqSelectLocaleConfigurationProvider`, который живёт в `@koobiq/components/core`: секцию `select` выводят
три независимых друг от друга пакета — `kbq-select`, `kbq-tree-select` и `kbq-tree-selection`.

Переопределение накладывается поверх того, что активно: поверх сервиса локали, если приложение его
предоставляет, иначе поверх значений по умолчанию из токена. Поэтому переданные вами ключи остаются
закреплёнными при вызове `setLocale()` во время работы, а все остальные следуют за локалью. Переопределите
секцию целиком, если она вообще не должна следовать за локалью, либо зарегистрируйте собственную локаль
(см. ниже), если переопределение должно переключаться вместе с остальными строками.

### Регистрация собственной локали

`addLocale()` принимает частичные данные — каждая секция и каждый ключ внутри секции необязательны. Всё,
что вы не указали, дополняется из поставляемой локали с тем же идентификатором, а для нового
идентификатора — из `KBQ_DEFAULT_LOCALE_ID`. Поэтому `getParams()` всегда возвращает полную секцию,
что бы вы ни зарегистрировали:

```ts
localeService.addLocale('en-GB', {
select: { selectAll: 'Select everything' },
a11y: { close: 'Dismiss' }
});
```

Те же данные можно передать заранее через `KBQ_LOCALE_DATA`:

```ts
{ provide: KBQ_LOCALE_DATA, useValue: { 'en-GB': { select: { selectAll: 'Select everything' } } } }
```

Полный контракт описан типом `KbqLocaleData`, поэтому опечатка в названии секции или ключа — это ошибка
компиляции, а не строка, которая молча никогда не появится.

### Чтение секции напрямую

```ts
const { selectAll } = localeService.getParams('select'); // KbqSelectLocaleConfiguration
const select = localeService.params('select'); // Signal<KbqSelectLocaleConfiguration>
```

Название секции проверяется по `KbqLocaleSection`, а тип результата выводится из него.

### Даты и числа

Адаптеры дат и числовые пайпы используют тот же сервис, но им нужны собственные провайдеры. Учтите, что
`KbqLocaleServiceModule` — его подключают модули адаптеров дат — регистрирует `KBQ_LOCALE_SERVICE` через
`useClass`, а значит создаёт **второй экземпляр**, независимый от `providedIn: 'root'`. Если менять локаль
на одном, а читать с другого, ничего не произойдёт. Всегда инжектируйте токен `KBQ_LOCALE_SERVICE`,
а не класс `KbqLocaleService`.

Чтобы ограничить локаль поддеревом, в котором есть даты, объявите адаптер и форматтер в том же массиве
`providers`: `imports: [KbqLuxonDateModule]` помещает их в environment injector, где они получат корневой
сервис локали и выведут названия месяцев на другом языке.
Loading