diff --git a/packages/app/AGENTS.md b/packages/app/AGENTS.md
index 2f587ea73d5..4972f2e3a6c 100644
--- a/packages/app/AGENTS.md
+++ b/packages/app/AGENTS.md
@@ -24,6 +24,9 @@
- NEVER hardcode user-visible English strings in production code. ALWAYS use an i18n key for visible copy, placeholders, accessible labels, tooltips, menus, dialogs, toasts, empty states, and displayed errors.
- When migrating existing copy to i18n, preserve the English text byte-for-byte unless the task explicitly requests a copy change.
- NEVER change existing English text or English keys to facilitate translation. English is intentional, designer-written source copy; adapt locale-specific translations and i18n mechanics around it.
+- Keep locale complexity behind the shared typed i18n APIs. Feature and component code should use `language.t(...)` for ordinary copy and `language.plural(baseKey, count, params)` for count-sensitive copy. It must not inspect the locale, call `Intl.PluralRules`, construct or select plural-category keys such as `.one` or `.other`, or branch on locale-specific grammar.
+- Prefer complete translated phrases. Do not concatenate grammatical fragments or make call sites assemble sentences. Keep placeholders to irreducible dynamic values such as names, paths, and counts.
+- If a translation cannot be expressed by the current API, deepen the shared language/UI i18n module so one typed call owns locale selection, plural resolution, fallback, and interpolation. Do not leak that machinery into product code.
- Do not translate from model knowledge alone. Verify terminology and grammar with Unicode CLDR locale/plural data, Microsoft Localization Style Guides and terminology, Apple localization/style guidance and localized platform UI, Mozilla localization style guides, Mozilla Pontoon, and the Firefox localization corpus at `github.com/mozilla-l10n/firefox-l10n`.
- Also use the relevant language authority or official dictionary for the locale (for example RAE/Fundéu, FranceTerme, Duden, TDK, Kotus/Kielitoimiston sanakirja, Språkrådet/Bokmålsordboka, Rada Języka Polskiego/PWN, the Russian and Arabic language academies, the Ukrainian Orthography, Taiwan MOE dictionaries, or the Royal Society of Thailand). Treat the English dictionary as the semantic source of truth and preserve placeholders, code identifiers, product names, and keyboard labels.
diff --git a/packages/app/src/components/dialog-usage-exceeded.tsx b/packages/app/src/components/dialog-usage-exceeded.tsx
index bf5da751e22..de6451a63b6 100644
--- a/packages/app/src/components/dialog-usage-exceeded.tsx
+++ b/packages/app/src/components/dialog-usage-exceeded.tsx
@@ -1,4 +1,5 @@
import { usePlatform } from "@/context/platform"
+import { useLanguage } from "@/context/language"
import { Button } from "@opencode-ai/ui/button"
import { useDialog } from "@opencode-ai/ui/context/dialog"
import { Dialog } from "@opencode-ai/ui/dialog"
@@ -14,6 +15,7 @@ export type DialogGoUpsellProps = {
export function DialogUsageExceeded(props: DialogGoUpsellProps) {
const dialog = useDialog()
+ const language = useLanguage()
const platform = usePlatform()
const runAction = () => {
@@ -32,7 +34,7 @@ export function DialogUsageExceeded(props: DialogGoUpsellProps) {
- Don't show again
+ {language.t("dialog.usageExceeded.dontShowAgain")}
{props.actionLabel}
diff --git a/packages/app/src/components/titlebar.tsx b/packages/app/src/components/titlebar.tsx
index 64df40685f2..15d963a1384 100644
--- a/packages/app/src/components/titlebar.tsx
+++ b/packages/app/src/components/titlebar.tsx
@@ -628,7 +628,7 @@ function TitlebarUpdateIconButton(props: { state: TitlebarUpdatePillState }) {
aria-label={props.state.ariaLabel}
>
- Update
+ {props.state.label}
{
+describe("i18n parity", () => {
test("non-English locales have every English key and required plural variants", async () => {
for (const domain of domains) {
const source = await dictionary(domain.source)
diff --git a/packages/app/src/i18n/pl.ts b/packages/app/src/i18n/pl.ts
index 17c9fb639fb..9b3c927498d 100644
--- a/packages/app/src/i18n/pl.ts
+++ b/packages/app/src/i18n/pl.ts
@@ -185,6 +185,9 @@ export const dict = {
"command.session.share.description": "Udostępnij tę sesję i skopiuj adres URL do schowka",
"command.session.unshare": "Przestań udostępniać sesję",
"command.session.unshare.description": "Zatrzymaj udostępnianie tej sesji",
+ "command.session.export": "Eksportuj sesję",
+ "command.session.export.description": "Eksportuj pełny zapis sesji w formacie JSON",
+
"palette.search.placeholder": "Szukaj plików, poleceń i sesji",
"palette.search.placeholder.home": "Szukaj poleceń i sesji",
"palette.empty": "Brak wyników",
@@ -535,6 +538,8 @@ export const dict = {
"dialog.project.edit.worktree.startup.description":
"Uruchamiany po utworzeniu nowej przestrzeni roboczej (worktree).",
"dialog.project.edit.worktree.startup.placeholder": "np. bun install",
+ "dialog.usageExceeded.dontShowAgain": "Nie pokazuj ponownie",
+
"context.breakdown.title": "Podział kontekstu",
"context.breakdown.note": 'Przybliżony podział tokenów wejściowych. "Inne" obejmuje definicje narzędzi i narzut.',
"context.breakdown.system": "System",
@@ -544,6 +549,8 @@ export const dict = {
"context.breakdown.other": "Inne",
"context.systemPrompt.title": "Prompt systemowy",
"context.rawMessages.title": "Surowe wiadomości",
+ "context.export.session": "Eksportuj sesję",
+
"context.stats.session": "Sesja",
"context.stats.messages": "Wiadomości",
"context.stats.provider": "Dostawca",
@@ -610,6 +617,11 @@ export const dict = {
"toast.session.unshare.success.description": "Udostępnianie sesji zostało pomyślnie zatrzymane!",
"toast.session.unshare.failed.title": "Nie udało się zatrzymać udostępniania sesji",
"toast.session.unshare.failed.description": "Wystąpił błąd podczas zatrzymywania udostępniania sesji",
+ "toast.session.export.success.title": "Sesja wyeksportowana",
+ "toast.session.export.success.description": "Sesję zapisano jako {{filename}}",
+ "toast.session.export.failed.title": "Nie udało się wyeksportować sesji",
+ "toast.session.export.failed.description": "Wystąpił błąd podczas eksportowania sesji",
+
"toast.session.listFailed.title": "Nie udało się załadować sesji dla {{project}}",
"toast.update.title": "Dostępna aktualizacja",
"toast.update.description": "Nowa wersja OpenCode ({{version}}) jest teraz dostępna do instalacji.",
@@ -797,6 +809,7 @@ export const dict = {
"common.moreOptions": "Więcej opcji",
"common.learnMore": "Dowiedz się więcej",
"common.rename": "Zmień nazwę",
+ "common.export": "Eksportuj",
"common.reset": "Resetuj",
"common.archive": "Archiwizuj",
"common.delete": "Usuń",
diff --git a/packages/app/src/i18n/ru.ts b/packages/app/src/i18n/ru.ts
index 6d119926148..cf4fa8d8f4c 100644
--- a/packages/app/src/i18n/ru.ts
+++ b/packages/app/src/i18n/ru.ts
@@ -192,6 +192,9 @@ export const dict = {
"command.session.unshare": "Отменить публикацию",
"command.session.unshare.description": "Прекратить публикацию сессии",
+ "command.session.export": "Экспортировать сессию",
+ "command.session.export.description": "Экспортировать полную историю сессии в формате JSON",
+
"palette.search.placeholder": "Поиск файлов, команд и сессий",
"palette.search.placeholder.home": "Поиск команд и сессий",
"palette.empty": "Ничего не найдено",
@@ -564,6 +567,8 @@ export const dict = {
"dialog.project.edit.worktree.startup.description":
"Запускается после создания нового рабочего пространства (worktree).",
"dialog.project.edit.worktree.startup.placeholder": "например, bun install",
+ "dialog.usageExceeded.dontShowAgain": "Больше не показывать",
+
"context.breakdown.title": "Разбивка контекста",
"context.breakdown.note":
'Приблизительная разбивка входных токенов. "Другое" включает определения инструментов и накладные расходы.',
@@ -576,6 +581,8 @@ export const dict = {
"context.systemPrompt.title": "Системный промпт",
"context.rawMessages.title": "Исходные сообщения",
+ "context.export.session": "Экспортировать сессию",
+
"context.stats.session": "Сессия",
"context.stats.messages": "Сообщения",
"context.stats.provider": "Провайдер",
@@ -653,6 +660,11 @@ export const dict = {
"toast.session.unshare.failed.title": "Не удалось отменить публикацию",
"toast.session.unshare.failed.description": "Произошла ошибка при отмене публикации",
+ "toast.session.export.success.title": "Сессия экспортирована",
+ "toast.session.export.success.description": "Сессия сохранена в файл {{filename}}",
+ "toast.session.export.failed.title": "Не удалось экспортировать сессию",
+ "toast.session.export.failed.description": "Произошла ошибка при экспорте сессии",
+
"toast.session.listFailed.title": "Не удалось загрузить сессии для {{project}}",
"toast.update.title": "Доступно обновление",
@@ -858,6 +870,7 @@ export const dict = {
"common.moreOptions": "Дополнительные опции",
"common.learnMore": "Подробнее",
"common.rename": "Переименовать",
+ "common.export": "Экспортировать",
"common.reset": "Сбросить",
"common.archive": "Архивировать",
"common.delete": "Удалить",
diff --git a/packages/app/src/i18n/sv.ts b/packages/app/src/i18n/sv.ts
index ab5007238b6..39b363c6b65 100644
--- a/packages/app/src/i18n/sv.ts
+++ b/packages/app/src/i18n/sv.ts
@@ -179,6 +179,9 @@ export const dict = {
"command.session.share.description": "Dela den här sessionen och kopiera URL:en till urklipp",
"command.session.unshare": "Sluta dela session",
"command.session.unshare.description": "Sluta dela den här sessionen",
+ "command.session.export": "Exportera session",
+ "command.session.export.description": "Exportera hela sessionsutskriften som JSON",
+
"palette.search.placeholder": "Sök efter filer, kommandon och sessioner",
"palette.search.placeholder.home": "Sök bland kommandon och sessioner",
"palette.empty": "Inga resultat hittades",
@@ -538,6 +541,8 @@ export const dict = {
"dialog.releaseNotes.action.next": "Nästa",
"dialog.releaseNotes.action.hideFuture": "Visa inte dessa i framtiden",
"dialog.releaseNotes.media.alt": "Förhandsvisning av version",
+ "dialog.usageExceeded.dontShowAgain": "Visa inte igen",
+
"context.breakdown.title": "Kontextfördelning",
"context.breakdown.note":
'Ungefärlig uppdelning av inmatningstokens. "Övrigt" inkluderar verktygsdefinitioner och overhead.',
@@ -548,6 +553,8 @@ export const dict = {
"context.breakdown.other": "Övrigt",
"context.systemPrompt.title": "Systemprompt",
"context.rawMessages.title": "Råmeddelanden",
+ "context.export.session": "Exportera session",
+
"context.stats.session": "Session",
"context.stats.messages": "Meddelanden",
"context.stats.provider": "Leverantör",
@@ -614,6 +621,11 @@ export const dict = {
"toast.session.unshare.success.description": "Delningen av sessionen har avslutats!",
"toast.session.unshare.failed.title": "Det gick inte att avsluta delningen",
"toast.session.unshare.failed.description": "Ett fel uppstod när delningen skulle avslutas",
+ "toast.session.export.success.title": "Session exporterad",
+ "toast.session.export.success.description": "Sessionen sparades i {{filename}}",
+ "toast.session.export.failed.title": "Det gick inte att exportera sessionen",
+ "toast.session.export.failed.description": "Ett fel uppstod när sessionen exporterades",
+
"toast.session.listFailed.title": "Det gick inte att läsa in sessioner för {{project}}",
"toast.project.reloadFailed.title": "Det gick inte att ladda om {{project}}",
"toast.update.title": "Uppdatering tillgänglig",
@@ -828,6 +840,7 @@ export const dict = {
"common.moreOptions": "Fler alternativ",
"common.learnMore": "Läs mer",
"common.rename": "Byt namn",
+ "common.export": "Exportera",
"common.reset": "Återställ",
"common.archive": "Arkivera",
"common.delete": "Radera",
diff --git a/packages/app/src/i18n/th.ts b/packages/app/src/i18n/th.ts
index e87d0db164d..1a0e9db72b1 100644
--- a/packages/app/src/i18n/th.ts
+++ b/packages/app/src/i18n/th.ts
@@ -191,6 +191,9 @@ export const dict = {
"command.session.unshare": "ยกเลิกการแชร์เซสชัน",
"command.session.unshare.description": "หยุดการแชร์เซสชันนี้",
+ "command.session.export": "ส่งออกเซสชัน",
+ "command.session.export.description": "ส่งออกบันทึกทั้งหมดของเซสชันเป็น JSON",
+
"palette.search.placeholder": "ค้นหาไฟล์ คำสั่ง และเซสชัน",
"palette.search.placeholder.home": "ค้นหาคำสั่งและเซสชัน",
"palette.empty": "ไม่พบผลลัพธ์",
@@ -561,6 +564,8 @@ export const dict = {
"dialog.project.edit.worktree.startup.description": "ทำงานหลังจากสร้างพื้นที่ทำงานใหม่ (worktree)",
"dialog.project.edit.worktree.startup.placeholder": "เช่น bun install",
+ "dialog.usageExceeded.dontShowAgain": "ไม่ต้องแสดงอีก",
+
"context.breakdown.title": "การแบ่งบริบท",
"context.breakdown.note": 'การแบ่งโดยประมาณของโทเค็นนำเข้า "อื่น ๆ" รวมถึงคำนิยามเครื่องมือและโอเวอร์เฮด',
"context.breakdown.system": "ระบบ",
@@ -572,6 +577,8 @@ export const dict = {
"context.systemPrompt.title": "พรอมต์ระบบ",
"context.rawMessages.title": "ข้อความดิบ",
+ "context.export.session": "ส่งออกเซสชัน",
+
"context.stats.session": "เซสชัน",
"context.stats.messages": "ข้อความ",
"context.stats.provider": "ผู้ให้บริการ",
@@ -650,6 +657,11 @@ export const dict = {
"toast.session.unshare.failed.title": "ไม่สามารถยกเลิกการแชร์เซสชัน",
"toast.session.unshare.failed.description": "เกิดข้อผิดพลาดระหว่างการยกเลิกการแชร์เซสชัน",
+ "toast.session.export.success.title": "ส่งออกเซสชันแล้ว",
+ "toast.session.export.success.description": "บันทึกเซสชันไปยัง {{filename}} แล้ว",
+ "toast.session.export.failed.title": "ไม่สามารถส่งออกเซสชัน",
+ "toast.session.export.failed.description": "เกิดข้อผิดพลาดขณะส่งออกเซสชัน",
+
"toast.session.listFailed.title": "ไม่สามารถโหลดเซสชันสำหรับ {{project}}",
"toast.update.title": "มีการอัปเดต",
@@ -843,6 +855,7 @@ export const dict = {
"common.moreOptions": "ตัวเลือกเพิ่มเติม",
"common.learnMore": "เรียนรู้เพิ่มเติม",
"common.rename": "เปลี่ยนชื่อ",
+ "common.export": "ส่งออก",
"common.reset": "รีเซ็ต",
"common.archive": "จัดเก็บ",
"common.delete": "ลบ",
diff --git a/packages/app/src/i18n/tr.ts b/packages/app/src/i18n/tr.ts
index d0d3a495034..85623036e42 100644
--- a/packages/app/src/i18n/tr.ts
+++ b/packages/app/src/i18n/tr.ts
@@ -197,6 +197,9 @@ export const dict = {
"command.session.unshare": "Paylaşımı kaldır",
"command.session.unshare.description": "Bu oturumun paylaşımını durdur",
+ "command.session.export": "Oturumu dışa aktar",
+ "command.session.export.description": "Oturumun tam dökümünü JSON olarak dışa aktar",
+
"palette.search.placeholder": "Dosya, komut ve oturum ara",
"palette.search.placeholder.home": "Komut ve oturum ara",
"palette.empty": "Sonuç bulunamadı",
@@ -569,6 +572,8 @@ export const dict = {
"dialog.project.edit.worktree.startup.description": "Yeni bir çalışma alanı (worktree) oluşturduktan sonra çalışır.",
"dialog.project.edit.worktree.startup.placeholder": "örneğin bun install",
+ "dialog.usageExceeded.dontShowAgain": "Bir daha gösterme",
+
"context.breakdown.title": "Bağlam Dökümü",
"context.breakdown.note": 'Girdi tokenlerinin yaklaşık dökümü. "Diğer" araç tanımları ve ek yükleri içerir.',
"context.breakdown.system": "Sistem",
@@ -580,6 +585,8 @@ export const dict = {
"context.systemPrompt.title": "Sistem İstemi",
"context.rawMessages.title": "Ham mesajlar",
+ "context.export.session": "Oturumu dışa aktar",
+
"context.stats.session": "Oturum",
"context.stats.messages": "Mesajlar",
"context.stats.provider": "Sağlayıcı",
@@ -658,6 +665,11 @@ export const dict = {
"toast.session.unshare.failed.title": "Oturum paylaşımı kaldırılamadı",
"toast.session.unshare.failed.description": "Oturum paylaşımı kaldırılırken bir hata oluştu",
+ "toast.session.export.success.title": "Oturum dışa aktarıldı",
+ "toast.session.export.success.description": "Oturum {{filename}} dosyasına kaydedildi",
+ "toast.session.export.failed.title": "Oturum dışa aktarılamadı",
+ "toast.session.export.failed.description": "Oturum dışa aktarılırken bir hata oluştu",
+
"toast.session.listFailed.title": "{{project}} için oturumlar yüklenemedi",
"toast.update.title": "Güncelleme mevcut",
@@ -861,6 +873,7 @@ export const dict = {
"common.moreOptions": "Daha fazla seçenek",
"common.learnMore": "Daha fazla bilgi",
"common.rename": "Yeniden adlandır",
+ "common.export": "Dışa aktar",
"common.reset": "Sıfırla",
"common.archive": "Arşivle",
"common.delete": "Sil",
diff --git a/packages/app/src/i18n/uk.ts b/packages/app/src/i18n/uk.ts
index fdeb0b83fb9..54732a87b7e 100644
--- a/packages/app/src/i18n/uk.ts
+++ b/packages/app/src/i18n/uk.ts
@@ -193,6 +193,9 @@ export const dict = {
"command.session.unshare": "Припинити поширення сесії",
"command.session.unshare.description": "Припинити поширення цієї сесії",
+ "command.session.export": "Експортувати сесію",
+ "command.session.export.description": "Експортувати повну історію сесії у форматі JSON",
+
"palette.search.placeholder": "Пошук файлів, команд і сесій",
"palette.search.placeholder.home": "Пошук команд і сесій",
"palette.empty": "Результатів не знайдено",
@@ -578,6 +581,8 @@ export const dict = {
"dialog.releaseNotes.action.hideFuture": "Не показувати це в майбутньому",
"dialog.releaseNotes.media.alt": "Попередній перегляд релізу",
+ "dialog.usageExceeded.dontShowAgain": "Більше не показувати",
+
"context.breakdown.title": "Розподіл контексту",
"context.breakdown.note":
'Приблизний розподіл вхідних токенів. "Інше" включає визначення інструментів і накладні витрати.',
@@ -590,6 +595,8 @@ export const dict = {
"context.systemPrompt.title": "Системний запит",
"context.rawMessages.title": "Необроблені повідомлення",
+ "context.export.session": "Експортувати сесію",
+
"context.stats.session": "Сесія",
"context.stats.messages": "Повідомлення",
"context.stats.provider": "Провайдер",
@@ -668,6 +675,11 @@ export const dict = {
"toast.session.unshare.failed.title": "Не вдалося припинити поширення сесії",
"toast.session.unshare.failed.description": "Під час припинення поширення сесії сталася помилка",
+ "toast.session.export.success.title": "Сесію експортовано",
+ "toast.session.export.success.description": "Сесію збережено у файл {{filename}}",
+ "toast.session.export.failed.title": "Не вдалося експортувати сесію",
+ "toast.session.export.failed.description": "Під час експортування сесії сталася помилка",
+
"toast.session.listFailed.title": "Не вдалося завантажити сесії для {{project}}",
"toast.project.reloadFailed.title": "Не вдалося перезавантажити {{project}}",
@@ -910,6 +922,7 @@ export const dict = {
"common.moreOptions": "Більше параметрів",
"common.learnMore": "Дізнатися більше",
"common.rename": "Перейменувати",
+ "common.export": "Експортувати",
"common.reset": "Скинути",
"common.archive": "Архівувати",
"common.delete": "Видалити",
diff --git a/packages/app/src/i18n/ur.ts b/packages/app/src/i18n/ur.ts
index 661c013ebe7..2ab69e7aec7 100644
--- a/packages/app/src/i18n/ur.ts
+++ b/packages/app/src/i18n/ur.ts
@@ -186,6 +186,9 @@ export const dict = {
"command.session.share.description": "اس سیشن کا اشتراک کریں اور URL کو کلپ بورڈ میں کاپی کریں۔",
"command.session.unshare": "سیشن کا اشتراک ختم کریں۔",
"command.session.unshare.description": "اس سیشن کا اشتراک کرنا بند کریں۔",
+ "command.session.export": "سیشن برآمد کریں",
+ "command.session.export.description": "سیشن کی مکمل نقل JSON کی صورت میں برآمد کریں",
+
"palette.search.placeholder": "فائلیں، کمانڈز اور سیشنز تلاش کریں۔",
"palette.search.placeholder.home": "کمانڈز اور سیشن تلاش کریں۔",
"palette.empty": "کوئی نتیجہ نہیں ملا",
@@ -546,6 +549,8 @@ export const dict = {
"dialog.releaseNotes.action.next": "اگلا",
"dialog.releaseNotes.action.hideFuture": "مستقبل میں یہ نہ دکھائیں۔",
"dialog.releaseNotes.media.alt": "ریلیز کا پیش منظر",
+ "dialog.usageExceeded.dontShowAgain": "دوبارہ نہ دکھائیں",
+
"context.breakdown.title": "سیاق و سباق کی تفصیل",
"context.breakdown.note": 'ان پٹ ٹوکنز کی تخمینی تقسیم۔ "دیگر" میں ٹول کی تعریفیں اور اضافی بوجھ شامل ہیں۔',
"context.breakdown.system": "سسٹم",
@@ -555,6 +560,8 @@ export const dict = {
"context.breakdown.other": "دیگر",
"context.systemPrompt.title": "سسٹم پرامپٹ",
"context.rawMessages.title": "خام پیغامات",
+ "context.export.session": "سیشن برآمد کریں",
+
"context.stats.session": "سیشن",
"context.stats.messages": "پیغامات",
"context.stats.provider": "فراہم کنندہ",
@@ -621,6 +628,11 @@ export const dict = {
"toast.session.unshare.success.description": "سیشن کا اشتراک کامیابی سے ختم ہو گیا!",
"toast.session.unshare.failed.title": "سیشن کا اشتراک ختم کرنے میں ناکام",
"toast.session.unshare.failed.description": "سیشن کا اشتراک ختم کرتے وقت ایک خرابی پیش آگئی",
+ "toast.session.export.success.title": "سیشن برآمد کر دیا گیا",
+ "toast.session.export.success.description": "سیشن کو \u2068{{filename}}\u2069 میں محفوظ کر دیا گیا",
+ "toast.session.export.failed.title": "سیشن برآمد کرنے میں ناکام",
+ "toast.session.export.failed.description": "سیشن برآمد کرتے وقت ایک خرابی پیش آ گئی",
+
"toast.session.listFailed.title": "{{project}} کے لیے سیشن لوڈ کرنے میں ناکام",
"toast.project.reloadFailed.title": "{{project}} کو دوبارہ لوڈ کرنے میں ناکام",
"toast.update.title": "اپ ڈیٹ دستیاب ہے۔",
@@ -834,6 +846,7 @@ export const dict = {
"common.moreOptions": "مزید اختیارات",
"common.learnMore": "مزید جانیں",
"common.rename": "نام تبدیل کریں۔",
+ "common.export": "برآمد کریں",
"common.reset": "دوبارہ ترتیب دیں۔",
"common.archive": "آرکائیو کریں",
"common.delete": "حذف کریں۔",
diff --git a/packages/app/src/i18n/vi.ts b/packages/app/src/i18n/vi.ts
index 1e243ba17d6..06fda4ce707 100644
--- a/packages/app/src/i18n/vi.ts
+++ b/packages/app/src/i18n/vi.ts
@@ -185,6 +185,9 @@ export const dict = {
"command.session.share.description": "Chia sẻ phiên này và sao chép URL vào bảng tạm",
"command.session.unshare": "Ngừng chia sẻ phiên",
"command.session.unshare.description": "Dừng chia sẻ phiên này",
+ "command.session.export": "Xuất phiên",
+ "command.session.export.description": "Xuất toàn bộ bản ghi phiên dưới dạng JSON",
+
"palette.search.placeholder": "Tìm kiếm tệp, lệnh và phiên",
"palette.search.placeholder.home": "Tìm kiếm lệnh và phiên",
"palette.empty": "Không tìm thấy kết quả nào",
@@ -543,6 +546,8 @@ export const dict = {
"dialog.releaseNotes.action.next": "Tiếp theo",
"dialog.releaseNotes.action.hideFuture": "Không hiển thị lại",
"dialog.releaseNotes.media.alt": "Xem trước bản phát hành",
+ "dialog.usageExceeded.dontShowAgain": "Không hiển thị lại",
+
"context.breakdown.title": "Phân tích ngữ cảnh",
"context.breakdown.note":
'Phân tích gần đúng của token đầu vào. "Khác" bao gồm các định nghĩa công cụ và chi phí chung.',
@@ -553,6 +558,8 @@ export const dict = {
"context.breakdown.other": "Khác",
"context.systemPrompt.title": "Lời nhắc hệ thống",
"context.rawMessages.title": "Tin nhắn thô",
+ "context.export.session": "Xuất phiên",
+
"context.stats.session": "Phiên",
"context.stats.messages": "Tin nhắn",
"context.stats.provider": "Nhà cung cấp",
@@ -619,6 +626,11 @@ export const dict = {
"toast.session.unshare.success.description": "Đã hủy chia sẻ phiên thành công!",
"toast.session.unshare.failed.title": "Không hủy chia sẻ được phiên",
"toast.session.unshare.failed.description": "Đã xảy ra lỗi khi hủy chia sẻ phiên",
+ "toast.session.export.success.title": "Đã xuất phiên",
+ "toast.session.export.success.description": "Đã lưu phiên vào {{filename}}",
+ "toast.session.export.failed.title": "Không thể xuất phiên",
+ "toast.session.export.failed.description": "Đã xảy ra lỗi khi xuất phiên",
+
"toast.session.listFailed.title": "Không thể tải phiên cho {{project}}",
"toast.project.reloadFailed.title": "Không thể tải lại {{project}}",
"toast.update.title": "Đã có bản cập nhật",
@@ -834,6 +846,7 @@ export const dict = {
"common.moreOptions": "Nhiều lựa chọn hơn",
"common.learnMore": "Tìm hiểu thêm",
"common.rename": "Đổi tên",
+ "common.export": "Xuất",
"common.reset": "Đặt lại",
"common.archive": "Lưu trữ",
"common.delete": "Xóa",
diff --git a/packages/app/src/i18n/zh.ts b/packages/app/src/i18n/zh.ts
index 08bda71a65e..f1186f5b545 100644
--- a/packages/app/src/i18n/zh.ts
+++ b/packages/app/src/i18n/zh.ts
@@ -218,6 +218,9 @@ export const dict = {
"command.session.unshare": "取消分享会话",
"command.session.unshare.description": "停止分享此会话",
+ "command.session.export": "导出会话",
+ "command.session.export.description": "将完整会话记录导出为 JSON",
+
"palette.search.placeholder": "搜索文件、命令和会话",
"palette.search.placeholder.home": "搜索命令和会话",
"palette.empty": "未找到结果",
@@ -577,6 +580,8 @@ export const dict = {
"dialog.project.edit.worktree.startup.description": "创建新工作区 (worktree) 后运行。",
"dialog.project.edit.worktree.startup.placeholder": "例如 bun install",
+ "dialog.usageExceeded.dontShowAgain": "不再显示",
+
"context.breakdown.title": "上下文细分",
"context.breakdown.note": "输入令牌的大致细分。“其他”包含工具定义和开销。",
"context.breakdown.system": "系统",
@@ -586,6 +591,8 @@ export const dict = {
"context.breakdown.other": "其他",
"context.systemPrompt.title": "系统提示词",
"context.rawMessages.title": "原始消息",
+ "context.export.session": "导出会话",
+
"context.stats.session": "会话",
"context.stats.messages": "消息数",
"context.stats.provider": "提供商",
@@ -654,6 +661,11 @@ export const dict = {
"toast.session.unshare.success.description": "会话已成功取消分享",
"toast.session.unshare.failed.title": "取消分享失败",
"toast.session.unshare.failed.description": "取消分享会话时发生错误",
+ "toast.session.export.success.title": "会话已导出",
+ "toast.session.export.success.description": "已将会话保存到 {{filename}}",
+ "toast.session.export.failed.title": "导出会话失败",
+ "toast.session.export.failed.description": "导出会话时发生错误",
+
"toast.session.listFailed.title": "无法加载 {{project}} 的会话",
"toast.update.title": "有可用更新",
"toast.update.description": "OpenCode 有新版本 ({{version}}) 可安装。",
@@ -839,6 +851,7 @@ export const dict = {
"common.moreOptions": "更多选项",
"common.learnMore": "了解更多",
"common.rename": "重命名",
+ "common.export": "导出",
"common.reset": "重置",
"common.archive": "归档",
"common.delete": "删除",
diff --git a/packages/app/src/i18n/zht.ts b/packages/app/src/i18n/zht.ts
index 78a452070a7..6cc3d555289 100644
--- a/packages/app/src/i18n/zht.ts
+++ b/packages/app/src/i18n/zht.ts
@@ -195,6 +195,9 @@ export const dict = {
"command.session.unshare": "取消分享工作階段",
"command.session.unshare.description": "停止分享此工作階段",
+ "command.session.export": "匯出工作階段",
+ "command.session.export.description": "將完整的工作階段記錄匯出為 JSON",
+
"palette.search.placeholder": "搜尋檔案、命令和工作階段",
"palette.search.placeholder.home": "搜尋命令和工作階段",
"palette.empty": "找不到結果",
@@ -558,6 +561,8 @@ export const dict = {
"dialog.project.edit.worktree.startup": "工作區啟動腳本",
"dialog.project.edit.worktree.startup.description": "在建立新的工作區 (worktree) 後執行。",
"dialog.project.edit.worktree.startup.placeholder": "例如 bun install",
+ "dialog.usageExceeded.dontShowAgain": "不再顯示",
+
"context.breakdown.title": "上下文細分",
"context.breakdown.note": "輸入 token 的概略細分。「其他」包含工具定義和額外負擔。",
"context.breakdown.system": "系統",
@@ -569,6 +574,8 @@ export const dict = {
"context.systemPrompt.title": "系統提示詞",
"context.rawMessages.title": "原始訊息",
+ "context.export.session": "匯出工作階段",
+
"context.stats.session": "工作階段",
"context.stats.messages": "訊息數",
"context.stats.provider": "提供者",
@@ -646,6 +653,11 @@ export const dict = {
"toast.session.unshare.failed.title": "取消分享失敗",
"toast.session.unshare.failed.description": "取消分享工作階段時發生錯誤",
+ "toast.session.export.success.title": "工作階段已匯出",
+ "toast.session.export.success.description": "已將工作階段儲存至 {{filename}}",
+ "toast.session.export.failed.title": "匯出工作階段失敗",
+ "toast.session.export.failed.description": "匯出工作階段時發生錯誤",
+
"toast.session.listFailed.title": "無法載入 {{project}} 的工作階段",
"toast.update.title": "有可用更新",
@@ -836,6 +848,7 @@ export const dict = {
"common.moreOptions": "更多選項",
"common.learnMore": "深入了解",
"common.rename": "重新命名",
+ "common.export": "匯出",
"common.reset": "重設",
"common.archive": "封存",
"common.delete": "刪除",
diff --git a/packages/desktop/AGENTS.md b/packages/desktop/AGENTS.md
index 72a129af0f0..a38bcf753b1 100644
--- a/packages/desktop/AGENTS.md
+++ b/packages/desktop/AGENTS.md
@@ -5,5 +5,7 @@
- NEVER hardcode user-visible English strings in production code. ALWAYS use an i18n key for native menus, picker titles, dialogs, buttons, accessible labels, and displayed errors.
- When migrating existing copy to i18n, preserve the English text byte-for-byte unless the task explicitly requests a copy change.
- NEVER change existing English text or English keys to facilitate translation. English is intentional, designer-written source copy; adapt locale-specific translations and i18n mechanics around it.
+- Keep locale and grammar logic in the shared typed i18n layer. Renderer code should resolve copy through the app language API, and the main process should consume typed native-translation bundles through `nativeT(...)`; native menus, dialogs, and IPC handlers must not inspect locales, choose plural categories, or assemble translated sentence fragments.
+- Prefer complete translated phrases with only irreducible dynamic placeholders. If native UI needs richer grammar, deepen the shared bundle/API instead of adding locale branches to desktop feature code.
- Do not translate from model knowledge alone. Verify terminology and grammar with Unicode CLDR locale/plural data, Microsoft Localization Style Guides and terminology, Apple localization/style guidance and localized platform UI, Mozilla localization style guides, Mozilla Pontoon, and the Firefox localization corpus at `github.com/mozilla-l10n/firefox-l10n`.
- Also use the relevant language authority or official dictionary for the locale (for example RAE/Fundéu, FranceTerme, Duden, TDK, Kotus/Kielitoimiston sanakirja, Språkrådet/Bokmålsordboka, Rada Języka Polskiego/PWN, the Russian and Arabic language academies, the Ukrainian Orthography, Taiwan MOE dictionaries, or the Royal Society of Thailand). Treat the English dictionary as the semantic source of truth and preserve placeholders, code identifiers, product names, and keyboard labels.