Commit Graph
13 Commits
Author SHA1 Message Date
OrcaWinandOrcaWin 938f9ff80f refactor(i18n): native i18next plurals at call sites, plural-aware extraction (#35637)
* perf(i18n): load built-in catalogs once, without copies or a runtime plural fill

Each catalog load parsed the JSON, rebuilt it to fill CLDR plural forms
from _other, then deep-copied and deep-merged it into the store (about
26 ms after parsing per 1.3 MB catalog), and concurrent loads of one
locale fetched it twice.

- sync:localization-catalog now commits the plural forms a locale's
  CLDR rules need beside each _other (fr/es/pt many, tr one, a few
  de/ru), and verify:localization-catalog fails when one is missing.
  Plural forms may include {{count}} where English omits it.
- Renderer and main load built-in catalogs as-is; plugin packs keep the
  runtime fill.
- Renderer catalog requests are shared per locale; a timed-out request
  is dropped so a retry fetches again. Bundles are added with skipCopy.

* fix(i18n): require {{count}} in plural forms when English shows it

* refactor(i18n): share one i18next setup between main and renderer

- createOrcaI18n (src/shared/orca-i18n-instance.ts) owns the backend,
  init options, plugin-pack registration, resource-language resolution
  and the translate wrapper; main and renderer inject only how a
  built-in catalog loads (dynamic import vs. fetch) and extra modules.
- Remove setRendererUiLanguage (tests only); tests load then switch.
- resolveUiLocale maps built-ins by lookup instead of an if-chain, and
  catalog URLs/loaders are direct map lookups.
- Drop <I18nextProvider>: initReactI18next already binds the instance.

* refactor(i18n): native i18next plurals at call sites, plural-aware extraction

Call sites branched on count === 1 and passed literal _one/_other keys,
which resolvePluralFamilyKey stripped again so CLDR could pick the form.
They now call translate('x', otherDefault, { count, defaultValue_one })
and i18next selects the form natively; catalog keys stay x_one/x_other.

- Converted 284 families with an AST codemod plus a few by hand; added
  defaultValue_one to 21 base-key families that relied on en.json.
- Extraction runs with plurals enabled, so both English defaults are
  checked for drift. A key that only fills {{count}} (no defaultValue_one)
  stays singular in en.json.
- verify/sync:localization-catalog expand a native plural call into its
  x_one/x_other English entries.
- Removed 22 base keys that a sibling plural family shadowed in every
  catalog.

* fix(i18n): pick English plural defaults by English rules; require count for plural refs

* refactor(i18n): convert plural sites merged from main; treat key-as-default plural extraction as dynamic

* fix(i18n): keep an undefined pass-through count absent; only dotted key-as-default is dynamic

* test(i18n): update plural call expectations in two renderer tests

* refactor(i18n): convert a plural site merged from main

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-28 22:11:28 -07:00
OrcaWinandOrcaWin 704d3cdd6f perf(i18n): load built-in catalogs once, without copies or a runtime plural fill (#35615)
* perf(i18n): load built-in catalogs once, without copies or a runtime plural fill

Each catalog load parsed the JSON, rebuilt it to fill CLDR plural forms
from _other, then deep-copied and deep-merged it into the store (about
26 ms after parsing per 1.3 MB catalog), and concurrent loads of one
locale fetched it twice.

- sync:localization-catalog now commits the plural forms a locale's
  CLDR rules need beside each _other (fr/es/pt many, tr one, a few
  de/ru), and verify:localization-catalog fails when one is missing.
  Plural forms may include {{count}} where English omits it.
- Renderer and main load built-in catalogs as-is; plugin packs keep the
  runtime fill.
- Renderer catalog requests are shared per locale; a timed-out request
  is dropped so a retry fetches again. Bundles are added with skipCopy.

* fix(i18n): require {{count}} in plural forms when English shows it

* chore(i18n): sync plural forms for catalogs merged from main

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-28 19:58:12 -07:00
52a43ed99e feat(i18n): add Russian UI language (#34717)
* feat(i18n): add Russian UI language

Register `ru` as a built-in UI language using the catalog from #33370,
reconciled against current en.json: keys pruned from English were dropped,
stale translations whose English source changed were removed, and the
catalog verifier now accepts CLDR few/many forms for an existing `_other`
family.

Co-authored-by: Vyacheslav Scherbinin <gentslava@mail.ru>

* fix(i18n): drop Russian _one entries superseded by count placeholders

Main now interpolates the count in English _one strings (#34693), so the
literal-1 Russian forms no longer match their source.

* feat(i18n): complete Russian catalog and add terminology glossary

Translate the 650 remaining English keys and the few/many plural forms for
every count family, and document the Russian glossary and style conventions.

* fix(i18n): apply reviewed Russian translation corrections

Fix mistranslations and grammar flagged in a full-catalog review, and align
terminology with the glossary: рабочее дерево for worktree, рабочее
пространство for workspace, пул-реквест and файлы cookie.

* fix(i18n): resolve Russian plural forms in main-process strings and align ru terminology

translateMain now resolves literal _one/_other keys through the CLDR plural
family like translate(), so native dialogs pick Russian few/many forms. The
plural helpers move to src/shared for reuse. apply-translation-delta accepts
locale-only plural categories sourced from English _other. ru.json follows the
glossary for account/session/prompt and uses a single ellipsis character.

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Co-authored-by: Vyacheslav Scherbinin <gentslava@mail.ru>
2026-10-27 23:18:15 -07:00
6631e5f6b6 fix(i18n): give each reused localization key its own sentence (#34648)
* fix(i18n): give each reused localization key its own sentence

Thirteen keys were shared by call sites that say different things. The
catalog holds one value per key, so the second site rendered the first
site's text: "Move X left" on the right-move button, "Use X repo color"
on the icon picker, "lines above" on the expand-below control, "Open X
issues" on the open-in-Linear action, and similar for status icons,
reclaimable workspaces, remote uploads, automation destination notices,
the add-project button and MiniMax credential errors.

Split every divergent site onto its own key with translations in all
locales, sync the runtime-required catalog, and make
verify-localization-catalog reject a key reused with different fallback
text so the collision cannot come back.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(i18n): pluralize the remote upload toast with i18next count

The toast passed an English "s" as {{value1}}, so ja/ko/zh rendered
"ファイルs", "파일s" and "文件s". The catalog gate requires every locale to
keep the source placeholders, so dropping {{value1}} per locale is not an
option; use count plurals (_one/_other) like nativeDropTempCopyFailed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* docs: add one-line docstrings to the functions this change touches

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(i18n): reject reused fallback keys before --fix writes en.json

A missing key used with two different fallback sentences had its first
sentence written to en.json before the reused-fallback check failed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Matheus Breguêz <matbrgz@gmail.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-27 18:28:23 -07:00
Neilandoutof-place 88518900e2 fix(i18n): reject duplicate keys in localization catalogs (#33950)
JSON.parse silently keeps the last value of a repeated key, so the
catalog verifier could not see duplicates. Scan en.json and every target
catalog with ts.parseJsonText before parsing and fail with file:line and
the dotted key for each duplicate.

Fixes #27153

Co-authored-by: outof-place <contact@outofplace.space>
2026-10-26 06:52:19 -07:00
NeilandEvgenii 5517163679 fix: nest settings translation keys for language packs (#28251)
Reshape dotted translation keys into nested objects while retaining identical translation paths and values in all six locales. Keep the existing language-pack parser and its safety restrictions.

Co-authored-by: Evgenii <kumiro@me.com>
2026-10-13 18:56:10 -07:00
Ilya Gusev 8262fb147f fix(i18n): extract translateSearchKeyword calls so settings-search keywords reach en.json
Reviewed and approved by Codex.
2026-09-03 15:07:34 -07:00
AnddyAgudelo a202d92772 fix(localization): stop the repair policy rewriting translated generic terms to English (#12192)
Fixes #12113.

shouldPreserveEnglishValue keyed on the English value, so any key whose source
string equalled a NEVER_TRANSLATE_VALUES entry was forced back to English on
every repair run — agent, commit, repo, terminal and Continue were all on that
list. Measured on a clean checkout: ko 279, ja 461, zh 1180, es 479 values
rewritten, the large majority destroying translator work.

17 generic terms move into locale-generic-ui-terms.mjs, and the brand revert now
skips a term's canonical rendering, so genuinely nonsensical forms (zh 回购,
ja/zh 端子, es Comprometerse) still fire while 터미널/커밋/エージェント survive.
Brand, path, and code tokens are untouched — MD -> 医学博士 and HEAD -> CABEZA
are why that list still earns its keep.

No catalog values change; every file is under config/scripts/.

Co-authored-by: AnddyAgudelo <44873492+AnddyAgudelo@users.noreply.github.com>
2026-08-04 03:56:33 -07:00
Brennan Benson 5e00a30e4e Decouple feature copy from locale parity (#8512)
* Decouple feature copy from locale parity

* Fix undeclared dynamic localization key check

* Fix localization code owner
2026-07-29 17:44:41 -07:00
NeilandOrca 97e4776dfe feat(plugins): Orca plugin system — kernel, content packs, panels, workers, marketplace v0 (experimental) (#8549)
* feat(plugins): Orca plugin system — kernel, content packs, panels, workers, marketplace v0 (experimental)

Adds Orca's experimental plugin system behind a settings flag: a
supervised kernel, declarative content packs (VM recipes, commands and
keybindings, language packs), sandboxed iframe panels, forked worker
hosts, and a Git-backed marketplace v0 with consent, provenance and
kill-list enforcement.

Theme, icon-theme and terminal-theme contributions are deferred to a
follow-up pass.

* fix(plugins): make unsupported marketplace listings unreachable by key

findPlugin() backs preview/install/previewInstalledUpdate via
requireListing(), so filtering only listPlugins() hid the catalog card
while leaving the dead install path reachable one click later.

* fix(plugins): fan Pi session-only status out to plugin subscribers

The providerSessionOnly early-return in applyNormalizedStatus emitted to
onAgentStatus (main-window fanout) but skipped enrichedStatusListeners, so
plugins subscribed to agent.status.changed silently missed every Pi
session_start event. Route both emit sites through one helper so a future
early return cannot drop the plugin tap again.

Co-authored-by: Orca <help@stably.ai>

* plugins: drop dead code and hoist duplicated trust-boundary patterns

Cleanup pass over the P1 diff, no behavior change:

- Delete `readPluginTreeSnapshot`/`readSnapshotFile` and their types, plus
  the now-vestigial `directories`/`signal` plumbing in `collectFiles`.
- Delete `resolveContainedPluginDirectory` (no callers).
- Delete `plugin-content-load-pool.ts`; it reimplemented the existing
  `mapWithConcurrency`, whose index arg also removes the pairing wrapper
  in `buildPluginList`.
- Hoist `PLUGIN_CONTENT_HASH_PATTERN` and `PLUGIN_COMMIT_PATTERN` into
  the install-lockfile module; 11 sites hand-rolled these identically.
- Point the new reliability gate at the PR instead of gitignored docs
  paths, matching every other gate's link form.

* fix(plugins): retry plugin state renames on Windows AV/EPERM locks

Six plugin write paths (lockfile, provenance, current pointer, kill
list, marketplace cache, staged install dir) did a plain rename, so an
antivirus or indexer holding the target open surfaced as a failed
install. The repo already retries this hazard for issue #1507, but only
through a sync helper; these paths are all async.

Adds one bounded async retry + atomic write used by all six, and trims a
consent-provenance header that restated its own JSX.

* test(plugins): cover the Windows rename retry path

The retry loop shipped untested: both existing cases hit the non-retry path,
and the temp-cleanup test passed identically with the `finally` removed.
Mock `rename` to queue errno codes so CI can exercise locks it cannot provoke.

Co-authored-by: Orca <help@stably.ai>

* fix(plugins): pin bundled plugin resources to LF

Windows CI checks out with autocrlf, so the byte-hashed launch tree arrived
as CRLF and verify-packaged-plugin-resources rejected it — the packaged build
could never pass on Windows. Reproduced locally: CRLF yields the exact CI
error, LF verifies clean. Files are already LF, so nothing renormalizes.

Co-authored-by: Orca <help@stably.ai>

* test: guard the bundled-plugin LF pin against a CRLF checkout

The byte-hash mismatch only surfaced in Windows packaging CI. Assert the
.gitattributes pin and that a CRLF tree is rejected, so a regression fails
on any platform instead of waiting for a packaged Windows build.

Co-authored-by: Orca <help@stably.ai>

* ci: trigger packaged-build check on bundled plugin resource changes

The launch tree is byte-hashed during packaging, but no trigger path covered
it — so the CRLF fix for that check would not have re-run the check. Add the
resources, verifier and .gitattributes paths that can break packaging.

Co-authored-by: Orca <help@stably.ai>

* perf(plugins): rebuild the panel frame only when its baked theme values change

The revision keys the panel iframe, so every bump destroys the sandboxed
frame and its in-panel state. It counted root attribute mutations, but
--workspace-sidebar-live-width is written every rAF of a sidebar drag, so
dragging with a panel open blanked it ~60x/sec. Compare the two values the
shell actually bakes in instead.

Co-authored-by: Orca <help@stably.ai>

* test: stop pinning a plugin name in the CRLF guard

The CRLF case rewrites every launch file, so the reported mismatch is
whichever plugin sorts first. P2 adds theme plugins that sort ahead of
orca-navigation-shortcuts, which broke the assertion there.

Co-authored-by: Orca <help@stably.ai>

* style: drop stray blank lines left by the rebase resolutions

Both sides of the agent-hooks and orca-runtime conflicts contributed a
trailing blank, which oxfmt rejects. Whitespace only.

Co-authored-by: Orca <help@stably.ai>

* test(plugins): stop the startup budget failing on machine load

P95 runs 16-34ms idle but exceeds the 50ms bound under full-suite
parallelism, so the gate flaked. Widen it to catch an order-of-magnitude
regression instead; the no-worker/no-plugin-code assertions are the real
guarantee. Verified a 400ms regression still fails.

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Orca <help@stably.ai>
2026-07-27 01:14:33 -07:00
Brennan Benson 43e481b1c3 Revert "Decouple feature copy from translated locale catalogs (#8488)" (#8500)
This reverts commit a5e9e139b1.
2026-07-12 23:43:07 -07:00
Brennan Benson a5e9e139b1 Decouple feature copy from translated locale catalogs (#8488)
* Decouple feature copy from locale catalogs

* Update PR workflow contract tests

* Address localization review findings

* Document localization cache context
2026-07-12 23:42:45 -07:00
Jinwoo HongandOrca 44d1e2760f Improve localization catalog sync workflow (#5110)
Co-authored-by: Orca <help@stably.ai>
2026-06-10 11:13:01 -07:00