diff --git a/lingui.config.ts b/lingui.config.ts new file mode 100644 index 00000000..39665c92 --- /dev/null +++ b/lingui.config.ts @@ -0,0 +1,19 @@ +import { defineConfig } from "@lingui/cli"; +import { formatter } from "@lingui/format-po"; + +// i18n is renderer-only: macros are expanded by Babel in the Vite pipeline, and +// the main process (tsdown) carries no catalogs. Adding a language is two steps: +// add its code to `locales` here (and to SUPPORTED_LOCALES in +// src/shared/locale.ts), then run `pnpm i18n:extract`. +export default defineConfig({ + sourceLocale: "en", + locales: ["en", "es", "ru", "uk", "zh-CN", "ja", "pt-BR", "de", "fr", "ko", "pl", "vi", "tr"], + catalogs: [ + { + path: "src/renderer/locales/{locale}/messages", + include: ["src/renderer"], + exclude: ["**/*.test.*", "**/node_modules/**"], + }, + ], + format: formatter({ lineNumbers: false }), +}); diff --git a/package.json b/package.json index 6e2936cf..bbc3400c 100644 --- a/package.json +++ b/package.json @@ -45,11 +45,13 @@ "lint:fix": "oxlint --type-aware --fix .", "fmt": "oxfmt --write .", "fmt:check": "oxfmt --check .", - "test": "vitest run", - "test:perf:cli-hook": "vitest run src/supervisor/runtime/cliHookEventChain.perf.test.ts", - "test:integration:providers": "vitest run --config vitest.integration.config.ts", + "test": "vitest run --configLoader runner", + "test:perf:cli-hook": "vitest run --configLoader runner src/supervisor/runtime/cliHookEventChain.perf.test.ts", + "test:integration:providers": "vitest run --configLoader runner --config vitest.integration.config.ts", "update-server": "node scripts/update-server.mjs", "db:clone": "node scripts/clone-db.mjs", + "i18n:extract": "lingui extract", + "i18n:extract:clean": "lingui extract --clean", "prepare": "husky" }, "dependencies": { @@ -68,6 +70,8 @@ "@heroui/styles": "^3.0.3", "@huggingface/transformers": "^4.2.0", "@lightcode/agents-usage": "workspace:*", + "@lingui/core": "6.2.0", + "@lingui/react": "6.2.0", "@monaco-editor/react": "^4.7.0", "@opencode-ai/sdk": "^1.14.48", "@sentry/electron": "^7.13.0", @@ -109,6 +113,10 @@ "devDependencies": { "@babel/core": "^7.29.0", "@electron/rebuild": "^4.0.4", + "@lingui/babel-plugin-lingui-macro": "6.2.0", + "@lingui/cli": "6.2.0", + "@lingui/format-po": "6.2.0", + "@lingui/vite-plugin": "6.2.0", "@rolldown/plugin-babel": "^0.2.3", "@sentry/cli": "^3.4.2", "@tailwindcss/postcss": "^4.2.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ccc690a2..50bbe82c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -71,6 +71,12 @@ importers: '@lightcode/agents-usage': specifier: workspace:* version: link:packages/agents-usage + '@lingui/core': + specifier: 6.2.0 + version: 6.2.0 + '@lingui/react': + specifier: 6.2.0 + version: 6.2.0(react@19.2.5) '@monaco-editor/react': specifier: ^4.7.0 version: 4.7.0(monaco-editor@0.55.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) @@ -189,6 +195,18 @@ importers: '@electron/rebuild': specifier: ^4.0.4 version: 4.0.4 + '@lingui/babel-plugin-lingui-macro': + specifier: 6.2.0 + version: 6.2.0 + '@lingui/cli': + specifier: 6.2.0 + version: 6.2.0 + '@lingui/format-po': + specifier: 6.2.0 + version: 6.2.0 + '@lingui/vite-plugin': + specifier: 6.2.0 + version: 6.2.0(@babel/core@7.29.7)(@lingui/babel-plugin-lingui-macro@6.2.0)(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.7)(@babel/runtime@7.29.7)(rolldown@1.0.3)(vite@8.0.16(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1)(yaml@2.9.0)))(rolldown@1.0.3)(vite@8.0.16(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1)(yaml@2.9.0)) '@rolldown/plugin-babel': specifier: ^0.2.3 version: 0.2.3(@babel/core@7.29.7)(@babel/runtime@7.29.7)(rolldown@1.0.3)(vite@8.0.16(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1)(yaml@2.9.0)) @@ -595,6 +613,10 @@ packages: '@chevrotain/utils@12.0.0': resolution: {integrity: sha512-lB59uJoaGIfOOL9knQqQRfhl9g7x8/wqFkp13zTdkRu1huG9kg6IJs1O8hqj9rs6h7orGxHJUKb+mX3rPbWGhA==} + '@colors/colors@1.5.0': + resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} + engines: {node: '>=0.1.90'} + '@csstools/color-helpers@6.0.2': resolution: {integrity: sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==} engines: {node: '>=20.19.0'} @@ -1152,6 +1174,14 @@ packages: resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} engines: {node: '>=18.0.0'} + '@jest/schemas@29.6.3': + resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/types@29.6.3': + resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -1172,6 +1202,69 @@ packages: resolution: {integrity: sha512-hloP58zRVCRSpgDxmqCWJNlizAlUgJFqG2ypq79DCvyv9tHjRYMDOcPFjzfl/A1/YxDvRCZz8wvZvmapQnKwFQ==} engines: {node: '>=12'} + '@lingui/babel-plugin-extract-messages@6.2.0': + resolution: {integrity: sha512-/iPL0DSvq6oZQjxLSHXofaxq18joGKEYzJjaZUWw9jdyWpRp1kNFMHPQVPbemVBR2PSJUNS7jlOu1iph7qtkrQ==} + engines: {node: '>=22.19.0'} + + '@lingui/babel-plugin-lingui-macro@6.2.0': + resolution: {integrity: sha512-sZqrd+RFLOePcimYoyNWs7n8brfkD5MXKb9trYNaV+/obt3C02LEpLrdIqUvykpn5+NgeZY6H0x/HqaXTil5vA==} + engines: {node: '>=22.19.0'} + + '@lingui/cli@6.2.0': + resolution: {integrity: sha512-47j2VnDCRAuxlwFnBshQqTGBmkiRvr2jUs7FzEHT0z/drCOhNwHoNCDDAGC/1SwsiK9kaeNZcObyZfrpe3z3PA==} + engines: {node: '>=22.19.0'} + hasBin: true + + '@lingui/conf@6.2.0': + resolution: {integrity: sha512-SVZlXH0MJgS6Cu9NFCtJeqT7VzbKp5VDQcd9j51x6lRCzknPJS4CjJ4GWzu0RHoTmtQyoADLzuQTPrzD0ommLA==} + engines: {node: '>=22.19.0'} + + '@lingui/core@6.2.0': + resolution: {integrity: sha512-iNxDy9+GfwBGi9YVgAt7q0V7hA7kwt5t+akScFWvIyzII0KpMd4oLq0JyPsedsKloWVmL8V0PuvtThM0Ja4SWg==} + engines: {node: '>=22.19.0'} + peerDependencies: + babel-plugin-macros: 2 || 3 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + + '@lingui/format-po@6.2.0': + resolution: {integrity: sha512-rchqiXGySJagw/sN+fL6Wi9A1hAooSO7B/5qPmHbH2DP5kGnMbE61MwqvhGaJQrWMtnac9AGz1PC4AKBWKVAZw==} + engines: {node: '>=22.19.0'} + + '@lingui/message-utils@6.2.0': + resolution: {integrity: sha512-oFmD6xYO40kC/7qBdxieEVekoYNI0KWtizCOanrlT6RXYPSwn5hH4Hf6u0mErEf23m/zNGDMO+D6YNQ4z7IxwQ==} + engines: {node: '>=22.19.0'} + + '@lingui/react@6.2.0': + resolution: {integrity: sha512-DDdzKgHG+j13qA4In3nqBCSJDhdW9baIx+0a9/5znoHlnJsR2VG3AqlAtCmuTMr7uhjzwuZYJgG2Ms718Rr39A==} + engines: {node: '>=22.19.0'} + peerDependencies: + babel-plugin-macros: 2 || 3 + react: ^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + + '@lingui/vite-plugin@6.2.0': + resolution: {integrity: sha512-Ti1wFsY40LP7+aXoooZ/fp1HqKLABBibaBNlboUYogsw6vZFatc3ZRYjOs6WcKozzlcawz8IeznGbCtAn3WVnA==} + engines: {node: '>=22.19.0'} + peerDependencies: + '@babel/core': ^7.29.6 + '@lingui/babel-plugin-lingui-macro': ^5 || ^6 + '@rolldown/plugin-babel': ^0.1.7 || ^0.2.0 + rolldown: ^1.0.0-rc.5 + vite: ^8.0.16 + peerDependenciesMeta: + '@babel/core': + optional: true + '@lingui/babel-plugin-lingui-macro': + optional: true + '@rolldown/plugin-babel': + optional: true + rolldown: + optional: true + '@malept/cross-spawn-promise@2.0.0': resolution: {integrity: sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg==} engines: {node: '>= 12.13.0'} @@ -1183,6 +1276,12 @@ packages: '@mermaid-js/parser@1.1.0': resolution: {integrity: sha512-gxK9ZX2+Fex5zu8LhRQoMeMPEHbc73UKZ0FQ54YrQtUxE1VVhMwzeNtKRPAu5aXks4FasbMe4xB4bWrmq6Jlxw==} + '@messageformat/date-skeleton@1.1.0': + resolution: {integrity: sha512-rmGAfB1tIPER+gh3p/RgA+PVeRE/gxuQ2w4snFWPF5xtb5mbWR7Cbw7wCOftcUypbD6HVoxrVdyyghPm3WzP5A==} + + '@messageformat/parser@5.1.1': + resolution: {integrity: sha512-3p0YRGCcTUCYvBKLIxtDDyrJ0YijGIwrTRu1DT8gIviIDZru8H23+FkY6MJBzM1n9n20CiM4VeDYuBsrrwnLjg==} + '@modelcontextprotocol/sdk@1.29.0': resolution: {integrity: sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==} engines: {node: '>=18'} @@ -2280,6 +2379,9 @@ packages: '@shikijs/vscode-textmate@10.0.2': resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} + '@sinclair/typebox@0.27.10': + resolution: {integrity: sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==} + '@sindresorhus/is@4.6.0': resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} engines: {node: '>=10'} @@ -2817,6 +2919,15 @@ packages: '@types/http-cache-semantics@4.2.0': resolution: {integrity: sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==} + '@types/istanbul-lib-coverage@2.0.6': + resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} + + '@types/istanbul-lib-report@3.0.3': + resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} + + '@types/istanbul-reports@3.0.4': + resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} + '@types/jsesc@2.5.1': resolution: {integrity: sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==} @@ -2888,6 +2999,12 @@ packages: '@types/verror@1.10.11': resolution: {integrity: sha512-RlDm9K7+o5stv0Co8i8ZRGxDbrTxhJtgjqjFyVh/tXQyl/rYtTKlnTvZ88oSTeYREWurwx20Js4kTuKCsFkUtg==} + '@types/yargs-parser@21.0.3': + resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} + + '@types/yargs@17.0.35': + resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==} + '@types/yauzl@2.10.3': resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} @@ -3293,6 +3410,10 @@ packages: resolution: {integrity: sha512-FxAv7HpHrXbh3aPo4o2qxHay2lkLY3x5Mw3KeE4KQE8ysVfziWeRZDwcjauvwBSGEC/nXUPzZy8zeh4HokqOnw==} engines: {node: '>=4'} + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + caniuse-lite@1.0.30001787: resolution: {integrity: sha512-mNcrMN9KeI68u7muanUpEejSLghOKlVhRqS/Za2IeyGllJ9I9otGpR9g3nsw7n4W378TE/LyIteA0+/FOZm4Kg==} @@ -3315,6 +3436,10 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} + chalk@5.6.2: + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + character-entities-html4@2.1.0: resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} @@ -3336,6 +3461,10 @@ packages: resolution: {integrity: sha512-csJvb+6kEiQaqo1woTdSAuOWdN0WTLIydkKrBnS+V5gZz0oqBrp4kQ35519QgK6TpBThiG3V1vNSHlIkv4AglQ==} engines: {node: '>=22.0.0'} + chokidar@5.0.0: + resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} + engines: {node: '>= 20.19.0'} + chownr@1.1.4: resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} @@ -3371,6 +3500,14 @@ packages: resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} engines: {node: '>=18'} + cli-spinners@3.4.0: + resolution: {integrity: sha512-bXfOC4QcT1tKXGorxL3wbJm6XJPDqEnij2gQ2m7ESQuE+/z9YFIWnl/5RpTiKWbMq3EVKR4fRLJGn6DVfu0mpw==} + engines: {node: '>=18.20'} + + cli-table3@0.6.5: + resolution: {integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==} + engines: {node: 10.* || >= 12.*} + cli-truncate@2.1.0: resolution: {integrity: sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==} engines: {node: '>=8'} @@ -3413,6 +3550,14 @@ packages: comma-separated-tokens@2.0.3: resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + commander@10.0.1: + resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} + engines: {node: '>=14'} + + commander@14.0.3: + resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} + engines: {node: '>=20'} + commander@5.1.0: resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==} engines: {node: '>= 6'} @@ -4543,6 +4688,10 @@ packages: resolution: {integrity: sha512-ERNhMg+i/XgDwPIPF3u24qpajVreaiSuvpb1Uu0jugw7KKcxGyCX8cgp8P5fwTmAuXku6beDHHECdKArjlg7tw==} engines: {node: '>=4'} + is-interactive@2.0.0: + resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==} + engines: {node: '>=12'} + is-ip@3.1.0: resolution: {integrity: sha512-35vd5necO7IitFPjd/YBeqwWnyDWbuLH9ZXQdMfDA8TEo7pv5X8yfrvVO3xbJbLUlERCMvf6X0hTUamQxCYJ9Q==} engines: {node: '>=8'} @@ -4581,6 +4730,10 @@ packages: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} + is-unicode-supported@2.1.0: + resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} + engines: {node: '>=18'} + isbinaryfile@4.0.10: resolution: {integrity: sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==} engines: {node: '>= 8.0.0'} @@ -4605,6 +4758,14 @@ packages: engines: {node: '>=10'} hasBin: true + jest-get-type@29.6.3: + resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-validate@29.7.0: + resolution: {integrity: sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jiti@2.6.1: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true @@ -4616,6 +4777,9 @@ packages: jose@6.2.3: resolution: {integrity: sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==} + js-sha256@0.10.1: + resolution: {integrity: sha512-5obBtsz9301ULlsgggLg542s/jqtddfOpV5KJc4hajc9JV8GeY2gZHSVpYBn4nWqAUTJ9v+xwtbJ1mIBgIH5Vw==} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -4704,6 +4868,10 @@ packages: lazy-val@1.0.5: resolution: {integrity: sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==} + leven@3.1.0: + resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} + engines: {node: '>=6'} + lightningcss-android-arm64@1.32.0: resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} engines: {node: '>= 12.0.0'} @@ -4778,6 +4946,10 @@ packages: resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} engines: {node: '>= 12.0.0'} + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + engines: {node: '>=14'} + linkifyjs@4.3.3: resolution: {integrity: sha512-P8aEP5U/D1/IlTY2OeYsErdwh9bGuLE30NcXtKEjgdHcahveQoQwM2yZNsioQHsWFz0P7KKudisbrzCgR0sDHg==} @@ -4806,6 +4978,10 @@ packages: lodash@4.18.1: resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} + log-symbols@7.0.1: + resolution: {integrity: sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg==} + engines: {node: '>=18'} + log-update@6.1.0: resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} engines: {node: '>=18'} @@ -5122,6 +5298,9 @@ packages: monaco-editor@0.55.1: resolution: {integrity: sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==} + moo@0.5.3: + resolution: {integrity: sha512-m2fmM2dDm7GZQsY7KK2cme8agi+AAljILjQnof7p1ZMDe6dQ4bdnSMx0cPppudoeNv5hEFQirN6u+O4fDE0IWA==} + motion-dom@12.40.0: resolution: {integrity: sha512-HxU3ZaBwNPVQUBQf1xxgq+7JrPNZvjLVxgbpEZL7RrWJnsxOf0/OM+yrHG9ogLQ31Do/r57Oz2gQWPK+6q62mg==} @@ -5206,6 +5385,10 @@ packages: resolution: {integrity: sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==} engines: {node: '>=0.10.0'} + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + normalize-url@6.1.0: resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==} engines: {node: '>=10'} @@ -5267,6 +5450,10 @@ packages: onnxruntime-web@1.26.0-dev.20260416-b7804b056c: resolution: {integrity: sha512-MD6Ss4GSpQBo6zqoJzyT9LRbKYs7x/JVN23FT24EcEvlqF4VuzPOeH6X38orZPKHQDbprn7K+SBpu0/mj2CQiw==} + ora@9.4.0: + resolution: {integrity: sha512-84cglkRILFxdtA8hAvLNdMrtBpPNBTrQ9/ulg0FA7xLMnD6mifv+enAIeRmvtv+WgdCE+LPGOfQmtJRrVaIVhQ==} + engines: {node: '>=20'} + orderedmap@2.1.1: resolution: {integrity: sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==} @@ -5401,6 +5588,10 @@ packages: resolution: {integrity: sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==} engines: {node: '>=10.4.0'} + pofile-ts@4.0.3: + resolution: {integrity: sha512-sz1pnjgEfPyZ+QvaeX3NtCmbYnEvG01LZRLoN/uXoLtPZtxCIH5IctL7yXXc0fFyk/fqV6K8g3hlNfr6IJwupA==} + engines: {node: '>=20'} + points-on-curve@0.2.0: resolution: {integrity: sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==} @@ -5450,6 +5641,10 @@ packages: resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + pretty-format@29.7.0: + resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + proc-log@6.1.0: resolution: {integrity: sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==} engines: {node: ^20.17.0 || >=22.9.0} @@ -5525,6 +5720,11 @@ packages: resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==} engines: {node: '>=10'} + pseudolocale@2.2.0: + resolution: {integrity: sha512-O+D2eU7fO9wVLqrohvt9V/9fwMadnJQ4jxwiK+LeNEqhMx8JYx4xQHkArDCJFAdPPOp/pQq6z5L37eBvAoc8jw==} + engines: {node: '>=16.0.0'} + hasBin: true + pseudomap@1.0.2: resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==} @@ -5593,6 +5793,9 @@ packages: react-is@17.0.2: resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + react-markdown@10.1.0: resolution: {integrity: sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==} peerDependencies: @@ -5631,6 +5834,10 @@ packages: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} + readdirp@5.0.0: + resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} + engines: {node: '>= 20.19.0'} + redent@3.0.0: resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} engines: {node: '>=8'} @@ -5929,6 +6136,10 @@ packages: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} + space-separated-tokens@2.0.2: resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} @@ -5952,6 +6163,10 @@ packages: std-env@4.0.0: resolution: {integrity: sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==} + stdin-discarder@0.3.2: + resolution: {integrity: sha512-eCPu1qRxPVkl5605OTWF8Wz40b4Mf45NY5LQmVPQ599knfs5QhASUm9GbJ5BDMDOXgrnh0wyEdvzmL//YMlw0A==} + engines: {node: '>=18'} + streamdown@2.5.0: resolution: {integrity: sha512-/tTnURfIOxZK/pqJAxsfCvETG/XCJHoWnk3jq9xLcuz6CSpnjjuxSRBTTL4PKGhxiZQf0lqPxGhImdpwcZ2XwA==} peerDependencies: @@ -6610,6 +6825,10 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + yoctocolors@2.1.2: + resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} + engines: {node: '>=18'} + zod-to-json-schema@3.25.2: resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==} peerDependencies: @@ -6944,6 +7163,9 @@ snapshots: '@chevrotain/utils@12.0.0': {} + '@colors/colors@1.5.0': + optional: true + '@csstools/color-helpers@6.0.2': {} '@csstools/css-calc@3.2.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': @@ -7497,6 +7719,19 @@ snapshots: dependencies: minipass: 7.1.3 + '@jest/schemas@29.6.3': + dependencies: + '@sinclair/typebox': 0.27.10 + + '@jest/types@29.6.3': + dependencies: + '@jest/schemas': 29.6.3 + '@types/istanbul-lib-coverage': 2.0.6 + '@types/istanbul-reports': 3.0.4 + '@types/node': 25.9.2 + '@types/yargs': 17.0.35 + chalk: 4.1.2 + '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -7520,6 +7755,93 @@ snapshots: dependencies: jsbi: 4.3.2 + '@lingui/babel-plugin-extract-messages@6.2.0': {} + + '@lingui/babel-plugin-lingui-macro@6.2.0': + dependencies: + '@babel/core': 7.29.7 + '@babel/types': 7.29.0 + '@lingui/conf': 6.2.0 + '@lingui/message-utils': 6.2.0 + transitivePeerDependencies: + - supports-color + + '@lingui/cli@6.2.0': + dependencies: + '@babel/core': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.0 + '@lingui/babel-plugin-extract-messages': 6.2.0 + '@lingui/babel-plugin-lingui-macro': 6.2.0 + '@lingui/conf': 6.2.0 + '@lingui/core': 6.2.0 + '@lingui/format-po': 6.2.0 + '@lingui/message-utils': 6.2.0 + chokidar: 5.0.0 + cli-table3: 0.6.5 + commander: 14.0.3 + esbuild: 0.28.1 + jiti: 2.6.1 + micromatch: 4.0.8 + ms: 2.1.3 + normalize-path: 3.0.0 + ora: 9.4.0 + pseudolocale: 2.2.0 + source-map: 0.7.6 + tinypool: 2.1.0 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + + '@lingui/conf@6.2.0': + dependencies: + jest-validate: 29.7.0 + jiti: 2.6.1 + lilconfig: 3.1.3 + normalize-path: 3.0.0 + + '@lingui/core@6.2.0': + dependencies: + '@lingui/babel-plugin-lingui-macro': 6.2.0 + '@lingui/message-utils': 6.2.0 + transitivePeerDependencies: + - supports-color + + '@lingui/format-po@6.2.0': + dependencies: + '@lingui/conf': 6.2.0 + '@lingui/message-utils': 6.2.0 + pofile-ts: 4.0.3 + + '@lingui/message-utils@6.2.0': + dependencies: + '@messageformat/date-skeleton': 1.1.0 + '@messageformat/parser': 5.1.1 + js-sha256: 0.10.1 + + '@lingui/react@6.2.0(react@19.2.5)': + dependencies: + '@lingui/babel-plugin-lingui-macro': 6.2.0 + '@lingui/core': 6.2.0 + react: 19.2.5 + transitivePeerDependencies: + - supports-color + + '@lingui/vite-plugin@6.2.0(@babel/core@7.29.7)(@lingui/babel-plugin-lingui-macro@6.2.0)(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.7)(@babel/runtime@7.29.7)(rolldown@1.0.3)(vite@8.0.16(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1)(yaml@2.9.0)))(rolldown@1.0.3)(vite@8.0.16(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1)(yaml@2.9.0))': + dependencies: + '@lingui/cli': 6.2.0 + '@lingui/conf': 6.2.0 + vite: 8.0.16(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1)(yaml@2.9.0) + optionalDependencies: + '@babel/core': 7.29.7 + '@lingui/babel-plugin-lingui-macro': 6.2.0 + '@rolldown/plugin-babel': 0.2.3(@babel/core@7.29.7)(@babel/runtime@7.29.7)(rolldown@1.0.3)(vite@8.0.16(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1)(yaml@2.9.0)) + rolldown: 1.0.3 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + '@malept/cross-spawn-promise@2.0.0': dependencies: cross-spawn: 7.0.6 @@ -7537,6 +7859,12 @@ snapshots: dependencies: langium: 4.2.3 + '@messageformat/date-skeleton@1.1.0': {} + + '@messageformat/parser@5.1.1': + dependencies: + moo: 0.5.3 + '@modelcontextprotocol/sdk@1.29.0(zod@4.4.2)': dependencies: '@hono/node-server': 1.19.14(hono@4.12.25) @@ -8572,6 +8900,8 @@ snapshots: '@shikijs/vscode-textmate@10.0.2': {} + '@sinclair/typebox@0.27.10': {} + '@sindresorhus/is@4.6.0': {} '@spectrum-icons/ui@3.7.0(@adobe/react-spectrum@3.47.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': @@ -9144,6 +9474,16 @@ snapshots: '@types/http-cache-semantics@4.2.0': {} + '@types/istanbul-lib-coverage@2.0.6': {} + + '@types/istanbul-lib-report@3.0.3': + dependencies: + '@types/istanbul-lib-coverage': 2.0.6 + + '@types/istanbul-reports@3.0.4': + dependencies: + '@types/istanbul-lib-report': 3.0.3 + '@types/jsesc@2.5.1': {} '@types/keyv@3.1.4': @@ -9232,6 +9572,12 @@ snapshots: '@types/verror@1.10.11': optional: true + '@types/yargs-parser@21.0.3': {} + + '@types/yargs@17.0.35': + dependencies: + '@types/yargs-parser': 21.0.3 + '@types/yauzl@2.10.3': dependencies: '@types/node': 25.9.1 @@ -9675,6 +10021,8 @@ snapshots: camelcase@4.1.0: {} + camelcase@6.3.0: {} + caniuse-lite@1.0.30001787: {} ccount@2.0.1: {} @@ -9697,6 +10045,8 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 + chalk@5.6.2: {} + character-entities-html4@2.1.0: {} character-entities-legacy@3.0.0: {} @@ -9718,6 +10068,10 @@ snapshots: '@chevrotain/types': 12.0.0 '@chevrotain/utils': 12.0.0 + chokidar@5.0.0: + dependencies: + readdirp: 5.0.0 + chownr@1.1.4: {} chownr@3.0.0: {} @@ -9740,6 +10094,14 @@ snapshots: dependencies: restore-cursor: 5.1.0 + cli-spinners@3.4.0: {} + + cli-table3@0.6.5: + dependencies: + string-width: 4.2.3 + optionalDependencies: + '@colors/colors': 1.5.0 + cli-truncate@2.1.0: dependencies: slice-ansi: 3.0.0 @@ -9783,6 +10145,10 @@ snapshots: comma-separated-tokens@2.0.3: {} + commander@10.0.1: {} + + commander@14.0.3: {} + commander@5.1.0: {} commander@7.2.0: {} @@ -10978,6 +11344,8 @@ snapshots: global-dirs: 0.1.1 is-path-inside: 1.0.1 + is-interactive@2.0.0: {} + is-ip@3.1.0: dependencies: ip-regex: 4.3.0 @@ -11002,6 +11370,8 @@ snapshots: is-stream@2.0.1: {} + is-unicode-supported@2.1.0: {} + isbinaryfile@4.0.10: {} isbinaryfile@5.0.7: {} @@ -11018,6 +11388,17 @@ snapshots: filelist: 1.0.6 picocolors: 1.1.1 + jest-get-type@29.6.3: {} + + jest-validate@29.7.0: + dependencies: + '@jest/types': 29.6.3 + camelcase: 6.3.0 + chalk: 4.1.2 + jest-get-type: 29.6.3 + leven: 3.1.0 + pretty-format: 29.7.0 + jiti@2.6.1: {} joi@18.1.2: @@ -11032,6 +11413,8 @@ snapshots: jose@6.2.3: {} + js-sha256@0.10.1: {} + js-tokens@4.0.0: {} js-yaml@4.2.0: @@ -11135,6 +11518,8 @@ snapshots: lazy-val@1.0.5: {} + leven@3.1.0: {} + lightningcss-android-arm64@1.32.0: optional: true @@ -11184,6 +11569,8 @@ snapshots: lightningcss-win32-arm64-msvc: 1.32.0 lightningcss-win32-x64-msvc: 1.32.0 + lilconfig@3.1.3: {} + linkifyjs@4.3.3: {} lint-staged@17.0.4: @@ -11213,6 +11600,11 @@ snapshots: lodash@4.18.1: {} + log-symbols@7.0.1: + dependencies: + is-unicode-supported: 2.1.0 + yoctocolors: 2.1.2 + log-update@6.1.0: dependencies: ansi-escapes: 7.3.0 @@ -11733,6 +12125,8 @@ snapshots: dompurify: 3.4.10 marked: 14.0.0 + moo@0.5.3: {} + motion-dom@12.40.0: dependencies: motion-utils: 12.39.0 @@ -11819,6 +12213,8 @@ snapshots: dependencies: remove-trailing-separator: 1.1.0 + normalize-path@3.0.0: {} + normalize-url@6.1.0: {} npm-run-path@2.0.2: @@ -11880,6 +12276,17 @@ snapshots: platform: 1.3.6 protobufjs: 7.6.4 + ora@9.4.0: + dependencies: + chalk: 5.6.2 + cli-cursor: 5.0.0 + cli-spinners: 3.4.0 + is-interactive: 2.0.0 + is-unicode-supported: 2.1.0 + log-symbols: 7.0.1 + stdin-discarder: 0.3.2 + string-width: 8.2.1 + orderedmap@2.1.1: {} oxfmt@0.47.0: @@ -12033,6 +12440,8 @@ snapshots: base64-js: 1.5.1 xmlbuilder: 15.1.1 + pofile-ts@4.0.3: {} + points-on-curve@0.2.0: {} points-on-path@0.2.1: @@ -12094,6 +12503,12 @@ snapshots: ansi-styles: 5.2.0 react-is: 17.0.2 + pretty-format@29.7.0: + dependencies: + '@jest/schemas': 29.6.3 + ansi-styles: 5.2.0 + react-is: 18.3.1 + proc-log@6.1.0: {} progress@2.0.3: {} @@ -12214,6 +12629,10 @@ snapshots: proxy-from-env@2.1.0: {} + pseudolocale@2.2.0: + dependencies: + commander: 10.0.1 + pseudomap@1.0.2: {} pump@3.0.4: @@ -12332,6 +12751,8 @@ snapshots: react-is@17.0.2: {} + react-is@18.3.1: {} + react-markdown@10.1.0(@types/react@19.2.14)(react@19.2.5): dependencies: '@types/hast': 3.0.4 @@ -12411,6 +12832,8 @@ snapshots: string_decoder: 1.3.0 util-deprecate: 1.0.2 + readdirp@5.0.0: {} + redent@3.0.0: dependencies: indent-string: 4.0.0 @@ -12817,6 +13240,8 @@ snapshots: source-map@0.6.1: {} + source-map@0.7.6: {} + space-separated-tokens@2.0.2: {} sprintf-js@1.1.3: {} @@ -12831,6 +13256,8 @@ snapshots: std-env@4.0.0: {} + stdin-discarder@0.3.2: {} + streamdown@2.5.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5): dependencies: clsx: 2.1.1 @@ -13452,6 +13879,8 @@ snapshots: yocto-queue@0.1.0: {} + yoctocolors@2.1.2: {} + zod-to-json-schema@3.25.2(zod@4.4.2): dependencies: zod: 4.4.2 diff --git a/src/main/sharedSettingsFile.test.ts b/src/main/sharedSettingsFile.test.ts index ad6567c2..b36f75b1 100644 --- a/src/main/sharedSettingsFile.test.ts +++ b/src/main/sharedSettingsFile.test.ts @@ -35,6 +35,8 @@ describe("sharedSettingsFile", () => { writeSharedSettingsFile(settingsPath, { themeMode: "dark", themePreset: "default", + locale: "system", + gitTextLanguage: "en", terminalPosition: "right", commitGenProvider: "auto", commitGenModel: "", @@ -124,6 +126,8 @@ describe("sharedSettingsFile", () => { expect(readSharedSettingsFile(settingsPath)).toEqual({ themeMode: "dark", themePreset: "default", + locale: "system", + gitTextLanguage: "en", terminalPosition: "right", commitGenProvider: "auto", commitGenModel: "", diff --git a/src/renderer/RendererCrashScreen.tsx b/src/renderer/RendererCrashScreen.tsx index 1891ee51..65a614b9 100644 --- a/src/renderer/RendererCrashScreen.tsx +++ b/src/renderer/RendererCrashScreen.tsx @@ -1,7 +1,10 @@ import { Component, useState, type ErrorInfo, type ReactNode } from "react"; import { Copy, RefreshCw } from "lucide-react"; +import { msg } from "@lingui/core/macro"; +import type { MessageDescriptor } from "@lingui/core"; import { Button } from "./components/common"; import { captureRendererException } from "./diagnostics/sentry"; +import { i18n } from "./i18n/i18n"; export type RendererCrashKind = "bootstrap" | "react" | "uncaught" | "unhandled-rejection"; @@ -117,16 +120,16 @@ export function formatRendererCrashReport(report: RendererCrashReport): string { return lines.filter((line): line is string => line !== null).join("\n"); } -function crashTitle(kind: RendererCrashKind): string { +function crashTitle(kind: RendererCrashKind): MessageDescriptor { switch (kind) { case "bootstrap": - return "Renderer failed during startup"; + return msg`Renderer failed during startup`; case "react": - return "Renderer hit a React error"; + return msg`Renderer hit a React error`; case "unhandled-rejection": - return "Renderer hit an unhandled promise rejection"; + return msg`Renderer hit an unhandled promise rejection`; case "uncaught": - return "Renderer hit an uncaught error"; + return msg`Renderer hit an uncaught error`; } } @@ -153,25 +156,26 @@ export function RendererCrashScreen(props: RendererCrashScreenProps) {
-

