This page was machine-translated. Spotted a mistake?Help improve it.
Skip to content

Руководство по переводам

SnapOtter поставляется с 21 языком из коробки. Система i18n использует лёгкое собственное среда выполнения с полнотой локалей, контролируемой TypeScript, и динамическим разбиением кода.

Поддерживаемые языки

CodeLanguageNative NameDirection
enАнглийскийEnglishLTR
zh-CNКитайский (упрощённый)简体中文LTR
zh-TWКитайский (традиционный)繁體中文LTR
jaЯпонский日本語LTR
koКорейский한국어LTR
esИспанскийEspañolLTR
frФранцузскийFrançaisLTR
itИтальянскийItalianoLTR
pt-BRПортугальский (Бразилия)Português (Brasil)LTR
deНемецкийDeutschLTR
nlНидерландскийNederlandsLTR
svШведскийSvenskaLTR
ruРусскийРусскийLTR
plПольскийPolskiLTR
ukУкраинскийУкраїнськаLTR
arАрабскийالعربيةRTL
trТурецкийTürkçeLTR
hiХиндиहिन्दीLTR
viВьетнамскийTiếng ViệtLTR
idИндонезийскийBahasa IndonesiaLTR
thТайскийไทยLTR

Как работает определение языка

SnapOtter использует трёхуровневый порядок разрешения:

  1. Предпочтение пользователя — хранится в localStorage("snapotter-locale") и синхронизируется с настройками пользователя при авторизации
  2. Автоопределение браузера — проходит по массиву navigator.languages с сопоставлением префиксов BCP 47
  3. Умолчание экземпляра — переменная окружения DEFAULT_LOCALE администратора (запрашивается из GET /api/v1/config/locale)
  4. Запасной вариант — английский — всегда доступен

Пользователи могут менять язык из:

  • Селектора-глобуса в подвале (десктоп, всегда виден)
  • Селектора языка на странице входа (до авторизации)
  • Раздела Настройки > Общие (предпочтение конкретного пользователя)
  • Выпадающего списка языков в мобильной боковой панели
  • Раздел Настройки > Система задаёт умолчание для всего экземпляра (только администратор)

Как работают переводы

Все строки интерфейса находятся в packages/shared/src/i18n/. Эталонный файл — en.ts, который экспортирует типизированный объект со всеми строками, используемыми приложением (~1500 ключей). Другие языки — это отдельные файлы (например, de.ts, fr.ts), которые экспортируют ту же структуру.

Тип TranslationKeys использует DeepStringRecord, чтобы принимать любое строковое значение, при этом требуя соблюдения структуры ключей. TypeScript обнаруживает отсутствующие ключи в любом файле перевода во время компиляции.

Во время выполнения загружается только активная локаль через динамический import(), что сохраняет основной бандл небольшим.

Использование переводов в компонентах

tsx
import { useTranslation } from "@/contexts/i18n-context";
import { format, plural } from "@/lib/format";

function MyComponent() {
  const { t, locale, setLocale } = useTranslation();
  
  return (
    <div>
      <h1>{t.common.settings}</h1>
      <p>{format(t.settings.people.deleteConfirm, { username: "admin" })}</p>
      <p>{plural(count, t.automate.fileCount, t.automate.fileCountPlural)}</p>
    </div>
  );
}

Как внести перевод

Мы приветствуем PR с переводами напрямую. Вы можете улучшить существующую локаль или добавить новую.

Чтобы сообщить об ошибке перевода, не отправляя код, откройте GitHub Issue с указанием языка, неверной строки и предлагаемого исправления.

TIP

PR с переводами не требуют предварительного одобрения. Форкните репозиторий, внесите изменения и откройте PR. См. Руководство для контрибьюторов, где описан полный процесс PR и требование CLA.

Как создать или обновить перевод

1. Форк и клонирование

bash
git clone https://github.com/<your-username>/snapotter.git
cd snapotter
pnpm install

2. Скопируйте эталонный файл (только для нового языка)

Пропустите этот шаг, если вы улучшаете существующий перевод.

bash
cp packages/shared/src/i18n/en.ts packages/shared/src/i18n/XX.ts

3. Переведите строки

Откройте свой новый файл и переведите каждое строковое значение. Сохраняйте структуру объекта и ключи в точности такими же.

ts
import type { TranslationKeys } from "./en.js";

export const xx: TranslationKeys = {
  common: {
    upload: "Your translation here",
    // ... translate all entries
  },
  // ... translate all sections
} as const;

Правила:

  • Не переводите ключи объекта, только строковые значения
  • Сохраняйте as const в конце
  • Импортируйте TranslationKeys из ./en.js и типизируйте свой экспорт
  • Сохраняйте плейсхолдеры {variable} в точности как есть
  • Массивы (rotatingPhrases, progressMessages) должны иметь одинаковое количество элементов
  • Не переводите: SnapOtter, JPEG, PNG, WebP, EXIF, API и другие технические термины

