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: 8795ae2aa060
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
# Посібник з перекладу {#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. **Резервна англійська** - завжди доступна
|
|||
|
|
|
|||
|
|
Користувачі можуть змінити мову з:
|
|||
|
|
- **Селектора-глобуса у футері** (десктоп, завжди видимий)
|
|||
|
|
- Селектора мови на **сторінці входу** (до автентифікації)
|
|||
|
|
- Розділу **Settings > General** (уподобання окремого користувача)
|
|||
|
|
- Випадаючого списку мов у **мобільній бічній панелі**
|
|||
|
|
- Розділ **Settings > System** задає стандартне значення для всього інстансу (лише для адміністратора)
|
|||
|
|
|
|||
|
|
## Як працюють переклади {#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. Дивіться [Посібник для контриб'юторів](/uk/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)
|
|||
|
|
щодо того, як працює конвеєр.
|
|||
|
|
:::
|