@cloud-ru/ds-locale

中文文档
1.0.0发布于 2026/08/18Apache-2.0TypeScriptESM + CJS近 30 天下载 1.4万

Рантайм локализации — строки живут в самих пакетах (defineLocale/defineMessages), провайдер несёт только язык.

原始使用文档
文档目录12 节
  1. Когда использовать
  2. Установка
  3. Примеры использования
  4. Переключение языка на лету
  5. Интерполяция
  6. Спецсимволы
  7. Props
  8. LocaleProvider
  9. Смотри также
  10. Оверрайды строк и новые языки
  11. Приложение уже использует компонент
  12. Корень держит пакеты в devDependencies

Locale

@cloud-ru/ds-locale — Рантайм локализации — строки живут в самих пакетах (defineLocale/defineMessages), провайдер несёт только язык.

@cloud-ru/ds-locale — рантайм локализации дизайн-системы. Строк он не содержит: каждый пакет объявляет свой словарь рядом с компонентом через defineLocale/defineMessages, а @cloud-ru/ds-locale несёт только текущий язык, fallback и реестр сервисных оверрайдов. Это держит пакет стабильным (контракт не растёт с числом компонентов) и безопасным для SSR и микрофронтов.

Полное руководство по модели — в паттерне Локализация — строки в пакетах.

Когда использовать

  • Компонент-пакет объявляет свои тексты: defineMessages({ 'en-GB', 'ru-RU' }) + defineLocale('NS', …).
  • Хост-приложение задаёт язык: LocaleProvider lang={…} (или store для MFE через getGlobalLocaleStore).
  • Сервис переопределяет строки или добавляет язык (например de-DE) через <locale>.extend(lang, …) либо тип LocaleOverride.

Установка

pnpm add @cloud-ru/ds-locale
import { defineLocale, defineMessages, LocaleProvider, LocaleOverride, useLang, getGlobalLocaleStore } from '@cloud-ru/ds-locale'

Примеры использования

Переключение языка на лету

Словарь компонента через defineMessages/defineLocale; LocaleProvider задаёт язык, useTranslations читает строку.

import { Button } from '@cloud-ru/ds-button';
import { defineLocale, defineMessages, LocaleProvider } from '@cloud-ru/ds-locale';
import { SegmentControl } from '@cloud-ru/ds-segment-control';
import { Tag } from '@cloud-ru/ds-tag';
import { Typography } from '@cloud-ru/ds-typography';
import { Flex } from '@cloud-ru/ds-uikit-product-flex';
import { useState } from 'react';

// Словарь компонента co-located: defineMessages требует одинаковый набор ключей во всех языках.
const DEMO_MESSAGES = defineMessages({
  'en-GB': { action: 'Save', status: 'Ready', greeting: 'Hello' },
  'ru-RU': { action: 'Сохранить', status: 'Готово', greeting: 'Привет' },
});

const demoLocale = defineLocale('LocaleDemo', DEMO_MESSAGES);

// Консьюмер читает строки из ближайшего LocaleProvider — текст компонентов меняется вслед за языком.
function LocalizedSurface() {
  const { t, lang } = demoLocale.useTranslations();

  return (
    <Flex gap='2m' align='center' wrap>
      <Button appearance='primary' label={t('action')} />
      <Tag appearance='green' label={t('status')} />
      <Typography variant='body' size='s'>
        {t('greeting')} · {lang}
      </Typography>
    </Flex>
  );
}

const LANG_ITEMS = [
  { value: 'ru-RU', label: 'Русский' },
  { value: 'en-GB', label: 'English' },
];

export function LanguageToggle() {
  const [lang, setLang] = useState('ru-RU');

  return (
    <Flex direction='column' gap='2m' align='flex-start'>
      <SegmentControl items={LANG_ITEMS} value={lang} onChange={value => setLang(String(value))} />
      <LocaleProvider lang={lang} fallbackLang='en-GB'>
        <LocalizedSurface />
      </LocaleProvider>
    </Flex>
  );
}

Интерполяция

Плейсхолдеры {{name}} в строке подставляются вторым аргументом t. Тип аргумента выводится из самой строки: t требует ровно те ключи, что есть в переводе — опечатка или пропуск ловятся компилятором. Значения — string | number; дату или число форматируйте на месте вызова. Плюрализации/ICU нет.

// словарь → greeting: 'Привет, {{name}}!'
t('greeting', { name: 'Ada' })            // ок
t('greeting', { naem: 'Ada' })            // ошибка компиляции: нет ключа name
t('greeting')                              // ошибка компиляции: интерполяция обязательна
t('apply')                                 // без плейсхолдеров — второй аргумент запрещён