4. Зарегистрируйте локаль (только для нового языка)

Добавьте свою локаль в SUPPORTED_LOCALES в packages/shared/src/i18n/index.ts:

ts
{ code: "xx", name: "Language Name", nativeName: "Native Name", dir: "ltr" },

5. Проверьте

bash
pnpm typecheck    # catches missing or mistyped keys
pnpm lint         # formatting check
pnpm dev          # manually verify strings appear correctly

6. Отправьте

Откройте PR против main с заголовком вида feat(i18n): add Swedish translation или fix(i18n): correct German typos. Бот CLA попросит вас подписать соглашение при первом вкладе.

Добавление новых ключей перевода

При добавлении новой функции, которой нужны новые строки интерфейса:

  1. Сначала добавьте новые ключи в en.ts (эталонный файл)
  2. Запустите pnpm typecheck — каждый файл локали упадёт, если в нём нет нового ключа
  3. Добавьте новый ключ во все файлы локалей (используйте английский как временный запасной вариант)

Конфигурация

Задайте язык экземпляра по умолчанию через переменную окружения:

yaml
DEFAULT_LOCALE: "de"  # German as the default for all new users

Справочник по файлам

FilePurpose
packages/shared/src/i18n/en.tsАнглийские строки (эталонная локаль, ~1500 ключей)
packages/shared/src/i18n/index.tsSUPPORTED_LOCALES, loadTranslations(), экспорт типов
packages/shared/src/i18n/<locale>.tsФайлы перевода по языкам
apps/web/src/contexts/i18n-context.tsxI18nProvider, хук useTranslation()
apps/web/src/lib/format.tsХелперы format(), plural(), formatFileSize()
apps/api/src/routes/config.tsПубличная конечная точка GET /api/v1/config/locale

Перевод веб-сайта, документации и справочника по API

Поддержка 21 языка выше относится к приложению. Публичный веб-сайт (snapotter.com), этот сайт документации и справочник по REST API также переведены на все 21 язык отдельным пайплайном с хеш-контролем, который повторно использует те же названия и описания инструментов из packages/shared/src/i18n, чтобы терминология оставалась согласованной везде.

Машинный перевод по умолчанию

Каждая неанглийская страница на веб-сайте и в документации переводится машинно при первом проходе (сессией Claude Code, а не сторонним сервисом) и несёт небольшой закрываемый баннер, сообщающий об этом, со ссылкой обратно сюда. Это сделано намеренно: так все 21 язык выходят быстро и честно, а затем сообществу предлагается доработать наиболее важные страницы. Машинный перевод передаёт смысл; проверка человеком делает текст естественным.

Как пайплайн решает, что переводить

Каждая переводимая единица английского источника хешируется, и хеш хранится рядом с её переводом. При каждом запуске пайплайн:

  • переводит любую единицу, у которой ещё нет перевода,
  • пропускает любую единицу, чей сохранённый хеш всё ещё совпадает с английским источником,
  • заново переводит машинную единицу, когда её английский источник меняется,
  • и помечает доработанную человеком единицу как stale (требует проверки), когда её английский источник меняется, вместо того чтобы перезаписывать вашу работу.

Доработка веб-перевода через PR

Вы улучшаете перевод веб-сайта, документации или справочника по API так же, как вы улучшаете локаль приложения: редактируя сгенерированный файл и открывая PR.

  1. Найдите сгенерированный перевод для вашего языка:
    • строки интерфейса веб-сайта: apps/landing/src/i18n/<locale>.json
    • страница документации: apps/docs/<locale>/**.md
    • справочник по API: apps/api/src/openapi.<locale>.yaml
  2. Отредактируйте текст. Сохраняйте код, ссылки, {placeholders} и любые маркеры ⸤I18N…⸥ в точности как есть; валидатор пайплайна отклоняет перевод, который отбрасывает или переупорядочивает их.
  3. Откройте PR. Редактирование единицы переключает её происхождение с machine на human, так что пайплайн никогда не перезапишет её при последующем запуске. Если английский источник позже изменится, ваша единица будет помечена stale для проверки, а не молча заменена.

Чтобы сообщить об ошибке перевода, не отправляя код, откройте GitHub Issue с указанием URL страницы, языка, неверного текста и вашего предлагаемого исправления.

TIP

Пайплайн перевода запускают мейнтейнеры; вам не нужен ключ API, чтобы внести вклад. Просто отредактируйте сгенерированный файл и откройте PR. См. scripts/i18n/README.md, где описано, как работает пайплайн.