Skip to content

Commit cbd2620

Browse files
authored
feat(i18n): add Ukrainian (uk) locale support (#28061)
1 parent 3bd3047 commit cbd2620

12 files changed

Lines changed: 1970 additions & 1 deletion

File tree

packages/app/src/context/language.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export type Locale =
1818
| "ja"
1919
| "pl"
2020
| "ru"
21+
| "uk"
2122
| "ar"
2223
| "no"
2324
| "br"
@@ -45,6 +46,7 @@ const LOCALES: readonly Locale[] = [
4546
"ja",
4647
"pl",
4748
"ru",
49+
"uk",
4850
"bs",
4951
"ar",
5052
"no",
@@ -65,6 +67,7 @@ const INTL: Record<Locale, string> = {
6567
ja: "ja",
6668
pl: "pl",
6769
ru: "ru",
70+
uk: "uk",
6871
ar: "ar",
6972
no: "nb-NO",
7073
br: "pt-BR",
@@ -85,6 +88,7 @@ const LABEL_KEY: Record<Locale, keyof Dictionary> = {
8588
ja: "language.ja",
8689
pl: "language.pl",
8790
ru: "language.ru",
91+
uk: "language.uk",
8892
ar: "language.ar",
8993
no: "language.no",
9094
br: "language.br",
@@ -110,6 +114,7 @@ const loaders: Record<Exclude<Locale, "en">, () => Promise<Dictionary>> = {
110114
ja: () => merge(import("@/i18n/ja"), import("@opencode-ai/ui/i18n/ja")),
111115
pl: () => merge(import("@/i18n/pl"), import("@opencode-ai/ui/i18n/pl")),
112116
ru: () => merge(import("@/i18n/ru"), import("@opencode-ai/ui/i18n/ru")),
117+
uk: () => merge(import("@/i18n/uk"), import("@opencode-ai/ui/i18n/uk")),
113118
ar: () => merge(import("@/i18n/ar"), import("@opencode-ai/ui/i18n/ar")),
114119
no: () => merge(import("@/i18n/no"), import("@opencode-ai/ui/i18n/no")),
115120
br: () => merge(import("@/i18n/br"), import("@opencode-ai/ui/i18n/br")),
@@ -145,6 +150,7 @@ const localeMatchers: Array<{ locale: Locale; match: (language: string) => boole
145150
{ locale: "ja", match: (language) => language.startsWith("ja") },
146151
{ locale: "pl", match: (language) => language.startsWith("pl") },
147152
{ locale: "ru", match: (language) => language.startsWith("ru") },
153+
{ locale: "uk", match: (language) => language.startsWith("uk") },
148154
{ locale: "ar", match: (language) => language.startsWith("ar") },
149155
{
150156
locale: "no",

packages/app/src/i18n/en.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,7 @@ export const dict = {
416416
"language.no": "Norsk",
417417
"language.br": "Português (Brasil)",
418418
"language.bs": "Bosanski",
419+
"language.uk": "Українська",
419420
"language.th": "ไทย",
420421
"language.tr": "Türkçe",
421422

packages/app/src/i18n/parity.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,13 @@ import { dict as ko } from "./ko"
1212
import { dict as no } from "./no"
1313
import { dict as pl } from "./pl"
1414
import { dict as ru } from "./ru"
15+
import { dict as uk } from "./uk"
1516
import { dict as th } from "./th"
1617
import { dict as zh } from "./zh"
1718
import { dict as zht } from "./zht"
1819
import { dict as tr } from "./tr"
1920

20-
const locales = [ar, br, bs, da, de, es, fr, ja, ko, no, pl, ru, th, tr, zh, zht]
21+
const locales = [ar, br, bs, da, de, es, fr, ja, ko, no, pl, ru, uk, th, tr, zh, zht]
2122
const keys = ["command.session.previous.unseen", "command.session.next.unseen"] as const
2223

2324
describe("i18n parity", () => {

packages/app/src/i18n/uk.ts

Lines changed: 963 additions & 0 deletions
Large diffs are not rendered by default.

packages/console/app/src/i18n/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { dict as da } from "~/i18n/da"
1111
import { dict as ja } from "~/i18n/ja"
1212
import { dict as pl } from "~/i18n/pl"
1313
import { dict as ru } from "~/i18n/ru"
14+
import { dict as uk } from "~/i18n/uk"
1415
import { dict as ar } from "~/i18n/ar"
1516
import { dict as no } from "~/i18n/no"
1617
import { dict as br } from "~/i18n/br"
@@ -35,6 +36,7 @@ export function i18n(locale: Locale): Dict {
3536
if (locale === "ja") return { ...base, ...ja }
3637
if (locale === "pl") return { ...base, ...pl }
3738
if (locale === "ru") return { ...base, ...ru }
39+
if (locale === "uk") return { ...base, ...uk }
3840
if (locale === "ar") return { ...base, ...ar }
3941
if (locale === "no") return { ...base, ...no }
4042
if (locale === "br") return { ...base, ...br }

packages/console/app/src/i18n/uk.ts

Lines changed: 785 additions & 0 deletions
Large diffs are not rendered by default.

packages/console/app/src/lib/language.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export const LOCALES = [
1111
"ja",
1212
"pl",
1313
"ru",
14+
"uk",
1415
"ar",
1516
"no",
1617
"br",
@@ -41,6 +42,7 @@ const LABEL = {
4142
ja: "日本語",
4243
pl: "Polski",
4344
ru: "Русский",
45+
uk: "Українська",
4446
ar: "العربية",
4547
no: "Norsk",
4648
br: "Português (Brasil)",
@@ -61,6 +63,7 @@ const TAG = {
6163
ja: "ja",
6264
pl: "pl",
6365
ru: "ru",
66+
uk: "uk",
6467
ar: "ar",
6568
no: "no",
6669
br: "pt-BR",
@@ -81,6 +84,7 @@ const DOCS = {
8184
ja: "ja",
8285
pl: "pl",
8386
ru: "ru",
87+
uk: "uk",
8488
ar: "ar",
8589
no: "nb",
8690
br: "pt-br",
@@ -104,6 +108,7 @@ const DOCS_SEGMENT = new Set([
104108
"ru",
105109
"th",
106110
"tr",
111+
"uk",
107112
"zh-cn",
108113
"zh-tw",
109114
])
@@ -124,6 +129,7 @@ const DOCS_LOCALE = {
124129
ru: "ru",
125130
th: "th",
126131
tr: "tr",
132+
uk: "uk",
127133
"zh-cn": "zh",
128134
"zh-tw": "zht",
129135
} as const satisfies Record<string, Locale>
@@ -239,6 +245,7 @@ function match(input: string): Locale | null {
239245
if (value.startsWith("ja")) return "ja"
240246
if (value.startsWith("pl")) return "pl"
241247
if (value.startsWith("ru")) return "ru"
248+
if (value.startsWith("uk")) return "uk"
242249
if (value.startsWith("ar")) return "ar"
243250
if (value.startsWith("tr")) return "tr"
244251
if (value.startsWith("th")) return "th"

packages/desktop/src/renderer/i18n/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { dict as desktopDa } from "./da"
1111
import { dict as desktopJa } from "./ja"
1212
import { dict as desktopPl } from "./pl"
1313
import { dict as desktopRu } from "./ru"
14+
import { dict as desktopUk } from "./uk"
1415
import { dict as desktopAr } from "./ar"
1516
import { dict as desktopNo } from "./no"
1617
import { dict as desktopBr } from "./br"
@@ -27,6 +28,7 @@ import { dict as appDa } from "../../../../app/src/i18n/da"
2728
import { dict as appJa } from "../../../../app/src/i18n/ja"
2829
import { dict as appPl } from "../../../../app/src/i18n/pl"
2930
import { dict as appRu } from "../../../../app/src/i18n/ru"
31+
import { dict as appUk } from "../../../../app/src/i18n/uk"
3032
import { dict as appAr } from "../../../../app/src/i18n/ar"
3133
import { dict as appNo } from "../../../../app/src/i18n/no"
3234
import { dict as appBr } from "../../../../app/src/i18n/br"
@@ -44,6 +46,7 @@ export type Locale =
4446
| "ja"
4547
| "pl"
4648
| "ru"
49+
| "uk"
4750
| "ar"
4851
| "no"
4952
| "br"
@@ -64,6 +67,7 @@ const LOCALES: readonly Locale[] = [
6467
"ja",
6568
"pl",
6669
"ru",
70+
"uk",
6771
"bs",
6872
"ar",
6973
"no",
@@ -89,6 +93,7 @@ function detectLocale(): Locale {
8993
if (language.toLowerCase().startsWith("ja")) return "ja"
9094
if (language.toLowerCase().startsWith("pl")) return "pl"
9195
if (language.toLowerCase().startsWith("ru")) return "ru"
96+
if (language.toLowerCase().startsWith("uk")) return "uk"
9297
if (language.toLowerCase().startsWith("ar")) return "ar"
9398
if (
9499
language.toLowerCase().startsWith("no") ||
@@ -148,6 +153,7 @@ function build(locale: Locale): Dictionary {
148153
if (locale === "ja") return { ...base, ...i18n.flatten(appJa), ...i18n.flatten(desktopJa) }
149154
if (locale === "pl") return { ...base, ...i18n.flatten(appPl), ...i18n.flatten(desktopPl) }
150155
if (locale === "ru") return { ...base, ...i18n.flatten(appRu), ...i18n.flatten(desktopRu) }
156+
if (locale === "uk") return { ...base, ...i18n.flatten(appUk), ...i18n.flatten(desktopUk) }
151157
if (locale === "ar") return { ...base, ...i18n.flatten(appAr), ...i18n.flatten(desktopAr) }
152158
if (locale === "no") return { ...base, ...i18n.flatten(appNo), ...i18n.flatten(desktopNo) }
153159
if (locale === "br") return { ...base, ...i18n.flatten(appBr), ...i18n.flatten(desktopBr) }
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
export const dict = {
2+
"desktop.menu.checkForUpdates": "Перевірити оновлення...",
3+
"desktop.menu.installCli": "Встановити CLI...",
4+
"desktop.menu.reloadWebview": "Перезавантажити Webview",
5+
"desktop.menu.restart": "Перезапустити",
6+
7+
"desktop.dialog.chooseFolder": "Виберіть теку",
8+
"desktop.dialog.chooseFile": "Виберіть файл",
9+
"desktop.dialog.saveFile": "Зберегти файл",
10+
11+
"desktop.updater.checkFailed.title": "Не вдалося перевірити оновлення",
12+
"desktop.updater.checkFailed.message": "Не вдалося перевірити наявність оновлень",
13+
"desktop.updater.none.title": "Немає доступних оновлень",
14+
"desktop.updater.none.message": "Ви вже використовуєте найновішу версію OpenCode",
15+
"desktop.updater.downloadFailed.title": "Помилка оновлення",
16+
"desktop.updater.downloadFailed.message": "Не вдалося завантажити оновлення",
17+
"desktop.updater.downloaded.title": "Оновлення завантажено",
18+
"desktop.updater.downloaded.prompt":
19+
"Версію {{version}} OpenCode завантажено. Бажаєте встановити її та перезапустити?",
20+
"desktop.updater.installFailed.title": "Помилка оновлення",
21+
"desktop.updater.installFailed.message": "Не вдалося встановити оновлення",
22+
23+
"desktop.cli.installed.title": "CLI встановлено",
24+
"desktop.cli.installed.message":
25+
"CLI встановлено до {{path}}\n\nПерезапустіть термінал, щоб використовувати команду 'opencode'.",
26+
"desktop.cli.failed.title": "Не вдалося встановити",
27+
"desktop.cli.failed.message": "Не вдалося встановити CLI: {{error}}",
28+
}

packages/ui/src/i18n/uk.ts

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
export const dict: Record<string, string> = {
2+
"ui.sessionReview.title": "Зміни сесії",
3+
"ui.sessionReview.title.git": "Зміни Git",
4+
"ui.sessionReview.title.branch": "Зміни гілки",
5+
"ui.sessionReview.title.lastTurn": "Зміни останнього кроку",
6+
"ui.sessionReview.diffStyle.unified": "Об'єднаний",
7+
"ui.sessionReview.diffStyle.split": "Розділений",
8+
"ui.sessionReview.expandAll": "Розгорнути все",
9+
"ui.sessionReview.collapseAll": "Згорнути все",
10+
"ui.sessionReview.change.added": "Додано",
11+
"ui.sessionReview.change.removed": "Видалено",
12+
"ui.sessionReview.change.modified": "Змінено",
13+
"ui.sessionReview.image.loading": "Завантаження...",
14+
"ui.sessionReview.image.placeholder": "Зображення",
15+
"ui.sessionReview.largeDiff.title": "Завеликий diff для відображення",
16+
"ui.sessionReview.largeDiff.meta": "Ліміт: {{limit}} змінених рядків. Поточно: {{current}} змінених рядків.",
17+
"ui.sessionReview.largeDiff.renderAnyway": "Все одно відобразити",
18+
"ui.sessionReview.openFile": "Відкрити файл",
19+
"ui.sessionReview.selection.line": "рядок {{line}}",
20+
"ui.sessionReview.selection.lines": "рядки {{start}}-{{end}}",
21+
22+
"ui.fileMedia.kind.image": "зображення",
23+
"ui.fileMedia.kind.audio": "аудіо",
24+
"ui.fileMedia.state.removed": "{{kind}} видалено",
25+
"ui.fileMedia.state.loading": "Завантаження {{kind}}...",
26+
"ui.fileMedia.state.error": "Не вдалося завантажити {{kind}}",
27+
"ui.fileMedia.state.unavailable": "{{kind}} недоступне",
28+
"ui.fileMedia.binary.title": "Бінарний файл",
29+
"ui.fileMedia.binary.description.path": "Неможливо відобразити {{path}}, оскільки це бінарний файл.",
30+
"ui.fileMedia.binary.description.default": "Неможливо відобразити цей файл, оскільки він бінарний.",
31+
32+
"ui.lineComment.label.prefix": "Коментар до ",
33+
"ui.lineComment.label.suffix": "",
34+
"ui.lineComment.editorLabel.prefix": "Коментування: ",
35+
"ui.lineComment.editorLabel.suffix": "",
36+
"ui.lineComment.placeholder": "Додати коментар",
37+
"ui.lineComment.submit": "Коментувати",
38+
39+
"ui.sessionTurn.steps.show": "Показати кроки",
40+
"ui.sessionTurn.steps.hide": "Приховати кроки",
41+
"ui.sessionTurn.summary.response": "Відповідь",
42+
"ui.sessionTurn.diff.showMore": "Показати більше змін ({{count}})",
43+
"ui.sessionTurn.diffs.changed": "Змінено",
44+
"ui.sessionTurn.diffs.showAll": "Показати всі",
45+
"ui.sessionTurn.diffs.showLess": "Показати менше",
46+
"ui.sessionTurn.diffs.more": "+{{count}} інших файлів",
47+
48+
"ui.sessionTurn.retry.retrying": "повтор",
49+
"ui.sessionTurn.retry.inSeconds": "за {{seconds}}с",
50+
"ui.sessionTurn.retry.attempt": "спроба №{{attempt}}",
51+
"ui.sessionTurn.retry.attemptLine": "{{line}} — спроба №{{attempt}}",
52+
"ui.sessionTurn.retry.geminiHot": "gemini зараз перевантажений",
53+
"ui.sessionTurn.error.freeUsageExceeded": "Перевищено ліміт безкоштовного використання",
54+
"ui.sessionTurn.error.addCredits": "Додати кредити",
55+
56+
"ui.sessionTurn.status.delegating": "Делегування роботи",
57+
"ui.sessionTurn.status.planning": "Планування наступних кроків",
58+
"ui.sessionTurn.status.gatheringContext": "Дослідження",
59+
"ui.sessionTurn.status.gatheredContext": "Досліджено",
60+
"ui.sessionTurn.status.searchingCodebase": "Пошук у кодовій базі",
61+
"ui.sessionTurn.status.searchingWeb": "Пошук в інтернеті",
62+
"ui.sessionTurn.status.makingEdits": "Внесення змін",
63+
"ui.sessionTurn.status.runningCommands": "Виконання команд",
64+
"ui.sessionTurn.status.thinking": "Міркування",
65+
"ui.sessionTurn.status.thinkingWithTopic": "Міркування — {{topic}}",
66+
"ui.sessionTurn.status.gatheringThoughts": "Збирання думок",
67+
"ui.sessionTurn.status.consideringNextSteps": "Розгляд наступних кроків",
68+
69+
"ui.messagePart.diagnostic.error": "Помилка",
70+
"ui.messagePart.title.edit": "Редагувати",
71+
"ui.messagePart.title.write": "Написати",
72+
"ui.messagePart.option.typeOwnAnswer": "Введіть власну відповідь",
73+
"ui.messagePart.review.title": "Перевірте свої відповіді",
74+
"ui.messagePart.questions.dismissed": "Питання відхилено",
75+
"ui.messagePart.compaction": "Сесію стиснуто",
76+
"ui.messagePart.context.read.one": "{{count}} читання",
77+
"ui.messagePart.context.read.other": "{{count}} читань",
78+
"ui.messagePart.context.search.one": "{{count}} пошук",
79+
"ui.messagePart.context.search.other": "{{count}} пошуків",
80+
"ui.messagePart.context.list.one": "{{count}} список",
81+
"ui.messagePart.context.list.other": "{{count}} списків",
82+
83+
"ui.list.loading": "Завантаження",
84+
"ui.list.empty": "Немає результатів",
85+
"ui.list.clearFilter": "Очистити фільтр",
86+
"ui.list.emptyWithFilter.prefix": "Немає результатів для",
87+
"ui.list.emptyWithFilter.suffix": "",
88+
89+
"ui.fileSearch.placeholder": "Знайти",
90+
"ui.fileSearch.previousMatch": "Попередній збіг",
91+
"ui.fileSearch.nextMatch": "Наступний збіг",
92+
"ui.fileSearch.close": "Закрити пошук",
93+
94+
"ui.messageNav.newMessage": "Нове повідомлення",
95+
96+
"ui.textField.copyToClipboard": "Копіювати в буфер обміну",
97+
"ui.textField.copyLink": "Копіювати посилання",
98+
"ui.textField.copied": "Скопійовано",
99+
100+
"ui.imagePreview.alt": "Попередній перегляд зображення",
101+
"ui.scrollView.ariaLabel": "контент для прокручування",
102+
103+
"ui.tool.read": "Читання",
104+
"ui.tool.loaded": "Завантажено",
105+
"ui.tool.list": "Список",
106+
"ui.tool.glob": "Glob",
107+
"ui.tool.grep": "Grep",
108+
"ui.tool.task": "Завдання",
109+
"ui.tool.webfetch": "Веб-отримання",
110+
"ui.tool.websearch": "Веб-пошук",
111+
"ui.tool.shell": "Оболонка",
112+
"ui.tool.patch": "Патч",
113+
"ui.tool.todos": "Завдання",
114+
"ui.tool.todos.read": "Читати завдання",
115+
"ui.tool.questions": "Питання",
116+
"ui.tool.agent": "Агент {{type}}",
117+
"ui.tool.agent.default": "Агент",
118+
"ui.tool.skill": "Навичка",
119+
120+
"ui.basicTool.called": "Викликано `{{tool}}`",
121+
"ui.toolErrorCard.failed": "Помилка",
122+
"ui.toolErrorCard.copyError": "Копіювати помилку",
123+
124+
"ui.common.file.one": "файл",
125+
"ui.common.file.other": "файлів",
126+
"ui.common.question.one": "питання",
127+
"ui.common.question.other": "питань",
128+
129+
"ui.common.add": "Додати",
130+
"ui.common.back": "Назад",
131+
"ui.common.cancel": "Скасувати",
132+
"ui.common.confirm": "Підтвердити",
133+
"ui.common.dismiss": "Відхилити",
134+
"ui.common.close": "Закрити",
135+
"ui.common.next": "Далі",
136+
"ui.common.submit": "Надіслати",
137+
138+
"ui.permission.deny": "Заборонити",
139+
"ui.permission.allowAlways": "Дозволяти завжди",
140+
"ui.permission.allowOnce": "Дозволити один раз",
141+
142+
"ui.message.expand": "Розгорнути повідомлення",
143+
"ui.message.collapse": "Згорнути повідомлення",
144+
"ui.message.copy": "Копіювати",
145+
"ui.message.copyMessage": "Копіювати повідомлення",
146+
"ui.message.forkMessage": "Відгалузити в нову сесію",
147+
"ui.message.revertMessage": "Скинути до цього моменту",
148+
"ui.message.copyResponse": "Копіювати відповідь",
149+
"ui.message.copied": "Скопійовано",
150+
"ui.message.duration.seconds": "{{count}}с",
151+
"ui.message.duration.minutesSeconds": "{{minutes}}хв {{seconds}}с",
152+
"ui.message.interrupted": "Перервано",
153+
"ui.message.queued": "У черзі",
154+
"ui.message.attachment.alt": "вкладення",
155+
156+
"ui.patch.action.deleted": "Видалено",
157+
"ui.patch.action.created": "Створено",
158+
"ui.patch.action.moved": "Переміщено",
159+
"ui.patch.action.patched": "Пропатчено",
160+
161+
"ui.question.subtitle.answered": "{{count}} відповідей",
162+
"ui.question.answer.none": "(немає відповіді)",
163+
"ui.question.review.notAnswered": "(не відповіли)",
164+
"ui.question.multiHint": "Виберіть усі відповідні варіанти",
165+
"ui.question.singleHint": "Виберіть одну відповідь",
166+
"ui.question.custom.placeholder": "Введіть свою відповідь...",
167+
}

0 commit comments

Comments
 (0)