Спецсимволы

Частые типографские символы — зарезервированными токенами, движок подставляет их сам. В аргументах t они не требуются, а в словаре читаются явно — {{nbsp}} вместо невидимого символа, поэтому строка остаётся читаемой в diff.

// словарь — токены видны в исходнике, символ появляется при выводе
const ru = {
  price: 'Цена:{{nbsp}}{{value}}{{nnbsp}}₽',        // 100 ₽ — число не отрывается от валюты
  limit: 'Лимит: 10{{thinsp}}000 запросов',         // 10 000 — тонкий пробел между разрядами
  hours: 'Часы работы: Пн{{ndash}}Пт',              // Пн–Пт — диапазон через среднее тире
  plan: 'Тариф{{nbsp}}{{mdash}}{{nbsp}}Бизнес',     // Тариф — Бизнес — длинное тире с неразрывными
  word: 'мак{{shy}}си{{shy}}маль{{shy}}ный',        // мягкие переносы: слово рвётся только при нехватке места
  saved: 'Сохранение{{hellip}}',                    // Сохранение… — многоточие одним символом
  done: 'Готово{{newline}}Можно закрыть окно',      // перенос строки (нужен white-space: pre-line)
}
t('price', { value: 100 })   // → Цена: 100 ₽
t('limit')                   // → Лимит: 10 000 запросов
t('plan')                    // → Тариф — Бизнес

Полный набор — в константе SPECIAL_CHARS (@cloud-ru/ds-locale).

Props

LocaleProvider

LocaleProviderProps

PropTypeDefaultDescription
childrenstring | number | boolean | ReactElement<any, string | JSXElementConstructor<any>> | Iterable<ReactNode> | ReactPortal | null | undefined—
fallbackLangLang—Язык, на который откатываемся при отсутствии перевода. По умолчанию en-GB.
langLang—Статический язык (одно-корневой app/SSR). Игнорируется, если задан store.
overridesOverrideEntry—Оверрайды/новые языки — собираются через <locale>.extend(lang, ...), app-static.
storeLangStore—Реактивный источник языка для MFE: getGlobalLocaleStore().store.
  • Lang = string

LangStore

PropTypeDefaultDescription
getServerSnapshot(() => LangSnapshot) | undefined—Значение для SSR/гидрации. Если не задан — используется getSnapshot.
getSnapshot() => LangSnapshot—Текущее значение. Обязан возвращать стабильную (по Object.is) ссылку, пока значение не менялось.
subscribe(onStoreChange: () => void) => () => void—Подписка на изменения; возвращает функцию отписки.
  • MessageTree = { [key: string]: string | MessageTree; }

OverrideEntry

PropTypeDefaultDescription
langLang—
messagesMessageTree | PartialDeep | PartialObjectDeep—
namespacestring—
  • PartialDeep = T extends BuiltIns | ((...arguments_: unknown[]) => unknown) | (new (...arguments_: unknown[]) => unknown) ? T : T extends Map<infer KeyType, infer ValueType> ? Map<PartialDeep<KeyType>, PartialDeep<ValueType>> : T extends Set<infer ItemType> ? Set<PartialDeep<ItemType>> : T extends ReadonlyMap<infer KeyType, infer ValueType> ? ReadonlyMap<PartialDeep<KeyType>, PartialDeep<ValueType>> : T extends ReadonlySet<infer ItemType> ? ReadonlySet<PartialDeep<ItemType>> : T extends object ? PartialObjectDeep<T> : unknown

  • PartialObjectDeep = { [KeyType in keyof ObjectType]?: PartialDeep<ObjectType[KeyType]> | undefined; }

Смотри также

Оверрайды строк и новые языки

Провайдер хранит реестр оверрайдов, но не словари. Оверрайд переопределяет строку или добавляет язык, которого нет в пакете из коробки. Источник строки — namespace пакета; язык — тег BCP-47 (en-GB, ru-RU, de-DE).

Namespace — это имя пакета в реестре оверрайдов: @cloud-ru/ds-calendar, @cloud-ru/ds-uikit-product-quota. Один пакет — один словарь и один namespace; под-области пакета (например container/upload/systemEvent у тостера) живут вложенными ключами словаря, а не отдельными namespace'ами.

Имена пакетов уникальны, поэтому namespace'ы не пересекаются между пакетами. Соответствие правилу проверяет pnpm check:locale-namespaces (завершается ошибкой на чужом или дублирующемся namespace).

Путь зависит от того, рендерит ли приложение сам компонент.

Приложение уже использует компонент

