Компоненты Koobiq выводят собственные строки — меню фильтров в filter bar, подсказки в code block,
доступные имена всех кнопок-иконок, плейсхолдер поля даты и так далее. Все они приходят из одного места:
KbqLocaleService.
Доступные идентификаторы локали: en-US, es-LA, pt-BR, ru-RU и tk-TM.
Данные приложения не переводятся. Названия опций, значения фильтров, ячейки таблиц и всё, что приложение передаёт в компонент, выводится без изменений.
Перевод собственных строк — задача приложения: Koobiq не требует и не поставляет для этого библиотеку. Если переведённая строка должна содержать компонент Koobiq — ссылку, кнопку, выпадающий список, — встройте его через dynamic-translation.
KbqLocaleService объявлен как providedIn: 'root', но компоненты читают его через токен
KBQ_LOCALE_SERVICE, у которого нет фабрики. Пока он не предоставлен, локализация не работает:
import { kbqLocaleServiceProvider } from '@koobiq/components/core'; bootstrapApplication(AppComponent, { providers: [kbqLocaleServiceProvider()] });
Без этого провайдера каждый компонент использует собственные значения по умолчанию (ru-RU), а смена
локали во время работы приложения ничего не меняет. Исключение — плейсхолдер поля даты: он называет формат, который
разбирает поле, — поставляемый для локали адаптера дат (KBQ_DATE_LOCALE, если токен не предоставлен —
LOCALE_ID Angular) или сам формат, например yyyy/mm/dd, для локали, строк которой библиотека не
поставляет, и для формата, заданного через KBQ_DATE_FORMATS.
Используйте kbqLocaleServiceProvider(), а не { provide: KBQ_LOCALE_SERVICE, useClass: KbqLocaleService }:
useClass создаёт второй экземпляр, независимый от providedIn: 'root', поэтому setLocale(), вызванный
на экземпляре из inject(KbqLocaleService), не меняет ничего из уже отрисованного. Эта утилита объявляет
сервис рядом с токеном, и обе ссылки указывают на один экземпляр.
Управлять активной локалью можно тремя способами:
KBQ_DEFAULT_LOCALE_ID — значение по умолчанию, ru-RU. Это обычная экспортируемая константа, а не
injection token: её нельзя предоставить, только прочитать.KBQ_LOCALE_ID, который задаётся через kbqLocaleIDProvider(id), фиксирует локаль один раз, в момент
создания KbqLocaleService. Провайдер должен лежать в том же массиве providers, что и сам сервис,
потому что сервис читает токен из создавшего инжектора.setLocale(id) меняет локаль во время работы приложения.providers: [kbqLocaleIDProvider('en-US'), kbqLocaleServiceProvider()];Та же пара в providers компонента ограничивает локаль его поддеревом, а остальное приложение сохраняет
свою локаль.
Чтение активной локали:
readonly localeService = kbqInjectLocaleService(); readonly currentLocale = this.localeService.localeId; // Signal<KbqLocaleIdLike> readonly localeData = this.localeService.data; // Signal<KbqLocaleData> readonly available = this.localeService.items; // Signal<KbqLocaleItem[]>, для выбора локали
kbqInjectLocaleService() читает токен KBQ_LOCALE_SERVICE, а не класс. Передайте { optional: true } в коде,
который должен работать и в приложении без сервиса локали.
changes — BehaviorSubject с идентификатором активной локали; через него о смене локали узнаёт код за
пределами шаблона. Геттеры id и current, напротив, устарели в пользу сигналов localeId и data.
Сигнал, прочитанный в шаблоне, регистрируется на читающем представлении, поэтому setLocale() во время
работы приложения доходит до OnPush-потомков, чего подписка в родителе сделать не может.
Локаль — это набор секций, по одной на область строк. Секция и есть единица переопределения: её имя стоит
и в данных локали, и в getParams(), и в [localeOverrides].
| Секция | Что переводит | Пакет |
|---|---|---|
a11y | доступные имена кнопок-иконок и областей, которые библиотека рисует сама | core |
select | подпись «Выбрать все» и счётчик скрытых значений | core |
popoverConfirm | вопрос и кнопку подтверждающего поповера | core |
formatters | разделители и сокращения kbqNumber, kbqTableNumber и kbqRoundNumber | core |
sizeUnits | единицы размера kbqDataSize | core |
input | разделители числового поля ввода | input |
datepicker | плейсхолдер и формат поля даты | datepicker |
timepicker | плейсхолдеры поля времени | timepicker |
timezone | плейсхолдер поиска часового пояса | timezone |
timeRange | заголовок и редактор диапазона времени | time-range |
codeBlock | подсказки и подписи кнопок блока кода | code-block |
fileUpload | тексты областей загрузки одного файла и нескольких | file-upload |
filterBar | поиск, сброс и меню сохранённых фильтров | filter-bar |
actionsPanel | подсказку закрытия панели действий | actions-panel |
clampedText | подписи «Развернуть» и «Свернуть» усечённого текста | clamped-text |
navbar | подсказки переключателя вертикального navbar | navbar |
searchExpandable | подсказку и плейсхолдер раскрывающегося поиска | search-expandable |
appSwitcher | поиск и заголовок переключателя приложений | app-switcher |
notificationCenter | подписи центра уведомлений | notification-center |
Полный контракт секции описан её типом Kbq<Секция>LocaleConfiguration, где задокументирован каждый ключ.
Токен секции и её провайдер называются механически: KBQ_<СЕКЦИЯ>_LOCALE_CONFIGURATION и
kbq<Секция>LocaleConfigurationProvider().
Провайдер секции переопределяет только переданные ключи — остальные сохраняют значения по умолчанию:
import { kbqCodeBlockLocaleConfigurationProvider } from '@koobiq/components/code-block'; providers: [kbqCodeBlockLocaleConfigurationProvider({ copyTooltip: 'Скопировать фрагмент' })];
Эти провайдеры работают и в element injector, поэтому провайдер на компоненте ограничивает переопределение его поддеревом.
<?xml version="1.0" encoding="UTF-8" ?> <vulnerabilities> <vulnerability> <name>Cross-site scripting</name> <abbreviation>XSS</abbreviation> <description>Cross-site scripting (XSS) is a type of security vulnerability that can be found in some web applications. XSS attacks enable attackers to inject client-side scripts into web pages viewed by other users. A cross-site scripting vulnerability may be used by attackers to bypass access controls such as the same-origin policy. During the second half of 2007, XSSed documented 11,253 site-specific cross-site vulnerabilities, compared to 2,134 "traditional" vulnerabilities documented by Symantec. XSS effects vary in range from petty nuisance to significant security risk, depending on the sensitivity of the data handled by the vulnerable site and the nature of any security mitigation implemented by the site's owner network.</description> </vulnerability> <vulnerability> <name>Denial-of-service attack</name> <abbreviation>DoS</abbreviation> <description>In computing, a denial-of-service attack (DoS attack) is a cyber-attack in which the perpetrator seeks to make a machine or network resource unavailable to its intended users by temporarily or indefinitely disrupting services of a host connected to a network. Denial of service is typically accomplished by flooding the targeted machine or resource with superfluous requests in an attempt to overload systems and prevent some or all legitimate requests from being fulfilled. The range of attacks varies widely, spanning from inundating a server with millions of requests to slow its performance, overwhelming a server with a substantial amount of invalid data, to submitting requests with an illegitimate IP address.</description> </vulnerability> <vulnerabilities>
Каждая такая функция поставляется из пакета своего компонента. Исключение —
kbqSelectLocaleConfigurationProvider, который живёт в @koobiq/components/core: секцию select выводят
четыре независимых друг от друга пакета — kbq-select, kbq-tree-select, kbq-tree-selection и
kbq-list-selection.
Переопределение накладывается поверх того, что активно: поверх строк активной локали, если приложение
предоставляет сервис, иначе поверх значений по умолчанию из токена. Поэтому переданные ключи остаются
закреплёнными при вызове setLocale() во время работы, а все остальные следуют за локалью. Переопределите
секцию целиком, если она вообще не должна следовать за локалью, либо зарегистрируйте собственную локаль
(см. ниже), если переопределение должно переключаться вместе с остальными строками.
Провайдер можно повесить только на инжектор, поэтому ограничить его одним компонентом — значит завести отдельную компонентную границу. Все локализованные компоненты принимают строки ещё и как привязку в шаблоне:
<kbq-select [localeOverrides]="{ select: { selectAll: 'Выбрать все' } }" />Значение задаётся по секциям — в том же виде, в каком его принимают addLocale() и KBQ_LOCALE_DATA, —
поэтому одной привязкой можно покрыть и доступные имена, которые компонент выводит через своих потомков:
<kbq-select [localeOverrides]="{ select: { selectAll: 'Выбрать все' }, a11y: { clear: 'Очистить выбор' } }" />
Чтобы ограничить переопределение целой областью, а не одним компонентом, повесьте
KbqLocaleOverridesDirective на любой элемент приложения. Всё, что отображается внутри, — в том числе
панель, которая открывается в overlay-контейнере, — разрешается относительно этого элемента:
import { KbqLocaleOverridesDirective } from '@koobiq/components/core';<div [kbqLocaleOverrides]="{ a11y: { close: 'Закрыть' } }"> <kbq-code-block [files]="files" /> <kbq-filter-bar [filter]="filter" /> </div>
Эти два имени не взаимозаменяемы. [localeOverrides] — инпут, который объявляет компонент Koobiq;
[kbqLocaleOverrides] — собственный селектор директивы, для элементов приложения. Селектор, написанный на
компоненте, который уже несёт директиву, сопоставляет её на одном элементе дважды, и Angular отклоняет это
ошибкой NG0309.
Источники применяются от самого общего к самому локальному:
kbq<Component>LocaleConfigurationProvider(), начиная с самого внешнего инжектора;KbqLocaleOverridesDirective, начиная с самого внешнего элемента;[localeOverrides] компонента.Шаги с 3 по 5 переопределяют только те ключи, которые называют. Шаг 2 — исключение: активная локаль заменяет значения токена целиком, а не накладывается на них, поэтому значение, переданное в токен конфигурации, отбрасывается, как только появляется сервис локали. Переопределяйте секцию, а не токен.
Пайп — не элемент, поэтому своего [localeOverrides] у kbqNumber, kbqRoundNumber и kbqDataSize нет.
Зато до них доходит носитель на предке: пайп разрешает секцию по тому же инжектору, что и обычный читатель.
addLocale() принимает частичные данные — каждая секция и каждый ключ внутри секции необязательны. Всё,
что не указано, дополняется из поставляемой локали с тем же идентификатором, а для нового идентификатора —
из KBQ_DEFAULT_LOCALE_ID. Поэтому getParams() всегда возвращает полную секцию, что бы ни было
зарегистрировано:
localeService.addLocale('en-GB', { select: { selectAll: 'Select everything' }, a11y: { close: 'Dismiss' } });
Параметр типизирован как KbqPartialLocaleData, поэтому опечатка в названии секции или ключа — это ошибка
компиляции, а не строка, которая молча никогда не появится.
Те же данные можно передать заранее через KBQ_LOCALE_DATA. Токен читается один раз, конструктором
KbqLocaleService, поэтому провайдер должен лежать в том же массиве providers, что и сам сервис:
const localeData = { 'en-GB': { select: { selectAll: 'Select everything' } } } satisfies KbqLocaleDataInput; providers: [{ provide: KBQ_LOCALE_DATA, useValue: localeData }, kbqLocaleServiceProvider()];
satisfies здесь не для красоты: useValue объявлен как any, и без него та же опечатка пройдёт молча.
Локаль, добавленная таким образом, регистрируется, но в список items не попадает — а переключатель локали
строится именно по этому списку. Если новая локаль должна быть доступна для выбора, перечислите items
целиком, вместе с поставляемыми локалями.
const { selectAll } = localeService.getParams('select'); // KbqSelectLocaleConfiguration const select = localeService.params('select'); // Signal<KbqSelectLocaleConfiguration>
Название секции проверяется по KbqLocaleSection, а тип результата выводится из него.
Оба варианта читают только локаль. Компоненту, которому важны и переопределения выше — провайдеры и
[localeOverrides], — придётся объявить директиву и читать через неё:
import { inject } from '@angular/core'; import { KBQ_SELECT_LOCALE_CONFIGURATION, KbqLocaleOverridesDirective } from '@koobiq/components/core'; @Component({ selector: 'my-widget', hostDirectives: [ { directive: KbqLocaleOverridesDirective, inputs: ['kbqLocaleOverrides: localeOverrides'] } ] }) export class MyWidget { protected readonly localeConfiguration = inject(KbqLocaleOverridesDirective, { self: true }).read( 'select', KBQ_SELECT_LOCALE_CONFIGURATION ); }
{ self: true } здесь обязательно: с { host: true } Angular поднимется до хост-элемента охватывающего
компонента и вернёт его носитель, так что компонент прочитает чужие переопределения вместо своих.
Запись hostDirectives приходится повторять литералом в каждом компоненте: вынесенная в константу, она
падает с NG1010 при использовании из другой точки входа.
Называйте поле localeConfiguration — это принятое в библиотеке имя итоговых строк, которые читает
компонент; оно отделено от [localeOverrides], который задаёт частичное переопределение. Компонент,
читающий вторую секцию, уточняет имя — как это делает a11yLocaleConfiguration.
read() возвращает сигнал, поэтому setLocale() доходит до шаблона сам, а источники сливаются в
перечисленном выше порядке. Именно чтение через носитель и заставляет [localeOverrides] работать на
компоненте: одно без другого не бывает. Там, где носитель поставить некуда — в пайпе или в содержимом,
которое создаётся программно, — те же источники читает kbqInjectLocaleConfiguration(section, token).
Адаптеры дат и числовые пайпы используют тот же сервис, но им нужны собственные провайдеры.
KbqLocaleServiceModule — его подключают модули адаптеров дат — объявляет сервис сам.
Чтобы ограничить локаль поддеревом, в котором есть даты, объявите адаптер и форматтер в providers того же
компонента, что и kbqLocaleIDProvider. Импорт модуля для этого не годится: imports: [KbqLuxonDateModule]
помещает адаптер в environment injector, где он получит корневой сервис локали, и названия месяцев
останутся на языке всего приложения, а не этого поддерева.