npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@cloud-ru/ds-locale

v1.0.0

Published

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

Readme

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

| Prop | Type | Default | Description | |------|------|---------|-------------| | children | string \| number \| boolean \| ReactElement<any, string \| JSXElementConstructor<any>> \| Iterable<ReactNode> \| ReactPortal \| null \| undefined | — | | | fallbackLang | Lang | — | Язык, на который откатываемся при отсутствии перевода. По умолчанию en-GB. | | lang | Lang | — | Статический язык (одно-корневой app/SSR). Игнорируется, если задан store. | | overrides | OverrideEntry | — | Оверрайды/новые языки — собираются через <locale>.extend(lang, ...), app-static. | | store | LangStore | — | Реактивный источник языка для MFE: getGlobalLocaleStore().store. |

Related types

  • Lang = string

LangStore

| Prop | Type | Default | Description | |------|------|---------|-------------| | getServerSnapshot | (() => LangSnapshot) \| undefined | — | Значение для SSR/гидрации. Если не задан — используется getSnapshot. | | getSnapshot | () => LangSnapshot | — | Текущее значение. Обязан возвращать стабильную (по Object.is) ссылку, пока значение не менялось. | | subscribe | (onStoreChange: () => void) => () => void | — | Подписка на изменения; возвращает функцию отписки. |

  • MessageTree = { [key: string]: string | MessageTree; }

OverrideEntry

| Prop | Type | Default | Description | |------|------|---------|-------------| | lang | Lang | — | | | messages | MessageTree | PartialDeep | PartialObjectDeep | — | | | namespace | string | — | |

  • 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). Для оверрайдов на корне этого достаточно — компонент импортировать не нужно.