Renderer crashed

-

{crashTitle(report.kind)}

+

{i18n._(msg`Renderer crashed`)}

+

{i18n._(crashTitle(report.kind))}

- The normal app shell could not render. The diagnostics below are shown before reload - so the failure can be investigated. + {i18n._( + msg`The normal app shell could not render. The diagnostics below are shown before reload so the failure can be investigated.`, + )}

diff --git a/src/renderer/actions/agentLoginActions.ts b/src/renderer/actions/agentLoginActions.ts index 2f0a552d..cb9715c9 100644 --- a/src/renderer/actions/agentLoginActions.ts +++ b/src/renderer/actions/agentLoginActions.ts @@ -1,7 +1,9 @@ import { toast } from "@heroui/react"; +import { msg } from "@lingui/core/macro"; import type { Project, ProjectLocation } from "@/shared/contracts"; import { stripAnsi } from "@/shared/ansi"; import { readBridge } from "@/renderer/bridge"; +import { i18n } from "@/renderer/i18n/i18n"; import { useAppStore } from "@/renderer/state/appStore"; import { useDevTerminalStore } from "@/renderer/state/devTerminalStore"; import { useLoginTerminalStore } from "@/renderer/state/loginTerminalStore"; @@ -45,7 +47,7 @@ export function runAgentLoginCommand(input: { }): boolean { const project = input.project ?? resolveLoginProject(); if (!project) { - toast.warning("Add a project before signing in."); + toast.warning(i18n._(msg`Add a project before signing in.`)); return false; } @@ -120,7 +122,9 @@ export function runAgentLoginCommand(input: { // (and leave callers' pending UI stuck). Tear it down and report failure. stopWatching(); fireOnce(-1); - toast.danger(error instanceof Error ? error.message : `Unable to open ${input.label} login.`); + toast.danger( + error instanceof Error ? error.message : i18n._(msg`Unable to open ${input.label} login.`), + ); useLoginTerminalStore.getState().close(); }); writeScriptToShell(shellId, script); @@ -144,7 +148,7 @@ export function runAgentInstallCommand(input: { }): boolean { const project = input.project ?? resolveLoginProject(); if (!project) { - toast.warning("Add a project before installing an agent."); + toast.warning(i18n._(msg`Add a project before installing an agent.`)); return false; } @@ -201,7 +205,9 @@ export function runAgentInstallCommand(input: { .catch((error) => { stopWatching(); fireOnce(-1); - toast.danger(error instanceof Error ? error.message : `Unable to install ${input.label}.`); + toast.danger( + error instanceof Error ? error.message : i18n._(msg`Unable to install ${input.label}.`), + ); useLoginTerminalStore.getState().close(); }); writeScriptToShell(shellId, script); diff --git a/src/renderer/actions/worktreeActions.ts b/src/renderer/actions/worktreeActions.ts index 3fb2f213..ec2e5bff 100644 --- a/src/renderer/actions/worktreeActions.ts +++ b/src/renderer/actions/worktreeActions.ts @@ -1,8 +1,10 @@ import { toast } from "@heroui/react"; +import { msg } from "@lingui/core/macro"; import { buildWorktreeLocation } from "@/shared/worktree"; import { errorDetail } from "@/shared/messages"; import type { Project } from "@/shared/contracts"; import { readBridge } from "@/renderer/bridge"; +import { i18n } from "@/renderer/i18n/i18n"; import { useAppStore } from "@/renderer/state/appStore"; import { useDevTerminalStore } from "@/renderer/state/devTerminalStore"; import { useFileEditorStore } from "@/renderer/state/fileEditorStore"; @@ -59,7 +61,7 @@ export async function performWorktreeRemoval( } catch (err: unknown) { const detail = errorDetail(err); console.warn(`[renderer] failed to remove worktree ${worktreePath}:`, detail); - toast.danger(detail || "Unable to remove worktree."); + toast.danger(detail || i18n._(msg`Unable to remove worktree.`)); return; } diff --git a/src/renderer/app.test.tsx b/src/renderer/app.test.tsx index bb453684..88ba3c13 100644 --- a/src/renderer/app.test.tsx +++ b/src/renderer/app.test.tsx @@ -1,5 +1,6 @@ import { Fragment, type ReactNode } from "react"; -import { act, fireEvent, render, screen, waitFor } from "@testing-library/react"; +import { act, fireEvent, screen, waitFor } from "@testing-library/react"; +import { renderWithI18n as render } from "@/renderer/testUtils/i18n"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { useAppStore } from "./state/appStore"; import { useGitStore } from "./state/gitStore"; diff --git a/src/renderer/app.tsx b/src/renderer/app.tsx index bf5c8358..e70fc5ba 100644 --- a/src/renderer/app.tsx +++ b/src/renderer/app.tsx @@ -1,4 +1,5 @@ import { toast } from "@heroui/react"; +import { Trans } from "@lingui/react/macro"; import { useEffect } from "react"; import { PixelLoader } from "./components/common"; import { msg } from "@/shared/messages"; @@ -272,7 +273,9 @@ export function App() {
-

Loading…

+

+ Loading… +

diff --git a/src/renderer/commands/CommandPalette.tsx b/src/renderer/commands/CommandPalette.tsx index 4768a880..cc614562 100644 --- a/src/renderer/commands/CommandPalette.tsx +++ b/src/renderer/commands/CommandPalette.tsx @@ -1,5 +1,7 @@ import { useEffect, useRef, useState } from "react"; import { Input, Label, Modal } from "@heroui/react"; +import { Trans, useLingui } from "@lingui/react/macro"; +import type { MessageDescriptor } from "@lingui/core"; import { Command, Search } from "lucide-react"; import { readBridge } from "@/renderer/bridge"; import { useAppStore } from "@/renderer/state/appStore"; @@ -18,6 +20,7 @@ import { const MAX_VISIBLE_COMMANDS = 80; export function CommandPalette() { + const { t } = useLingui(); const isOpen = useCommandPaletteStore((state) => state.isOpen); const close = useCommandPaletteStore((state) => state.close); const keybindings = useKeybindingStore((state) => state.keybindings); @@ -37,7 +40,9 @@ export function CommandPalette() { const commands = buildCommandRegistry().filter((command) => isCommandAvailable(command, whenContext), ); - const filteredCommands = filterCommands(commands, query).slice(0, MAX_VISIBLE_COMMANDS); + const resolve = (value: string | MessageDescriptor): string => + typeof value === "string" ? value : t(value); + const filteredCommands = filterCommands(commands, query, resolve).slice(0, MAX_VISIBLE_COMMANDS); const activeCommand = filteredCommands[activeIndex]; useEffect(() => { @@ -76,9 +81,12 @@ export function CommandPalette() { { setQuery(event.target.value); @@ -101,7 +109,7 @@ export function CommandPalette() {
{filteredCommands.length > 0 ? ( -
+
{filteredCommands.map((command, index) => { const shortcut = shortcutForCommand(command.id, keybindings); return ( @@ -118,9 +126,9 @@ export function CommandPalette() { > - + - {command.subtitle ?? command.group} + {resolve(command.subtitle ?? command.group)} {shortcut ? ( @@ -133,7 +141,9 @@ export function CommandPalette() { })}
) : ( -
No commands found
+
+ No commands found +
)}
@@ -142,16 +152,20 @@ export function CommandPalette() { ); } -function filterCommands(commands: AppCommand[], query: string): AppCommand[] { +function filterCommands( + commands: AppCommand[], + query: string, + resolve: (value: string | MessageDescriptor) => string, +): AppCommand[] { const normalized = query.trim().toLowerCase(); if (!normalized) return commands; const terms = normalized.split(/\s+/); return commands.filter((command) => { const haystack = [ command.id, - command.title, - command.group, - command.subtitle ?? "", + resolve(command.title), + resolve(command.group), + command.subtitle ? resolve(command.subtitle) : "", ...(command.keywords ?? []), ] .join(" ") diff --git a/src/renderer/commands/registry.ts b/src/renderer/commands/registry.ts index 3390ffca..2064ff96 100644 --- a/src/renderer/commands/registry.ts +++ b/src/renderer/commands/registry.ts @@ -1,7 +1,10 @@ import { toast } from "@heroui/react"; +import { msg } from "@lingui/core/macro"; +import type { MessageDescriptor } from "@lingui/core"; import { buildWorktreeLocation } from "@/shared/worktree"; import type { AgentSlashCommand, Project, Thread } from "@/shared/contracts"; import { readBridge } from "@/renderer/bridge"; +import { i18n } from "@/renderer/i18n/i18n"; import { captureThreadInputSubmitted } from "@/renderer/analytics/posthog"; import { getCurrentProjectId } from "@/renderer/actions/currentProject"; import { @@ -24,9 +27,9 @@ import { evaluateWhenClause } from "./when"; export interface AppCommand { id: string; - title: string; - group: string; - subtitle?: string; + title: string | MessageDescriptor; + group: string | MessageDescriptor; + subtitle?: string | MessageDescriptor; keywords?: string[]; when?: string; run: (args?: unknown) => void | Promise; @@ -82,20 +85,20 @@ function baseCommands(): AppCommand[] { return [ { id: "palette.open", - title: "Open Command Palette", + title: msg`Open Command Palette`, group: "Lightcode", run: () => useCommandPaletteStore.getState().open(), }, { id: "settings.open", - title: "Open Settings", + title: msg`Open Settings`, group: "Lightcode", run: openSettings, }, { id: "project.settings.open", - title: "Open Project Settings", - group: "Project", + title: msg`Open Project Settings`, + group: msg`Project`, when: "hasProject", run: () => { const project = resolveActiveContext().project; @@ -104,21 +107,21 @@ function baseCommands(): AppCommand[] { }, { id: "thread.new", - title: "New Thread", - group: "Thread", + title: msg`New Thread`, + group: msg`Thread`, when: "hasProject", run: () => openNewThread(resolveActiveContext().project?.id), }, { id: "thread.search.open", - title: "Search Threads", - group: "Thread", + title: msg`Search Threads`, + group: msg`Thread`, run: () => usePanelStore.getState().openThreadSearch(), }, { id: "terminal.toggle", - title: "Toggle Terminal", - group: "Terminal", + title: msg`Toggle Terminal`, + group: msg`Terminal`, when: "hasProject", run: () => { const active = resolveActiveContext(); @@ -132,15 +135,15 @@ function baseCommands(): AppCommand[] { }, { id: "terminal.command.run", - title: "Run Terminal Command", - group: "Terminal", + title: msg`Run Terminal Command`, + group: msg`Terminal`, when: "hasProject", run: (args) => runTerminalCommand(args), }, { id: "files.open", - title: "Open Files", - group: "Project", + title: msg`Open Files`, + group: msg`Project`, when: "hasProject", run: () => { const active = resolveActiveContext(); @@ -149,8 +152,8 @@ function baseCommands(): AppCommand[] { }, { id: "git.open", - title: "Open Git Review", - group: "Project", + title: msg`Open Git Review`, + group: msg`Project`, when: "hasProject", run: () => { const active = resolveActiveContext(); @@ -159,15 +162,15 @@ function baseCommands(): AppCommand[] { }, { id: "pane.close", - title: "Close Pane", - group: "Thread", + title: msg`Close Pane`, + group: msg`Thread`, when: "threadView", run: closeFocusedPane, }, { id: "editor.save", - title: "Save File", - group: "Editor", + title: msg`Save File`, + group: msg`Editor`, when: "editorOpen", run: () => { const editor = useFileEditorStore.getState(); @@ -176,22 +179,23 @@ function baseCommands(): AppCommand[] { }, { id: "editor.close", - title: "Close Editor Tab", - group: "Editor", + title: msg`Close Editor Tab`, + group: msg`Editor`, when: "editorOpen", run: () => { const editor = useFileEditorStore.getState(); const path = editor.activePath; if (!path) return; const buffer = editor.buffers[path]; - if (buffer?.isDirty && !window.confirm(`Discard unsaved changes in ${path}?`)) return; + if (buffer?.isDirty && !window.confirm(i18n._(msg`Discard unsaved changes in ${path}?`))) + return; editor.closeTab(path); }, }, { id: "editor.open", - title: "Open File", - group: "Editor", + title: msg`Open File`, + group: msg`Editor`, when: "hasProject", run: (args) => openFileFromArgs(args), }, @@ -205,7 +209,7 @@ function projectScriptCommands(): AppCommand[] { const command: AppCommand = { id: `script.${action.id}.run`, title: action.name, - group: "Scripts", + group: msg`Scripts`, keywords: [action.command, "project action", "script"], when: "hasProject", run: () => @@ -227,7 +231,7 @@ function chatCommand(command: AgentSlashCommand, thread: Thread): AppCommand { return { id: `chat.command.${command.id}`, title: `/${command.id}`, - group: "Chat Commands", + group: msg`Chat Commands`, subtitle: command.description ?? command.label, keywords: [command.label, command.description ?? ""], when: "hasThread", @@ -287,7 +291,9 @@ function openFileFromArgs(args: unknown): void { void useFileEditorStore .getState() .openFile(args.path, "fullscreen", false, readLineNumber(args)) - .catch((error) => toast.danger(error instanceof Error ? error.message : "Unable to open file")); + .catch((error) => + toast.danger(error instanceof Error ? error.message : i18n._(msg`Unable to open file`)), + ); } function readLineNumber(args: Record): { lineNumber?: number } | undefined { diff --git a/src/renderer/components/common/BranchSelector/BranchSelector.test.tsx b/src/renderer/components/common/BranchSelector/BranchSelector.test.tsx index 002b7b1e..be241746 100644 --- a/src/renderer/components/common/BranchSelector/BranchSelector.test.tsx +++ b/src/renderer/components/common/BranchSelector/BranchSelector.test.tsx @@ -1,4 +1,5 @@ -import { fireEvent, render, screen, waitFor } from "@testing-library/react"; +import { fireEvent, screen, waitFor } from "@testing-library/react"; +import { renderWithI18n as render } from "@/renderer/testUtils/i18n"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { BranchSelector } from "./BranchSelector"; diff --git a/src/renderer/components/common/BranchSelector/BranchSelector.tsx b/src/renderer/components/common/BranchSelector/BranchSelector.tsx index 6cbc19bb..7925d169 100644 --- a/src/renderer/components/common/BranchSelector/BranchSelector.tsx +++ b/src/renderer/components/common/BranchSelector/BranchSelector.tsx @@ -1,4 +1,5 @@ import { type ReactNode, useEffect, useRef, useState } from "react"; +import { Plural, Trans, useLingui } from "@lingui/react/macro"; import { ChevronDown, GitBranch, GitFork, Search } from "lucide-react"; import { Popover, toast, Tooltip } from "@heroui/react"; import type { GitBranchInfo } from "@/shared/contracts"; @@ -77,6 +78,7 @@ export function BranchSelector(props: BranchSelectorProps) { compact = false, } = props; const triggerIconSize = compact ? "size-3" : "size-3.5"; + const { t } = useLingui(); const [isOpen, setIsOpen] = useState(false); const [search, setSearch] = useState(""); @@ -166,7 +168,7 @@ export function BranchSelector(props: BranchSelectorProps) { setPendingDelete({ branch, ...(worktreePath ? { worktreePath } : {}), - threadIds: threads.map((t) => t.id), + threadIds: threads.map((thread) => thread.id), threadCount: threads.length, }); } @@ -246,11 +248,11 @@ export function BranchSelector(props: BranchSelectorProps) { }); if (result.changesTransferred === false) { toast.danger( - `Created a worktree on "${newBranch}", but the changes conflicted and remain in a git stash — resolve them in the worktree.`, + t`Created a worktree on "${newBranch}", but the changes conflicted and remain in a git stash — resolve them in the worktree.`, ); } else { toast.success( - `Moved your changes into a new worktree on "${newBranch}". "${currentBranch}" is now clean.`, + t`Moved your changes into a new worktree on "${newBranch}". "${currentBranch}" is now clean.`, ); } } catch (error) { @@ -268,7 +270,7 @@ export function BranchSelector(props: BranchSelectorProps) { {trigger ?? (
{items.length === 0 ? ( -
No models found
+
+ No models found +
) : ( void; }) { const { domIdPrefix, items, selectedKeys, scrollRef, toggleFavorite, onSelect } = props; + const { t } = useLingui(); const [visibleRow, setVisibleRow] = useState(0); const [scrollTop, setScrollTop] = useState(0); const [activeRowId, setActiveRowId] = useState(() => { @@ -596,7 +601,7 @@ function WindowedProviderModelList(props: {
= 0 ? `${domIdPrefix}-${items[activeIndex]?.id}` : undefined } @@ -765,7 +770,7 @@ function WindowedProviderModelList(props: { {item.hideFavoriteToggle ? null : (
); } diff --git a/src/renderer/components/common/ProviderModelMenu/parts/buildItems.ts b/src/renderer/components/common/ProviderModelMenu/parts/buildItems.ts index 3b766d8f..b3f0d6fb 100644 --- a/src/renderer/components/common/ProviderModelMenu/parts/buildItems.ts +++ b/src/renderer/components/common/ProviderModelMenu/parts/buildItems.ts @@ -1,3 +1,5 @@ +import { msg } from "@lingui/core/macro"; +import type { MessageDescriptor } from "@lingui/core"; import { baseAgentKind, type AgentCapability, @@ -396,7 +398,7 @@ export function buildProviderModelItems(input: BuildProviderModelItemsInput): Pr function pushShortcutSection( sectionId: string, - headerLabel: string, + headerLabel: MessageDescriptor, refs: readonly ModelRef[], ): void { // Favorites/recents store one entry per (agentKind, modelId, presentationMode). @@ -447,14 +449,14 @@ export function buildProviderModelItems(input: BuildProviderModelItemsInput): Pr if (!singleProviderMode) { if (favorites?.length) { - pushShortcutSection("fav", "Favorites", favorites); + pushShortcutSection("fav", msg`Favorites`, favorites); } if (recents?.length) { const filteredRecents = recents .filter((r) => !sectionFavoriteSet.has(refKey(r))) .slice(0, recentsLimit); if (filteredRecents.length > 0) { - pushShortcutSection("recent", "Recent", filteredRecents); + pushShortcutSection("recent", msg`Recent`, filteredRecents); } } } diff --git a/src/renderer/components/common/ProviderModelMenu/parts/types.ts b/src/renderer/components/common/ProviderModelMenu/parts/types.ts index af3b689f..124b79b4 100644 --- a/src/renderer/components/common/ProviderModelMenu/parts/types.ts +++ b/src/renderer/components/common/ProviderModelMenu/parts/types.ts @@ -1,9 +1,10 @@ +import type { MessageDescriptor } from "@lingui/core"; import type { ThreadPresentationMode } from "@/shared/contracts"; export interface ProviderModelHeaderPlain { type: "header-plain"; id: string; - label: string; + label: MessageDescriptor; } export interface ProviderModelHeaderProvider { diff --git a/src/renderer/components/composer/AttachmentBar.test.tsx b/src/renderer/components/composer/AttachmentBar.test.tsx index 4c018084..28f14c26 100644 --- a/src/renderer/components/composer/AttachmentBar.test.tsx +++ b/src/renderer/components/composer/AttachmentBar.test.tsx @@ -1,5 +1,6 @@ -import { fireEvent, render, screen } from "@testing-library/react"; +import { fireEvent, screen } from "@testing-library/react"; import { describe, expect, it, vi } from "vitest"; +import { renderWithI18n as render } from "@/renderer/testUtils/i18n"; import { AttachmentBar } from "./AttachmentBar"; import type { Attachment } from "./useAttachments"; diff --git a/src/renderer/components/composer/AttachmentBar.tsx b/src/renderer/components/composer/AttachmentBar.tsx index 96f9a2a3..ce4d2d24 100644 --- a/src/renderer/components/composer/AttachmentBar.tsx +++ b/src/renderer/components/composer/AttachmentBar.tsx @@ -1,6 +1,7 @@ import type { ReactNode } from "react"; import { Tooltip } from "@heroui/react"; import { Globe, X } from "lucide-react"; +import { useLingui } from "@lingui/react/macro"; import { getEntryIconUrl } from "@/renderer/components/common/fileIcons"; import { toLocalFileUrl } from "@/shared/promptContent"; import type { Attachment } from "./useAttachments"; @@ -10,7 +11,9 @@ export function BrowserChip(props: { title?: string; variant?: "chip" | "header"; }) { - const { onRemove, title = "Browser MCP enabled for this thread", variant = "chip" } = props; + const { t } = useLingui(); + const { onRemove, variant = "chip" } = props; + const title = props.title ?? t`Browser MCP enabled for this thread`; if (variant === "header") { // Same structure as the other header buttons (CircleCheck / ArrowRightLeft // / Bug / X) so the indicator slots into the row without alignment drift. @@ -41,12 +44,12 @@ export function BrowserChip(props: { role={onRemove ? "group" : "img"} >