--- description: "21 поддерживаемый язык и способы создания или улучшения переводов для SnapOtter с помощью системы i18n, контролируемой TypeScript." i18n_source_hash: 55837d9fdaef i18n_provenance: human i18n_output_hash: f94f8a0a6f70 --- # Руководство по переводам {#translation-guide} SnapOtter поставляется с 21 языком из коробки. Система i18n использует лёгкое собственное среда выполнения с полнотой локалей, контролируемой TypeScript, и динамическим разбиением кода. ## Поддерживаемые языки {#supported-languages} | Code | Language | Native Name | Direction | |------|----------|-------------|-----------| | `en` | Английский | English | LTR | | `zh-CN` | Китайский (упрощённый) | 简体中文 | LTR | | `zh-TW` | Китайский (традиционный) | 繁體中文 | LTR | | `ja` | Японский | 日本語 | LTR | | `ko` | Корейский | 한국어 | LTR | | `es` | Испанский | Español | LTR | | `fr` | Французский | Français | LTR | | `it` | Итальянский | Italiano | LTR | | `pt-BR` | Португальский (Бразилия) | Português (Brasil) | LTR | | `de` | Немецкий | Deutsch | LTR | | `nl` | Нидерландский | Nederlands | LTR | | `sv` | Шведский | Svenska | LTR | | `ru` | Русский | Русский | LTR | | `pl` | Польский | Polski | LTR | | `uk` | Украинский | Українська | LTR | | `ar` | Арабский | العربية | RTL | | `tr` | Турецкий | Türkçe | LTR | | `hi` | Хинди | हिन्दी | LTR | | `vi` | Вьетнамский | Tiếng Việt | LTR | | `id` | Индонезийский | Bahasa Indonesia | LTR | | `th` | Тайский | ไทย | LTR | ## Как работает определение языка {#how-language-detection-works} SnapOtter использует трёхуровневый порядок разрешения: 1. **Предпочтение пользователя** — хранится в `localStorage("snapotter-locale")` и синхронизируется с настройками пользователя при авторизации 2. **Автоопределение браузера** — проходит по массиву `navigator.languages` с сопоставлением префиксов BCP 47 3. **Умолчание экземпляра** — переменная окружения `DEFAULT_LOCALE` администратора (запрашивается из `GET /api/v1/config/locale`) 4. **Запасной вариант — английский** — всегда доступен Пользователи могут менять язык из: - **Селектора-глобуса в подвале** (десктоп, всегда виден) - Селектора языка на **странице входа** (до авторизации) - Раздела **Настройки > Общие** (предпочтение конкретного пользователя) - Выпадающего списка языков в **мобильной боковой панели** - Раздел **Настройки > Система** задаёт умолчание для всего экземпляра (только администратор) ## Как работают переводы {#how-translations-work} Все строки интерфейса находятся в `packages/shared/src/i18n/`. Эталонный файл — `en.ts`, который экспортирует типизированный объект со всеми строками, используемыми приложением (~1500 ключей). Другие языки — это отдельные файлы (например, `de.ts`, `fr.ts`), которые экспортируют ту же структуру. Тип `TranslationKeys` использует `DeepStringRecord`, чтобы принимать любое строковое значение, при этом требуя соблюдения структуры ключей. TypeScript обнаруживает отсутствующие ключи в любом файле перевода во время компиляции. Во время выполнения загружается только активная локаль через динамический `import()`, что сохраняет основной бандл небольшим. ## Использование переводов в компонентах {#using-translations-in-components} ```tsx import { useTranslation } from "@/contexts/i18n-context"; import { format, plural } from "@/lib/format"; function MyComponent() { const { t, locale, setLocale } = useTranslation(); return (

{t.common.settings}

{format(t.settings.people.deleteConfirm, { username: "admin" })}

