feat(app): add Hebrew locale (#42475)

Co-authored-by: Luke Parker <10430890+Hona@users.noreply.github.com>
This commit is contained in:
opencode-agent[bot]
2026-08-14 12:31:48 +10:00
committed by GitHub
parent 71caa7d406
commit 979ac810af
10 changed files with 1515 additions and 1 deletions
+48
View File
@@ -0,0 +1,48 @@
# he Glossary
## Sources
- Hebrew Academy approved IT terminology: https://terms.hebrew-academy.org.il/Millonim/ShowMillon?KodMillon=192
- Firefox Hebrew localization corpus: https://github.com/mozilla-l10n/firefox-l10n/tree/main/he
- KDE Hebrew localization team and corpus: https://l10n.kde.org/team-infos.php?teamcode=he
- Community-maintained VS Code Hebrew language pack: https://github.com/AMAARETS/vscode-language-pack-he
- Microsoft Hebrew developer documentation for Git terminology: https://learn.microsoft.com/he-il/power-platform/alm/tutorials/github-actions-deploy
- W3C guidance for bidirectional text: https://www.w3.org/International/articles/strings-and-bidi/
## Do Not Translate (Locale Additions)
- `OpenCode` (preserve casing in prose and UI copy)
- `API`, `MCP`, `LSP`, `OAuth`, `Git`, model names, and provider names
- Commands, flags, keyboard shortcuts, file paths, URLs, identifiers, hashes, and code literals
- Keep `commit` and `diff` when they name the exact Git artifact or operation
## Preferred Terms
| English / Context | Preferred | Notes |
| ----------------- | ------------- | ------------------------------------------------------------------------- |
| session | `הפעלה` | Use `שיחה` only when the source specifically means a chat or conversation |
| workspace | `סביבת עבודה` | |
| terminal | `מסוף` | Prefer the established Hebrew term over transliteration |
| command | `פקודה` | |
| provider | `ספק` | Use `ספק מודלים` where the bare noun is ambiguous |
| model | `מודל` | |
| API key | `מפתח API` | Keep the acronym in Latin letters |
| plugin | `תוסף` | |
| repository | `מאגר` | Use `מאגר Git` where context is ambiguous |
| branch | `ענף` | |
| context | `הקשר` | Use `חלון הקשר` for context window |
| tokens | `אסימונים` | |
## Guidance
- Prefer natural modern Israeli Hebrew over word-for-word translation or obscure coined terms.
- Use short action verbs for controls and translate complete phrases in context.
- Keep recognized developer acronyms and exact Git vocabulary in Latin script instead of phonetic transliteration.
- Treat embedded code, paths, commands, shortcuts, hashes, model IDs, and other Latin technical artifacts as LTR content inside the RTL interface.
- Keep recurring concepts consistent and do not collapse session, chat, run, and launch into one Hebrew term.
## Avoid
- Avoid transliterations such as `טרמינל`, `פלאגין`, and `קומנד` when `מסוף`, `תוסף`, and `פקודה` are clear.
- Avoid translating `commit` as `התחייבות`.
- Avoid inventing Hebrew expansions for `API`, `MCP`, or `LSP`.
+2 -1
View File
@@ -25,7 +25,7 @@ import {
export type Locale = DesktopNativeLocale
export type Direction = "ltr" | "rtl"
const RTL_LOCALES: ReadonlySet<Locale> = new Set(["ar", "ur", "pa", "fa", "dv"])
const RTL_LOCALES: ReadonlySet<Locale> = new Set(["ar", "he", "ur", "pa", "fa", "dv"])
function localeDirection(locale: Locale): Direction {
return RTL_LOCALES.has(locale) ? "rtl" : "ltr"
@@ -73,6 +73,7 @@ const loaders: Record<Exclude<Locale, "en">, () => Promise<Dictionary>> = {
ru: () => merge(import("@/i18n/ru"), import("@opencode-ai/ui/i18n/ru")),
uk: () => merge(import("@/i18n/uk"), import("@opencode-ai/ui/i18n/uk")),
ar: () => merge(import("@/i18n/ar"), import("@opencode-ai/ui/i18n/ar")),
he: () => merge(import("@/i18n/he"), import("@opencode-ai/ui/i18n/he")),
no: () => merge(import("@/i18n/no"), import("@opencode-ai/ui/i18n/no")),
br: () => merge(import("@/i18n/br"), import("@opencode-ai/ui/i18n/br")),
th: () => merge(import("@/i18n/th"), import("@opencode-ai/ui/i18n/th")),
@@ -29,6 +29,7 @@ describe("desktop native translations", () => {
"Українська",
"Bosanski",
"العربية",
"עברית",
"Norsk",
"Português (Brasil)",
"ไทย",
@@ -133,6 +134,11 @@ describe("desktop native locale detection", () => {
expect(detectDesktopNativeLocale(["nb-NO"])).toBe("no")
expect(detectDesktopNativeLocale(["nn-NO"])).toBe("no")
})
test("recognizes Hebrew language tags", () => {
expect(detectDesktopNativeLocale(["he"])).toBe("he")
expect(detectDesktopNativeLocale(["he-IL"])).toBe("he")
})
})
describe("desktop native ICU data", () => {
+3
View File
@@ -13,6 +13,7 @@ export const DESKTOP_NATIVE_LOCALES = [
"uk",
"bs",
"ar",
"he",
"no",
"br",
"th",
@@ -80,6 +81,7 @@ export const DESKTOP_NATIVE_LABELS: Record<DesktopNativeLocale, string> = {
uk: "Українська",
bs: "Bosanski",
ar: "العربية",
he: "עברית",
no: "Norsk",
br: "Português (Brasil)",
th: "ไทย",
@@ -145,6 +147,7 @@ export const DESKTOP_NATIVE_LOCALE_TAGS: Record<DesktopNativeLocale, string> = {
uk: "uk",
bs: "bs",
ar: "ar",
he: "he-IL",
no: "nb-NO",
br: "pt-BR",
th: "th",
File diff suppressed because it is too large Load Diff
+20
View File
@@ -0,0 +1,20 @@
export const dict = {
"desktop.menu.checkForUpdates": "בדוק אם יש עדכונים...",
"desktop.menu.reloadWebview": "טען מחדש את תצוגת האינטרנט",
"desktop.menu.restart": "הפעל מחדש",
"desktop.dialog.chooseFolder": "בחירת תיקייה",
"desktop.dialog.chooseFile": "בחירת קובץ",
"desktop.dialog.saveFile": "שמירת קובץ",
"desktop.updater.checkFailed.title": "בדיקת העדכונים נכשלה",
"desktop.updater.checkFailed.message": "לא ניתן לבדוק אם קיימים עדכונים",
"desktop.updater.none.title": "אין עדכון זמין",
"desktop.updater.none.message": "כבר מותקנת הגרסה העדכנית ביותר של OpenCode",
"desktop.updater.downloadFailed.title": "העדכון נכשל",
"desktop.updater.downloadFailed.message": "הורדת העדכון נכשלה",
"desktop.updater.downloaded.title": "העדכון הורד",
"desktop.updater.downloaded.prompt": "גרסה {{version}} של OpenCode הורדה. להתקין אותה ולהפעיל מחדש את היישום?",
"desktop.updater.installFailed.title": "העדכון נכשל",
"desktop.updater.installFailed.message": "התקנת העדכון נכשלה",
"desktop.error.dev.rootNotFound":
"רכיב השורש לא נמצא. האם שכחת להוסיף אותו ל-index.html, או שיש טעות בשם מאפיין ה-id?",
}
@@ -18,6 +18,7 @@ import { dict as desktopPl } from "./pl"
import { dict as desktopRu } from "./ru"
import { dict as desktopUk } from "./uk"
import { dict as desktopAr } from "./ar"
import { dict as desktopHe } from "./he"
import { dict as desktopNo } from "./no"
import { dict as desktopBr } from "./br"
import { dict as desktopBs } from "./bs"
@@ -126,6 +127,7 @@ function build(locale: Locale): Dictionary {
if (locale === "ru") return { ...base, ...i18n.flatten(desktopRu) }
if (locale === "uk") return { ...base, ...i18n.flatten(desktopUk) }
if (locale === "ar") return { ...base, ...i18n.flatten(desktopAr) }
if (locale === "he") return { ...base, ...i18n.flatten(desktopHe) }
if (locale === "no") return { ...base, ...i18n.flatten(desktopNo) }
if (locale === "br") return { ...base, ...i18n.flatten(desktopBr) }
if (locale === "bs") return { ...base, ...i18n.flatten(desktopBs) }
+199
View File
@@ -0,0 +1,199 @@
export const dict = {
"ui.sessionReview.title": "שינויים בהפעלה",
"ui.sessionReview.title.git": "שינויים ב-Git",
"ui.sessionReview.title.branch": "שינויים בענף",
"ui.sessionReview.title.lastTurn": "שינויים בתור האחרון",
"ui.sessionReview.diffStyle.unified": "מאוחד",
"ui.sessionReview.diffStyle.split": "מפוצל",
"ui.sessionReview.expandAll": "הרחב הכל",
"ui.sessionReview.collapseAll": "כווץ הכל",
"ui.sessionReview.change.added": "נוסף",
"ui.sessionReview.change.removed": "הוסר",
"ui.sessionReview.change.modified": "שונה",
"ui.sessionReview.image.loading": "טוען...",
"ui.sessionReview.image.placeholder": "תמונה",
"ui.sessionReview.largeDiff.title": "ה-diff גדול מדי להצגה",
"ui.sessionReview.largeDiff.meta": "המגבלה היא {{limit}} שורות שהשתנו. כעת יש {{current}} שורות שהשתנו.",
"ui.sessionReview.largeDiff.renderAnyway": "הצג בכל זאת",
"ui.sessionReviewV2.expandMode": "הרחבה או כיווץ של ה-diff",
"ui.sessionReviewV2.filterFiles": "סינון קבצים",
"ui.sessionReviewV2.toggleSidebar": "הצגה או הסתרה של עץ הקבצים",
"ui.sessionReviewV2.showAllLines": "הצג את כל השורות",
"ui.sessionReviewV2.hideNonDiffLines": "הסתרת שורות שאינן חלק מה-diff",
"ui.sessionReviewV2.unifiedDiff": "diff מאוחד",
"ui.sessionReviewV2.splitDiff": "diff מפוצל",
"ui.sessionReviewV2.previousFile": "הקובץ הקודם",
"ui.sessionReviewV2.nextFile": "הקובץ הבא",
"ui.sessionReviewV2.diffView": "תצוגת diff",
"ui.sessionReviewV2.empty.noGit.title": "אין שינויים במעקב",
"ui.sessionReviewV2.empty.noGit.description": "עקוב, סקור ובטל שינויים בפרויקט הזה",
"ui.sessionReviewV2.empty.noGit.action": "צור מאגר Git",
"ui.sessionReviewV2.empty.noGit.actionLoading": "יוצר מאגר Git...",
"ui.sessionReviewV2.empty.changes.title": "אין עדיין שינויים בקובץ",
"ui.sessionReviewV2.empty.changes.description": "שינויים בפרויקט יופיעו כאן",
"ui.sessionReview.openFile": "פתיחת קובץ",
"ui.sessionReview.selection.line": "שורה {{line}}",
"ui.sessionReview.selection.lines": "שורות {{start}}-{{end}}",
"ui.fileMedia.kind.image": "תמונה",
"ui.fileMedia.kind.audio": "אודיו",
"ui.fileMedia.state.removed": "הוסר קובץ {{kind}}.",
"ui.fileMedia.state.loading": "טוען {{kind}}...",
"ui.fileMedia.state.error": "לא ניתן לטעון {{kind}}.",
"ui.fileMedia.state.unavailable": "תצוגה מקדימה של {{kind}} אינה זמינה.",
"ui.fileMedia.binary.title": "קובץ בינארי",
"ui.fileMedia.binary.description.path": "{{path}} הוא קובץ בינארי.",
"ui.fileMedia.binary.description.default": "תוכן בינארי",
"ui.lineComment.label.prefix": "תגובה על ",
"ui.lineComment.label.suffix": "",
"ui.lineComment.editorLabel.prefix": "תגובה על ",
"ui.lineComment.editorLabel.suffix": "",
"ui.lineComment.placeholder": "הוסף תגובה",
"ui.lineComment.contextPlaceholder": "הוסף הקשר לשינוי זה",
"ui.lineComment.submit": "שלח תגובה",
"ui.lineComment.cancel": "בטל",
"ui.sessionTurn.steps.show": "הצג צעדים",
"ui.sessionTurn.steps.hide": "הסתר צעדים",
"ui.sessionTurn.summary.response": "תגובה",
"ui.sessionTurn.diff.showMore": "הצג שינויים נוספים ({{count}})",
"ui.sessionTurn.diffs.changed.one": "{{count}} קובץ השתנה",
"ui.sessionTurn.diffs.changed.other": "{{count}} קבצים השתנו",
"ui.sessionTurn.diffs.showAll": "הצג הכל",
"ui.sessionTurn.diffs.showLess": "הצג פחות",
"ui.sessionTurn.diffs.more": "+{{count}} קבצים נוספים",
"ui.sessionTurn.retry.retrying": "מנסה שוב",
"ui.sessionTurn.retry.inSeconds": "בעוד {{seconds}} שניות",
"ui.sessionTurn.retry.attempt": "ניסיון #{{attempt}}",
"ui.sessionTurn.retry.attemptLine": "{{line}} - ניסיון #{{attempt}}",
"ui.sessionTurn.retry.geminiHot": "העומס על Gemini גבוה מדי כרגע",
"ui.sessionTurn.error.freeUsageExceeded": "חריגה מהשימוש בחינם",
"ui.sessionTurn.error.addCredits": "הוסף קרדיטים",
"dialog.usageExceeded.freeTier.title": "הגעת למגבלה החינמית",
"dialog.usageExceeded.freeTier.description":
"הירשם ל-OpenCode Go לקבלת גישה אמינה למודלי הקוד הפתוח הטובים ביותר, החל מ-$5 לחודש.",
"dialog.usageExceeded.freeTier.actionLabel": "הירשם",
"dialog.usageExceeded.accountRateLimit.title": "הגעת למגבלת Go",
"dialog.usageExceeded.accountRateLimit.description":
"הגעת למגבלת השימוש. כדי להמשיך להשתמש במודל זה כעת, הפעל שימוש מהיתרה הזמינה שלך",
"dialog.usageExceeded.accountRateLimit.actionLabel": "פתח את ההגדרות",
"ui.sessionTurn.status.delegating": "מעביר את העבודה לסוכן אחר",
"ui.sessionTurn.status.planning": "תכנון השלבים הבאים",
"ui.sessionTurn.status.gatheringContext": "בודק את הפרויקט",
"ui.sessionTurn.status.gatheredContext": "בדיקת הפרויקט הסתיימה",
"ui.sessionTurn.status.searchingCodebase": "חיפוש בבסיס הקוד",
"ui.sessionTurn.status.searchingWeb": "חיפוש באינטרנט",
"ui.sessionTurn.status.makingEdits": "ביצוע עריכות",
"ui.sessionTurn.status.runningCommands": "הפעלת פקודות",
"ui.sessionTurn.status.thinking": "חושב",
"ui.sessionTurn.status.thinkingWithTopic": "חשיבה - {{topic}}",
"ui.sessionTurn.status.gatheringThoughts": "מגבש מחשבות",
"ui.sessionTurn.status.consideringNextSteps": "בוחן את הצעדים הבאים",
"ui.messagePart.diagnostic.error": "שגיאה",
"ui.messagePart.title.edit": "ערוך",
"ui.messagePart.title.write": "כתוב",
"ui.messagePart.option.typeOwnAnswer": "הקלד את התשובה שלך",
"ui.messagePart.review.title": "בדיקת התשובות",
"ui.messagePart.questions.dismissed": "השאלות נדחו",
"ui.messagePart.compaction": "ההפעלה נדחסה",
"ui.messagePart.context.read.one": "{{count}} קריאה",
"ui.messagePart.context.read.other": "{{count}} קריאות",
"ui.messagePart.context.search.one": "{{count}} חיפוש",
"ui.messagePart.context.search.other": "{{count}} חיפושים",
"ui.messagePart.context.list.one": "{{count}} הצגה",
"ui.messagePart.context.list.other": "{{count}} הצגות",
"ui.list.loading": "טוען",
"ui.list.empty": "אין תוצאות",
"ui.list.clearFilter": "נקה מסנן",
"ui.list.emptyWithFilter.prefix": "אין תוצאות עבור",
"ui.list.emptyWithFilter.suffix": "",
"ui.fileSearch.placeholder": "מצא",
"ui.fileSearch.previousMatch": "התוצאה הקודמת",
"ui.fileSearch.nextMatch": "התוצאה הבאה",
"ui.fileSearch.close": "סגור חיפוש",
"ui.messageNav.newMessage": "הודעה חדשה",
"ui.promptInput.noMatchingItems": "אין פריטים תואמים",
"ui.promptInput.commands": "פקודות",
"ui.promptInput.dropFiles": "שחרר קבצים לצירוף",
"ui.promptInput.removeAttachment": "הסר את הקובץ המצורף",
"ui.promptInput.label": "פרומפט",
"ui.promptInput.placeholder.shell": "הזן פקודת מעטפת...",
"ui.promptInput.placeholder.normal": "שאל כל דבר, {{slash}} עבור פקודות, {{at}} עבור הקשר...",
"ui.promptInput.add": "הוסף תמונות וקבצים",
"ui.promptInput.attachments": "תמונות וקבצים",
"ui.promptInput.context": "הקשר",
"ui.promptInput.shell": "פקודת מעטפת",
"ui.promptInput.chooseAgent": "בחר סוכן",
"ui.promptInput.chooseModel": "בחר מודל",
"ui.promptInput.chooseVariant": "בחר גרסה של מודל",
"ui.promptInput.send": "שלח",
"ui.promptInput.stop": "עצור",
"ui.tabs.close": "סגור כרטיסייה",
"ui.textField.copyToClipboard": "העתק ללוח",
"ui.textField.copyLink": "העתק קישור",
"ui.textField.copied": "הועתק",
"ui.imagePreview.alt": "תצוגה מקדימה של תמונה",
"ui.scrollView.ariaLabel": "תוכן שניתן לגלול",
"ui.tool.read": "קרא",
"ui.tool.loaded": "טעון",
"ui.tool.list": "רשימה",
"ui.tool.glob": "Glob",
"ui.tool.grep": "Grep",
"ui.tool.task": "משימה",
"ui.tool.webfetch": "Webfetch",
"ui.tool.websearch": "Web Search",
"ui.tool.websearch.provider": "{{provider}} Web Search",
"ui.tool.shell": "Shell",
"ui.tool.patch": "Patch",
"ui.tool.questions": "שאלות",
"ui.tool.questions.numbered": "שאלות {{number}}",
"ui.tool.agent": "סוכן {{type}}",
"ui.tool.agent.default": "סוכן",
"ui.tool.skill": "מיומנות",
"ui.basicTool.called": "בוצעה קריאה אל `{{tool}}`",
"ui.toolErrorCard.failed": "נכשל",
"ui.toolErrorCard.copyError": "שגיאת העתקה",
"ui.common.file.one": "קובץ",
"ui.common.file.other": "קבצים",
"ui.common.question.one": "שאלה",
"ui.common.question.other": "שאלות",
"ui.common.add": "הוסף",
"ui.common.clear": "נקה",
"ui.common.file": "קובץ",
"ui.common.back": "חזרה",
"ui.common.cancel": "בטל",
"ui.common.confirm": "אשר",
"ui.common.dismiss": "סגור",
"ui.common.close": "סגור",
"ui.common.next": "הבא",
"ui.common.submit": "שלח",
"ui.common.showMore": "הצג עוד",
"ui.permission.deny": "דחה",
"ui.permission.allowAlways": "אפשר תמיד",
"ui.permission.allowOnce": "אפשר פעם אחת",
"ui.message.expand": "הרחב את ההודעה",
"ui.message.collapse": "כווץ הודעה",
"ui.message.copy": "העתק",
"ui.message.copyMessage": "העתק הודעה",
"ui.message.forkMessage": "יצירת הפעלה חדשה מכאן",
"ui.message.revertMessage": "ביטול ההודעה",
"ui.message.copyResponse": "העתק את התגובה",
"ui.message.copied": "הועתק",
"ui.message.duration.seconds": "{{count}}s",
"ui.message.duration.minutesSeconds": "{{minutes}}m {{seconds}}s",
"ui.message.interrupted": "נקטע",
"ui.message.queued": "בתור",
"ui.message.attachment.alt": "קובץ מצורף",
"ui.patch.action.deleted": "נמחק",
"ui.patch.action.created": "נוצר",
"ui.patch.action.moved": "הועבר",
"ui.patch.action.patched": "עודכן",
"ui.question.subtitle.answered": "שאלות שנענו: {{count}}",
"ui.question.answer.none": "(אין תשובה)",
"ui.question.review.notAnswered": "(ללא תשובה)",
"ui.question.multiHint": "בחר את כל התשובות המתאימות",
"ui.question.singleHint": "בחר תשובה אחת",
"ui.question.custom.placeholder": "הקלד את תשובתך...",
"ui.sessionTurn.diffs.changed.two": "{{count}} קבצים השתנו",
"ui.messagePart.context.read.two": "{{count}} קריאות",
"ui.messagePart.context.search.two": "{{count}} חיפושים",
"ui.messagePart.context.list.two": "{{count}} הצגות",
}
+6
View File
@@ -75,10 +75,16 @@ describe("translate app", () => {
"packages/ui/src/i18n/dv.ts",
"packages/desktop/src/renderer/i18n/dv.ts",
])
expect(targetFiles("he")).toEqual([
"packages/app/src/i18n/he.ts",
"packages/ui/src/i18n/he.ts",
"packages/desktop/src/renderer/i18n/he.ts",
])
})
test("maps product locale codes to their glossaries", () => {
expect(glossaryFile("fr")).toBe(".opencode/glossary/fr.md")
expect(glossaryFile("he")).toBe(".opencode/glossary/he.md")
expect(glossaryFile("zh")).toBe(".opencode/glossary/zh-cn.md")
expect(glossaryFile("zht")).toBe(".opencode/glossary/zh-tw.md")
})
+1
View File
@@ -14,6 +14,7 @@ const locales = DESKTOP_NATIVE_LOCALES.filter((locale): locale is Locale => loca
const languages = {
ar: "Arabic",
he: "Hebrew",
br: "Brazilian Portuguese",
bs: "Bosnian",
da: "Danish",