Интернационализация (i18n)
У компонентов CDS есть собственные тексты: «Отсутствуют данные» в селекте, подписи фильтров таблицы, aria-label кнопки закрытия, счётчик файлов. Все они идут через встроенный слой локализации, поэтому вы можете перевести их на другой язык, переопределить отдельную строку под свой продукт или переключать язык на лету — не переписывая компоненты.
В библиотеку встроены два словаря — русский и английский. По умолчанию активен ru, поэтому без настройки всё уже на русском.
Настройка
Локаль настраивается опцией locale в createCds. Переключиться на встроенный английский — одна строка, словари для этого передавать не нужно:
import { createApp } from 'vue';
import '@central-design-system/components/styles';
import { createCds, components, directives } from '@central-design-system/components';
import App from './App.vue';
const cds = createCds({
components,
directives,
locale: {
locale: 'en'
}
});
createApp(App).use(cds);Языки, которых в библиотеке нет, добавляются словарём в messages:
import de from './locales/cds-de';
createCds({
components,
directives,
locale: {
locale: 'de',
fallback: 'en',
messages: { de }
}
});| Опция | Тип | Назначение |
|---|---|---|
locale | string | Активный язык; из коробки доступны ru (по умолчанию) и en |
fallback | string | Язык, из которого берётся строка, если в активном её нет; по умолчанию ru |
messages | LocaleMessages | Словари дополнительных языков; словарь одноимённого встроенного заменяется целиком |
decimalSeparator | string | Разделитель дробной части; по умолчанию выводится из активной локали |
rtl | Record<string, boolean> | Карта языков с письмом справа налево (см. RTL) |
adapter | LocaleInstance | Свой движок переводов вместо встроенного (см. Свой адаптер) |
Встроенные словари доступны и вам — они лежат в экспорте locales. Берите их за основу для своего языка или чтобы поправить пару строк:
import { locales } from '@central-design-system/components';
locales.ru; // русский словарь
locales.en; // английский словарьПолный набор ключей — в разделе Ключи сообщений.
Как компоненты берут текст
Строки компонентов — это ключи с префиксом $cds., которые библиотека прогоняет через функцию перевода. Часть ключей объявлена значениями пропсов по умолчанию, поэтому их видно в API: text у CdsFileUpload — $cds.fileUpload.browse, closeLabel у CdsNotification — $cds.close, noDataText у CdsSelect — $cds.noDataText.
Отсюда три способа поменять текст.
Сменить язык целиком — опцией locale:
createCds({ locale: { locale: 'en' } });Поменять надписи во всём продукте — словарём поверх встроенного:
import { createCds, locales } from '@central-design-system/components';
createCds({
locale: {
messages: {
ru: { ...locales.ru, noDataText: 'Ничего не нашлось' }
}
}
});Важно. Словарь языка заменяется целиком, а не сливается по ключам. Без спреда ...locales.ru в русской локали останется один ключ, а остальные строки превратятся в свои же ключи ($cds.close вместо «Закрыть»). Вложенные секции спредьте отдельно: table: { ...locales.ru.table, emptyText: '…' }.
Поменять одну надпись в одном месте — пропсом:
<CdsSelect :items="items" no-data-text="Подходящих поставщиков нет" />Тот же проп можно задать значением по умолчанию для всех экземпляров компонента — см. глобальную конфигурацию:
createCds({
defaults: {
CdsSelect: { noDataText: 'Ничего не нашлось' }
}
});Функция перевода принимает и обычный текст: строка без префикса $cds. возвращается как есть, подставляя параметры. Так пропсы с локализуемым значением работают одинаково и с ключом, и с готовой фразой.
Если ключа нет ни в активной локали, ни в fallback, компонент покажет сам ключ, а в dev-режиме в консоли появится предупреждение [CDS: useLocale] Translation key … not found.
Смена языка в рантайме
createCds возвращает инстанс, у которого локаль лежит в реактивных ссылках — переключение языка достаточно записать в current:
const cds = createCds({ components, directives });
// например, по выбору пользователя в настройках профиля
cds.locale.current.value = 'en';Внутри компонентов то же делает композабл useLocale:
<script setup lang="ts">
import { CdsButton, CdsButtonToggle, useLocale } from '@central-design-system/components';
const { current } = useLocale();
</script>
<template>
<CdsButtonToggle v-model="current" appearance="tertiary">
<CdsButton value="ru" text="Русский" />
<CdsButton value="en" text="English" />
</CdsButtonToggle>
</template>Все смонтированные компоненты пересчитают свои строки сами — перезагрузка страницы не нужна.
Локаль для части приложения
Отдельному поддереву можно задать свой язык — например, показать англоязычный виджет внутри русского интерфейса. Для этого в компоненте-обёртке вызовите provideLocale: он берёт локаль из контекста, накладывает переданные пропсы и провайдит результат детям.
<script setup lang="ts">
import { provideLocale } from '@central-design-system/components';
const props = defineProps<{ locale?: string; fallback?: string }>();
provideLocale(props);
</script>
<template>
<div>
<slot />
</div>
</template><template>
<LocaleProvider locale="en">
<CdsSelect :items="items" />
</LocaleProvider>
</template>Не переданные пропсы наследуются от родительской локали, поэтому обёртка с одним locale не теряет ни fallback, ни словари.
useLocale
Композабл отдаёт текущую локаль и её функции — используйте его в своих компонентах, чтобы их тексты жили в тех же словарях, что и тексты CDS.
<script setup lang="ts">
import { useLocale } from '@central-design-system/components';
const { t, n } = useLocale();
</script>
<template>
<p>{{ t('$cds.table.emptyText') }}</p>
<p>{{ n(1234.56) }}</p>
</template>| Свойство | Тип | Назначение |
|---|---|---|
t | (key: string, ...params: unknown[]) => string | Перевод ключа $cds.… с подстановкой параметров |
n | (value: number) => string | Форматирование числа по активной локали |
current | Ref<string> | Активный язык; запись меняет язык |
fallback | Ref<string> | Язык-подстраховка |
messages | Ref<LocaleMessages> | Словари; можно дописывать в рантайме |
decimalSeparator | ShallowRef<string> | Разделитель дробной части активной локали |
Параметры в строках нумерованные — {0}, {1}:
const { t } = useLocale();
t('$cds.carousel.delimiter', 3, 8); // «Слайд 3 из 8»
t('Осталось дней: {0}', 5); // «Осталось дней: 5»n под капотом — Intl.NumberFormat с активной локалью и fallback, так что разделители групп и дробной части приходят из браузера. decimalSeparator библиотека выводит из той же локали (, для ru, . для en); если в продукте принят другой — задайте его явно в createCds.
Даты
Названия месяцев и дней недели живут в отдельных date-локалях: их везут ru и en. Компоненты даты и времени берут date-локаль по активному языку, поэтому после current.value = 'en' календарь переключится сам.
Одному компоненту язык задаётся пропом locale — он есть у CdsDatePicker, CdsDateInput и CdsTimePicker:
<CdsDatePicker v-model="value" locale="en" :first-day-of-week="0" header />Проп принимает не только имя языка, но и свой объект date-локали (тип TDateLocale) — так подключается язык, которого в библиотеке нет. Встроенные date-локали для сверки и переиспользования лежат в том же экспорте locales: locales.ruDate, locales.enDate и карта locales.dateLocales.
const de = {
days: 'Sonntag_Montag_Dienstag_Mittwoch_Donnerstag_Freitag_Samstag'.split('_'),
daysShort: 'So_Mo_Di_Mi_Do_Fr_Sa'.split('_'),
months: 'Januar_Februar_März_April_Mai_Juni_Juli_August_September_Oktober_November_Dezember'.split('_'),
monthsLong: 'Januar_Februar_März_April_Mai_Juni_Juli_August_September_Oktober_November_Dezember'.split('_'),
monthsShort: 'Jan_Feb_Mär_Apr_Mai_Jun_Jul_Aug_Sep_Okt_Nov_Dez'.split('_'),
today: 'Heute',
now: 'Jetzt',
firstDayOfWeek: 1,
format24h: true,
pluralDay: (day = 0) => (day === 1 ? 'Tag' : 'Tage')
};<CdsDatePicker v-model="value" :locale="de" />Таблица
CdsTable собирает свои подписи из ключей $cds.table.*, но принимает и точечное переопределение пропом locale — он перекрывает только переданные строки:
<CdsTable :columns="columns" :data="data" :locale="{ emptyText: 'Заявок пока нет' }" />RTL
Языки с письмом справа налево описываются картой rtl: ключ — язык, значение — направление.
createCds({
locale: {
locale: 'ar',
messages: { ar },
rtl: { ar: true }
}
});Компоненты читают направление через useRtl и разворачивают то, что зависит от оси: сторону раскрытия меню и тултипов, стрелки пагинации, порядок ячеек CdsOtpInput, заливку CdsProgressBar. Отступы и границы в стилях в основном описаны логическими свойствами (margin-inline, inset-inline) и зеркалятся браузером сами — но полноценной поддержки RTL дизайн-система пока не заявляет, отдельные места потребуют доводки на вашей стороне.
Атрибут направления библиотека за вас не проставляет — повесьте его на корень приложения сами:
<script setup lang="ts">
import { useRtl } from '@central-design-system/components';
const { isRtl } = useRtl();
</script>
<template>
<div :dir="isRtl ? 'rtl' : 'ltr'">
<slot />
</div>
</template>useRtl отдаёт isRtl и rtlClasses — готовый класс cds-locale--is-rtl / cds-locale--is-ltr для ваших собственных стилей.
Свой адаптер
Если в приложении уже есть i18n-библиотека, отдайте переводы ей: опция adapter принимает любой объект, реализующий интерфейс LocaleInstance. Тогда messages, locale и fallback из createCds не используются — источником истины становится ваш движок, а строки CDS живут в общих словарях приложения под ключом $cds.
import type { LocaleInstance, LocaleOptions } from '@central-design-system/components';
import { toRef } from 'vue';
import type { I18n } from 'vue-i18n';
export function createVueI18nAdapter(i18n: I18n<any, any, any, string, false>): LocaleInstance {
const format = (value: number) => i18n.global.n(value);
return {
name: 'vue-i18n',
current: i18n.global.locale,
fallback: i18n.global.fallbackLocale as never,
messages: i18n.global.messages as never,
decimalSeparator: toRef(() => (format(0.1).includes(',') ? ',' : '.')),
t: (key, ...params) => i18n.global.t(key, params as never),
n: format,
provide: () => createVueI18nAdapter(i18n)
};
}import { createI18n } from 'vue-i18n';
const i18n = createI18n({
legacy: false,
locale: 'ru',
messages: {
ru: {
$cds: { noDataText: 'Отсутствуют данные' },
profile: { title: 'Профиль' }
}
}
});
const cds = createCds({
components,
directives,
locale: {
adapter: createVueI18nAdapter(i18n)
}
});Обязательные члены LocaleInstance: name (по нему адаптер и распознаётся), current, fallback, messages, decimalSeparator, t, n и provide — фабрика вложенной локали для provideLocale. Если поддержка «локали в поддереве» вам не нужна, provide может возвращать тот же инстанс, как в примере выше.
Ключи сообщений
Встроенный русский словарь — шаблон для перевода на новый язык. Скопируйте его, переведите значения и передайте в messages под ключом своего языка; его английская версия доступна как locales.en.
export default {
badge: 'Бейдж',
noDataText: 'Отсутствуют данные',
selectAll: 'Выбрать все',
close: 'Закрыть',
open: 'Открыть',
dismiss: 'Отклонить',
confirmEdit: {
ok: 'ОК',
cancel: 'Отмена'
},
carousel: {
prev: 'Предыдущий слайд',
next: 'Следующий слайд',
delimiter: 'Слайд {0} из {1}'
},
input: {
clear: 'Очистить {0}',
otp: 'Введите одноразовый код',
prependAction: '{0} предварительных действий',
appendAction: '{0} добавочных действий'
},
fileInput: {
counter: 'Файлов: {0}',
counterSize: 'Файлов: {0} ({1} всего)'
},
fileUpload: {
title: 'Перетащите файлы сюда',
divider: 'или',
browse: 'Выберите файлы'
},
steps: {
prev: 'Назад',
next: 'Далее'
},
table: {
filterTitle: 'Фильтр меню',
filterConfirm: 'Выбрать',
filterReset: 'Сброс',
filterEmptyText: 'Нет фильтров',
filterCheckAll: 'Выбрать все элементы',
filterSearchPlaceholder: 'Поиск фильтров',
emptyText: 'Нет данных',
selectAll: 'Выбрать текущую страницу',
selectInvert: 'Инвертировать текущую страницу',
selectNone: 'Очистить все данные',
selectionAll: 'Выбрать все данные',
sortTitle: 'Сортировка',
expand: 'Раскрыть строку',
collapse: 'Свернуть строку',
triggerDesc: 'Отсортировать по убыванию',
triggerAsc: 'Отсортировать по возрастанию',
cancelSort: 'Отменить сортировку',
showed: 'Отображено',
from: 'из',
rowOnPage: 'Строк на странице'
},
uploader: {
uploading: 'Загрузка...',
uploadFile: 'Загрузить файл',
downloadFile: 'Скачать файл',
removeFile: 'Удалить файл',
uploadError: 'Ошибка загрузки',
previewFile: 'Предпросмотр файла'
}
};Словарь можно оставить неполным: недостающие ключи возьмутся из fallback, то есть по умолчанию из русского.