mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
235 lines
15 KiB
Markdown
235 lines
15 KiB
Markdown
---
|
||||
|
|
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 (
|
|||
|
|
<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>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Как внести перевод {#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/<your-username>/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/<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 {#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/<locale>.json`
|
|||
|
|
- страница документации: `apps/docs/<locale>/**.md`
|
|||
|
|
- справочник по API: `apps/api/src/openapi.<locale>.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),
|
|||
|
|
где описано, как работает пайплайн.
|
|||
|
|
:::
|