Search K
Руководство по переводам
SnapOtter поставляется с 21 языком из коробки. Система i18n использует лёгкое собственное среда выполнения с полнотой локалей, контролируемой TypeScript, и динамическим разбиением кода.
Поддерживаемые языки
| 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 |
Как работает определение языка
SnapOtter использует трёхуровневый порядок разрешения:
- Предпочтение пользователя — хранится в
localStorage("snapotter-locale")и синхронизируется с настройками пользователя при авторизации - Автоопределение браузера — проходит по массиву
navigator.languagesс сопоставлением префиксов BCP 47 - Умолчание экземпляра — переменная окружения
DEFAULT_LOCALEадминистратора (запрашивается изGET /api/v1/config/locale) - Запасной вариант — английский — всегда доступен
Пользователи могут менять язык из:
- Селектора-глобуса в подвале (десктоп, всегда виден)
- Селектора языка на странице входа (до авторизации)
- Раздела Настройки > Общие (предпочтение конкретного пользователя)
- Выпадающего списка языков в мобильной боковой панели
- Раздел Настройки > Система задаёт умолчание для всего экземпляра (только администратор)
Как работают переводы
Все строки интерфейса находятся в 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 install2. Скопируйте эталонный файл (только для нового языка)
Пропустите этот шаг, если вы улучшаете существующий перевод.
bash
cp packages/shared/src/i18n/en.ts packages/shared/src/i18n/XX.ts3. Переведите строки
Откройте свой новый файл и переведите каждое строковое значение. Сохраняйте структуру объекта и ключи в точности такими же.
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 correctly6. Отправьте
Откройте PR против main с заголовком вида feat(i18n): add Swedish translation или fix(i18n): correct German typos. Бот CLA попросит вас подписать соглашение при первом вкладе.
Добавление новых ключей перевода
При добавлении новой функции, которой нужны новые строки интерфейса:
- Сначала добавьте новые ключи в
en.ts(эталонный файл) - Запустите
pnpm typecheck— каждый файл локали упадёт, если в нём нет нового ключа - Добавьте новый ключ во все файлы локалей (используйте английский как временный запасной вариант)
Конфигурация
Задайте язык экземпляра по умолчанию через переменную окружения:
yaml
DEFAULT_LOCALE: "de" # German as the default for all new usersСправочник по файлам
| File | Purpose |
|---|---|
packages/shared/src/i18n/en.ts | Английские строки (эталонная локаль, ~1500 ключей) |
packages/shared/src/i18n/index.ts | SUPPORTED_LOCALES, loadTranslations(), экспорт типов |
packages/shared/src/i18n/<locale>.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
Поддержка 21 языка выше относится к приложению. Публичный веб-сайт (snapotter.com), этот сайт документации и справочник по REST API также переведены на все 21 язык отдельным пайплайном с хеш-контролем, который повторно использует те же названия и описания инструментов из packages/shared/src/i18n, чтобы терминология оставалась согласованной везде.
Машинный перевод по умолчанию
Каждая неанглийская страница на веб-сайте и в документации переводится машинно при первом проходе (сессией Claude Code, а не сторонним сервисом) и несёт небольшой закрываемый баннер, сообщающий об этом, со ссылкой обратно сюда. Это сделано намеренно: так все 21 язык выходят быстро и честно, а затем сообществу предлагается доработать наиболее важные страницы. Машинный перевод передаёт смысл; проверка человеком делает текст естественным.
Как пайплайн решает, что переводить
Каждая переводимая единица английского источника хешируется, и хеш хранится рядом с её переводом. При каждом запуске пайплайн:
- переводит любую единицу, у которой ещё нет перевода,
- пропускает любую единицу, чей сохранённый хеш всё ещё совпадает с английским источником,
- заново переводит машинную единицу, когда её английский источник меняется,
- и помечает доработанную человеком единицу как
stale(требует проверки), когда её английский источник меняется, вместо того чтобы перезаписывать вашу работу.
Доработка веб-перевода через PR
Вы улучшаете перевод веб-сайта, документации или справочника по API так же, как вы улучшаете локаль приложения: редактируя сгенерированный файл и открывая PR.
- Найдите сгенерированный перевод для вашего языка:
- строки интерфейса веб-сайта:
apps/landing/src/i18n/<locale>.json - страница документации:
apps/docs/<locale>/**.md - справочник по API:
apps/api/src/openapi.<locale>.yaml
- строки интерфейса веб-сайта:
- Отредактируйте текст. Сохраняйте код, ссылки,
{placeholders}и любые маркеры⸤I18N…⸥в точности как есть; валидатор пайплайна отклоняет перевод, который отбрасывает или переупорядочивает их. - Откройте PR. Редактирование единицы переключает её происхождение с
machineнаhuman, так что пайплайн никогда не перезапишет её при последующем запуске. Если английский источник позже изменится, ваша единица будет помеченаstaleдля проверки, а не молча заменена.
Чтобы сообщить об ошибке перевода, не отправляя код, откройте GitHub Issue с указанием URL страницы, языка, неверного текста и вашего предлагаемого исправления.
TIP
Пайплайн перевода запускают мейнтейнеры; вам не нужен ключ API, чтобы внести вклад. Просто отредактируйте сгенерированный файл и откройте PR. См. scripts/i18n/README.md, где описано, как работает пайплайн.