{plural(count, t.automate.fileCount, t.automate.fileCountPlural)}

); } ``` ## Как внести перевод {#contributing-a-translation} Мы приветствуем PR с переводами напрямую. Вы можете улучшить существующую локаль или добавить новую. Чтобы сообщить об ошибке перевода, не отправляя код, откройте [GitHub Issue](https://github.com/snapotter-hq/SnapOtter/issues) с указанием языка, неверной строки и предлагаемого исправления. ::: tip PR с переводами не требуют предварительного одобрения. Форкните репозиторий, внесите изменения и откройте PR. См. [Руководство для контрибьюторов](/ru/guide/contributing), где описан полный процесс PR и требование CLA. ::: ## Как создать или обновить перевод {#how-to-create-or-update-a-translation} ### 1. Форк и клонирование {#_1-fork-and-clone} ```bash git clone https://github.com//snapotter.git cd snapotter pnpm install ``` ### 2. Скопируйте эталонный файл (только для нового языка) {#_2-copy-the-reference-file-new-language-only} Пропустите этот шаг, если вы улучшаете существующий перевод. ```bash cp packages/shared/src/i18n/en.ts packages/shared/src/i18n/XX.ts ``` ### 3. Переведите строки {#_3-translate-the-strings} Откройте свой новый файл и переведите каждое строковое значение. Сохраняйте структуру объекта и ключи в точности такими же. ```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. Зарегистрируйте локаль (только для нового языка) {#_4-register-the-locale-new-language-only} Добавьте свою локаль в `SUPPORTED_LOCALES` в `packages/shared/src/i18n/index.ts`: ```ts { code: "xx", name: "Language Name", nativeName: "Native Name", dir: "ltr" }, ``` ### 5. Проверьте {#_5-verify} ```bash pnpm typecheck # catches missing or mistyped keys pnpm lint # formatting check pnpm dev # manually verify strings appear correctly ``` ### 6. Отправьте {#_6-submit} Откройте PR против `main` с заголовком вида `feat(i18n): add Swedish translation` или `fix(i18n): correct German typos`. Бот CLA попросит вас подписать соглашение при первом вкладе. ## Добавление новых ключей перевода {#adding-new-translation-keys} При добавлении новой функции, которой нужны новые строки интерфейса: 1. Сначала добавьте новые ключи в `en.ts` (эталонный файл) 2. Запустите `pnpm typecheck` — каждый файл локали упадёт, если в нём нет нового ключа 3. Добавьте новый ключ во все файлы локалей (используйте английский как временный запасной вариант) ## Конфигурация {#configuration} Задайте язык экземпляра по умолчанию через переменную окружения: ```yaml DEFAULT_LOCALE: "de" # German as the default for all new users ``` ## Справочник по файлам {#file-reference} | File | Purpose | |------|---------| | `packages/shared/src/i18n/en.ts` | Английские строки (эталонная локаль, ~1500 ключей) | | `packages/shared/src/i18n/index.ts` | `SUPPORTED_LOCALES`, `loadTranslations()`, экспорт типов | | `packages/shared/src/i18n/.ts` | Файлы перевода по языкам | | `apps/web/src/contexts/i18n-context.tsx` | `I18nProvider`, хук `useTranslation()` | | `apps/web/src/lib/format.ts` | Хелперы `format()`, `plural()`, `formatFileSize()` | | `apps/api/src/routes/config.ts` | Публичная конечная точка `GET /api/v1/config/locale` | ## Перевод веб-сайта, документации и справочника по API {#translating-the-web-surfaces} Поддержка 21 языка выше относится к **приложению**. Публичный веб-сайт (snapotter.com), этот сайт документации и справочник по REST API также переведены на все 21 язык отдельным пайплайном с хеш-контролем, который повторно использует те же названия и описания инструментов из `packages/shared/src/i18n`, чтобы терминология оставалась согласованной везде. ### Машинный перевод по умолчанию {#machine-translated-by-default} Каждая неанглийская страница на веб-сайте и в документации переводится **машинно** при первом проходе (сессией Claude Code, а не сторонним сервисом) и несёт небольшой закрываемый баннер, сообщающий об этом, со ссылкой обратно сюда. Это сделано намеренно: так все 21 язык выходят быстро и честно, а затем сообществу предлагается доработать наиболее важные страницы. Машинный перевод передаёт смысл; проверка человеком делает текст естественным. ### Как пайплайн решает, что переводить {#how-the-web-pipeline-decides} Каждая переводимая единица английского источника хешируется, и хеш хранится рядом с её переводом. При каждом запуске пайплайн: - переводит любую единицу, у которой ещё нет перевода, - пропускает любую единицу, чей сохранённый хеш всё ещё совпадает с английским источником, - заново переводит **машинную** единицу, когда её английский источник меняется, - и помечает **доработанную человеком** единицу как `stale` (требует проверки), когда её английский источник меняется, вместо того чтобы перезаписывать вашу работу. ### Доработка веб-перевода через PR {#refining-a-web-translation-by-pr} Вы улучшаете перевод веб-сайта, документации или справочника по API так же, как вы улучшаете локаль приложения: редактируя сгенерированный файл и открывая PR. 1. Найдите сгенерированный перевод для вашего языка: - строки интерфейса веб-сайта: `apps/landing/src/i18n/.json` - страница документации: `apps/docs//**.md` - справочник по API: `apps/api/src/openapi..yaml` 2. Отредактируйте текст. Сохраняйте код, ссылки, `{placeholders}` и любые маркеры `⸤I18N…⸥` в точности как есть; валидатор пайплайна отклоняет перевод, который отбрасывает или переупорядочивает их. 3. Откройте PR. Редактирование единицы переключает её происхождение с `machine` на `human`, так что пайплайн **никогда не перезапишет её** при последующем запуске. Если английский источник позже изменится, ваша единица будет помечена `stale` для проверки, а не молча заменена. Чтобы сообщить об ошибке перевода, не отправляя код, откройте [GitHub Issue](https://github.com/snapotter-hq/SnapOtter/issues) с указанием URL страницы, языка, неверного текста и вашего предлагаемого исправления. ::: tip Пайплайн перевода запускают мейнтейнеры; вам не нужен ключ API, чтобы внести вклад. Просто отредактируйте сгенерированный файл и откройте PR. См. [`scripts/i18n/README.md`](https://github.com/snapotter-hq/SnapOtter/blob/main/scripts/i18n/README.md), где описано, как работает пайплайн. :::