Когда пакет компонента — обычная зависимость, оверрайд собирается его locale-объектом. extend типизирован по форме словаря: опечатка в ключе — ошибка компиляции.

import { calendarLocale } from '@cloud-ru/ds-calendar'
import { LocaleProvider } from '@cloud-ru/ds-locale'

const calendarDe = calendarLocale.extend('de-DE', {
  apply: 'Anwenden',
  current: 'Jetzt',
})

function Shell({ children }) {
  return (
    <LocaleProvider lang='de-DE' overrides={[calendarDe]}>
      {children}
    </LocaleProvider>
  )
}

Корень держит пакеты в devDependencies

Корневое приложение, которое добавляет язык сразу для всех микрофронтов, компоненты не рендерит — ему нужны только типы словарей. Пакеты компонентов остаются в devDependencies, а locale-объект импортируется через import type из под-пути @ds/<pkg>/locale и стирается при компиляции. В сборку корня не попадают ни React-дерево компонента, ни его стили, ни строки.

  • @cloud-ru/ds-locale — обычная зависимость: в нём провайдер, который объединяет оверрайды по namespace.
  • @ds/<pkg> — devDependency: из него берутся только типы.

LocaleOverride<typeof <pkg>Locale> выводит из типа и литерал namespace, и форму словаря. Строка namespace и ключи сообщений проверяются компилятором.

import { composeOverrides, getGlobalLocaleStore, LocaleOverride, LocaleProvider } from '@cloud-ru/ds-locale'
import type { calendarLocale } from '@cloud-ru/ds-calendar/locale'
import type { uploadFilesLocale } from '@cloud-ru/ds-uikit-product-upload-files/locale'
import type { quotaLocale } from '@cloud-ru/ds-uikit-product-quota/locale'

// Немецкий сразу для нескольких пакетов: одна запись на namespace.
const calendarDe: LocaleOverride<typeof calendarLocale> = {
  namespace: '@cloud-ru/ds-calendar', // сверяется с литералом из типа пакета
  lang: 'de-DE',
  messages: {
    apply: 'Anwenden',
    current: 'Jetzt',
    defaultPresets: { lastWeek: 'Letzte 7 Tage' },
  },
}

const quotaDe: LocaleOverride<typeof quotaLocale> = {
  namespace: '@cloud-ru/ds-uikit-product-quota',
  lang: 'de-DE',
  messages: {
    increaseQuota: 'Kontingent erhöhen',
    widgetTitle: { withoutProject: 'Projektkontingente' },
  },
}

const uploadFilesDe: LocaleOverride<typeof uploadFilesLocale> = {
  namespace: '@cloud-ru/ds-uikit-product-upload-files',
  lang: 'de-DE',
  messages: {
    dropzoneButton: 'Datei auswählen',
    errorRequired: 'Pflichtfeld',
  },
}

// composeOverrides сводит записи (и массивы записей) в один список для провайдера.
const overrides = composeOverrides(calendarDe, quotaDe, uploadFilesDe)

function Shell({ children }) {
  return (
    <LocaleProvider store={getGlobalLocaleStore().store} overrides={overrides}>
      {children}
    </LocaleProvider>
  )
}

Под-путь @ds/<pkg>/locale отдаёт только locale-слой пакета: locale-объект и тип формы словаря (например CalendarMessages). Для оверрайдов на корне этого достаточно — компонент импортировать не нужно.

版本与发布记录

@cloud-ru/ds-locale 共发布 1 个版本,首次发布于 2026/08/18;过去 12 个月发布了 1 个版本。

版本发布日期距今
1.0.0latest2026/08/182 个月前

关于 @cloud-ru/ds-locale 的常见问题

@cloud-ru/ds-locale 是什么?

Рантайм локализации — строки живут в самих пакетах (defineLocale/defineMessages), провайдер несёт только язык.

如何安装 @cloud-ru/ds-locale?

在项目目录运行 npm install @cloud-ru/ds-locale,或使用 pnpm add / yarn add / bun add。

@cloud-ru/ds-locale 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@cloud-ru/ds-locale 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@cloud-ru/ds-locale 可以商用吗?

@cloud-ru/ds-locale 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@cloud-ru/ds-locale 的下载量有多少?

根据 npm 官方下载统计,@cloud-ru/ds-locale 在 2026/10/10 – 2026/11/08 这 30 天内被下载 13,770 次,日均约 459 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@cloud-ru/ds-locale 还在维护吗?

最新版本 1.0.0 发布于 2026/08/18(2 个月前);至今共发布 1 个版本,其中过去 12 个月发布了 1 个;首次发布于 2026/08/18。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。