From 655797c4fe9293b6649095c8cb5c6b07ffe09c5b Mon Sep 17 00:00:00 2001 From: Albrecht Degering Date: Wed, 2 Sep 2026 21:39:02 +0200 Subject: [PATCH] feat(translator-tools): add locale review and glossary workflow --- CHANGELOG.md | 14 +- README.md | 35 +- THIRD_PARTY_NOTICES.md | 2 +- docs/ARCHITECTURE.md | 15 +- docs/PRIVACY-SECURITY.md | 15 +- package-lock.json | 557 +++++++- package.json | 13 +- public/CHANGELOG.md | 14 +- public/LICENSES/npm-runtime-licenses.txt | 30 + public/README.md | 35 +- public/THIRD_PARTY_NOTICES.md | 2 +- public/docs/ARCHITECTURE.md | 15 +- public/docs/PRIVACY-SECURITY.md | 15 +- public/favicon.svg | 2 +- public/manifest.webmanifest | 6 +- public/sw.js | 4 +- public/toolbox-app.json | 49 +- src/App.tsx | 27 +- src/components/ErrorBoundary.tsx | 2 +- src/components/HelpDialog.tsx | 12 +- src/components/Workbench.tsx | 1670 +++++++++++++--------- src/styles.css | 493 +++---- src/text/evidence.ts | 251 ---- src/text/pipeline.ts | 515 ------- src/toolbox/manifest.source.json | 49 +- src/toolbox/transfer.ts | 146 ++ src/version.ts | 2 +- tests/browser/app.spec.ts | 91 -- tests/browser/responsive.spec.ts | 18 - tests/components/app.test.tsx | 17 - tests/smoke.test.ts | 13 + tests/text/pipeline.test.ts | 147 -- 32 files changed, 2084 insertions(+), 2192 deletions(-) delete mode 100644 src/text/evidence.ts delete mode 100644 src/text/pipeline.ts create mode 100644 src/toolbox/transfer.ts delete mode 100644 tests/browser/app.spec.ts delete mode 100644 tests/browser/responsive.spec.ts delete mode 100644 tests/components/app.test.tsx create mode 100644 tests/smoke.test.ts delete mode 100644 tests/text/pipeline.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index e9e4ff0..54747a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,11 @@ # Changelog -## 0.2.0 - 2026-09-02 +## Unreleased -- Added composable literal and line transforms with allocation preflights, plus BOM, UTF-8 validity, zero-byte and byte-newline evidence. -- Added paired text/artifact-evidence downloads with source/output hashes and - pipeline provenance; automatic Toolbox handoff remains intentionally disabled - pending the coordinated Portal consumer rollout. +- Added local translator workspace for i18next-like and single-locale JSON review. +- Added language pair selection, glossary storage and fuzzy in-context suggestions. +- Added local-only import/export pipeline with portal handoff integration. -## 0.1.0 - 2026-09-01 +## 0.1.0 - 2026-09-02 -- Added the initial local-first Text Tools workbench. -- Added Toolbox integration, bounded input handling, offline shell and deterministic release packaging. +- Initial local-first translator review implementation. diff --git a/README.md b/README.md index b0b4dc6..eb14a3f 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,25 @@ -# Text Tools +# Translator Tools -Compose, inspect and evidence plain-text transformations locally in the browser. +Translator Tools is a standalone local-first application in the +[add·ideas Toolbox](https://git.add-ideas.de/lotobo/toolbox-portal). Inputs are +parsed and edited in the browser only; no locale strings are uploaded. -Text Tools is a standalone local-first application in the [add·ideas Toolbox](https://git.add-ideas.de/lotobo/toolbox-portal). Inputs are processed in the browser and are not uploaded. +## Quick scope -## Version 0.2 scope +- Import locale files (`.json`, `.js`, `.txt`) in i18next-like or single-locale forms. +- Review authoritative language against one target language at a time. +- Edit source and target strings and copy-source quickly. +- Add translation glossary entries per language pair and receive fuzzy suggestions. +- Add new languages and export a reviewer-updated translation bundle locally. +- Send exports through the same-tool transfer channel to the Portal. -- Ordered pipelines for line endings, trimming, whitespace, stable sorting, deduplication, literal replacement, line prefix/suffix/filter/number/join and line-order reversal -- Locale-aware case conversion, Unicode NFC/NFD/NFKC/NFKD and best-effort Latin transliteration -- JSON/HTML/URL/Base64/hex escaping plus strict inverse decode/unescape stages; binary decoders accept canonical encodings and require valid UTF-8 text -- Bounded RFC-style quoted CSV column selection across embedded delimiters and newlines, alongside the retained literal multi-character delimiter-per-line mode -- Explicit UTF-8, UTF-16LE and Latin-1 file decoding plus BOM, malformed UTF-8 offset, zero-byte parity, byte-level newline and replacement-character evidence -- Versioned JSON recipe import/export, per-step size/change reports, and a paired text-plus-JSON artifact download with source/output SHA-256 and transformation provenance +## Notes -Pipelines are limited to 2,000,000 UTF-16 input units and an 8×/32 MiB output expansion. File input is limited to 16 MiB before decoding. Encoding is selected explicitly rather than guessed; compatibility normalisation, transliteration, narrow encodings and several transforms can be lossy, so source, result, and warnings remain visible. See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) and [docs/PRIVACY-SECURITY.md](docs/PRIVACY-SECURITY.md). - -The artifact evidence is a portable download contract, not an automatic Toolbox -handoff. SDK 0.3.0 provides the shared transfer contract, but automatic Open With -remains disabled in this release until the Portal consumer rollout is -coordinated. Local downloads stay explicit and portable in the meantime. +- The parser accepts JavaScript-style locale assignment forms such as + `const de = { ... }` and tolerant spacing/commas. +- Parsing and transfers are bounded to 5 MiB per imported/exported transfer file. +- Language detection relies on file name hints (`de_*.json`) and explicit user + selection. ## Development @@ -32,7 +33,7 @@ npm run test:browser ## Release -`npm run release:artifact` creates a deterministic `release/text-tools-0.2.0.zip` and checksum sidecar. +`npm run release:artifact` creates a deterministic `release/translator-tools-0.1.0.zip` and checksum sidecar. ## Licence diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index b0d6612..96d9d7b 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -1,6 +1,6 @@ # Third-party notices -Text Tools 0.2.0 directly depends on these runtime packages: +Translator Tools 0.1.0 directly depends on these runtime packages: | Package | Pinned version | Declared licence | | -------------------------------- | -------------: | ---------------- | diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index cf1d583..65850b1 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -1,9 +1,16 @@ # Architecture -Text Tools is a static React/Vite application wrapped in the shared Toolbox shell. `text/pipeline.ts` defines an ordered, typed list of transformations. `applyPipeline` starts from the exact source, applies enabled steps one at a time, checks expansion after every step, and returns the last successful output with per-step before/after counts and warnings. +Translator Tools is a static React/Vite application wrapped in the shared Toolbox shell. -Text encoding/decoding, line-ending conversion, bounded CSV parsing and Unicode-aware case primitives come from `@add-ideas/toolbox-helpers`. File decoding uses an explicit encoding and fatal-or-replacement choice; it does not guess a charset. Inverse JSON/HTML/URL/Base64/hex stages are strict: malformed syntax and non-UTF-8 byte output fail the step rather than being silently repaired. Column selection offers a quoted CSV mode that preserves embedded delimiters/newlines and a separate literal-delimiter-per-line mode. Recipe JSON carries a schema/app identity, accepts at most 100 supported steps, discards supplied IDs and assigns local monotonic IDs. +The workbench parses a single imported locale file in-browser, normalizes either: -`text/evidence.ts` inspects original bytes before decoding, including BOM, exact UTF-8 validity/first invalid offset, zero-byte parity and CR/LF/CRLF counts. Artifact export emits the selected output bytes and a deterministic evidence document containing canonical-source and exact-output SHA-256 values, inventories, recipe and step reports. The document records that direct Toolbox handoff is unavailable in this release; the SDK 0.3.0 transfer contract is intentionally not activated until the Portal consumer rollout is coordinated. +- i18next-like language maps (`{ "en": {...}, "de": {...} }`) +- single-locale objects (`{ "key": "value" }`, or JS-style assignment forms like `const de = {...}`). -Version 0.2 operations are synchronous and bounded, so the app creates no worker and has no persistence or server API. Relative entry and asset URLs keep the build relocatable below a nested portal path. +Strings are flattened into stable dot paths for review/editing and can be serialized +back with an inverse unflattening step. Suggestions come from local glossary entries +and are matched by language pair and fuzzy string distance. + +No background workers are required today. Persistent state is limited to a local +Storage glossary cache; everything else remains in process memory until download or +transfer. diff --git a/docs/PRIVACY-SECURITY.md b/docs/PRIVACY-SECURITY.md index d6bc791..47d39f4 100644 --- a/docs/PRIVACY-SECURITY.md +++ b/docs/PRIVACY-SECURITY.md @@ -1,9 +1,16 @@ # Privacy and security -Source text, imported files, recipes and results stay in page memory. There is no telemetry, analytics, account, persistence or runtime network path. Imported text is rendered as text/preformatted content, not executable HTML; HTML escaping produces a string and does not preview it as markup. +Locale files and generated outputs stay in-browser and are not uploaded. -Files are rejected above 16 MiB before reading. Decoded/pasted pipeline input is limited to 2,000,000 UTF-16 units, recipe text to 1,000,000 units/100 steps, and output to both 8× the original (with a small-input floor) and 32 MiB. Quoted CSV selection additionally limits rows, columns and field size. Strict inverse stages reject malformed escapes/encodings and invalid UTF-8; they do not attempt permissive repair. These limits reduce accidental expansion but do not prove that output is safe for a downstream interpreter. +No accounts, telemetry or runtime network requests are used for the core workflow. +The app stores glossary terms in local browser storage so they are available on the same +device for repeated reviews. -Compatibility normalisation, case conversion, transliteration, whitespace/line transforms, column selection and narrow encodings can change or lose information. Locale sorting depends on the browser's `Intl.Collator`. The exact source, output, per-step counts and warnings remain visible so users can review those changes before copying or downloading. +Parser/serializer limits are bounded to prevent accidental expansion: -Byte evidence does not guess an encoding or prove the selected decoder is correct. A valid UTF-8 byte sequence may represent text intended for another encoding, zero-byte parity is only a UTF-16 review signal, and replacement characters may already have existed in the source. Artifact evidence hashes canonical UTF-8 source text and exact exported bytes, clearly labels both, and includes filenames and transformation details that may themselves be sensitive. +- Imports and exports are capped at 5 MiB each. +- Large objects are flattened and rendered with per-session bounds. + +Suggested translations are purely local matching; they do not call external services. +Transfers to the Portal are signed local handoffs only and never stored by a remote +processor by default. diff --git a/package-lock.json b/package-lock.json index 381064c..4e7feec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,18 @@ { - "name": "text-tools", - "version": "0.2.0", + "name": "translator-tools", + "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "text-tools", - "version": "0.2.0", + "name": "translator-tools", + "version": "0.1.0", "license": "GPL-3.0-or-later", "dependencies": { "@add-ideas/toolbox-contract": "0.3.0", "@add-ideas/toolbox-helpers": "0.2.0", "@add-ideas/toolbox-shell-react": "0.3.0", + "json5": "^2.2.3", "react": "19.2.8", "react-dom": "19.2.8" }, @@ -49,10 +50,9 @@ }, "node_modules/@add-ideas/toolbox-helpers": { "version": "0.2.0", - "license": "GPL-3.0-or-later", - "engines": { - "node": ">=22" - } + "resolved": "https://git.add-ideas.de/api/packages/lotobo/npm/%40add-ideas%2Ftoolbox-helpers/-/0.2.0/toolbox-helpers-0.2.0.tgz", + "integrity": "sha512-SdOqkw+P+3J3fa5iVkzb5P15rVepB001GNV21Oh8w0CZcVL+YRltgD/s+MVcTyrNijWQf3E5vtQON/3N2LLyKg==", + "license": "GPL-3.0-or-later" }, "node_modules/@add-ideas/toolbox-shell-react": { "version": "0.3.0", @@ -82,11 +82,15 @@ }, "node_modules/@adobe/css-tools": { "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.5.0.tgz", + "integrity": "sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q==", "dev": true, "license": "MIT" }, "node_modules/@asamuzakjp/css-color": { "version": "5.1.11", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.11.tgz", + "integrity": "sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==", "dev": true, "license": "MIT", "dependencies": { @@ -102,6 +106,8 @@ }, "node_modules/@asamuzakjp/dom-selector": { "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.1.1.tgz", + "integrity": "sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==", "dev": true, "license": "MIT", "dependencies": { @@ -117,6 +123,8 @@ }, "node_modules/@asamuzakjp/generational-cache": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/generational-cache/-/generational-cache-1.0.1.tgz", + "integrity": "sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==", "dev": true, "license": "MIT", "engines": { @@ -125,11 +133,15 @@ }, "node_modules/@asamuzakjp/nwsapi": { "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", + "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", "dev": true, "license": "MIT" }, "node_modules/@babel/code-frame": { "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", "dev": true, "license": "MIT", "dependencies": { @@ -143,6 +155,8 @@ }, "node_modules/@babel/compat-data": { "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", "dev": true, "license": "MIT", "engines": { @@ -151,6 +165,8 @@ }, "node_modules/@babel/core": { "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", "dev": true, "license": "MIT", "dependencies": { @@ -180,6 +196,8 @@ }, "node_modules/@babel/generator": { "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", "dev": true, "license": "MIT", "dependencies": { @@ -195,6 +213,8 @@ }, "node_modules/@babel/helper-compilation-targets": { "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", "dev": true, "license": "MIT", "dependencies": { @@ -210,6 +230,8 @@ }, "node_modules/@babel/helper-globals": { "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", "dev": true, "license": "MIT", "engines": { @@ -218,6 +240,8 @@ }, "node_modules/@babel/helper-module-imports": { "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", "dev": true, "license": "MIT", "dependencies": { @@ -230,6 +254,8 @@ }, "node_modules/@babel/helper-module-transforms": { "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", "dev": true, "license": "MIT", "dependencies": { @@ -246,6 +272,8 @@ }, "node_modules/@babel/helper-string-parser": { "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", "dev": true, "license": "MIT", "engines": { @@ -254,6 +282,8 @@ }, "node_modules/@babel/helper-validator-identifier": { "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", "dev": true, "license": "MIT", "engines": { @@ -262,6 +292,8 @@ }, "node_modules/@babel/helper-validator-option": { "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", "dev": true, "license": "MIT", "engines": { @@ -270,6 +302,8 @@ }, "node_modules/@babel/helpers": { "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", "dev": true, "license": "MIT", "dependencies": { @@ -282,6 +316,8 @@ }, "node_modules/@babel/parser": { "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", "dev": true, "license": "MIT", "dependencies": { @@ -296,6 +332,8 @@ }, "node_modules/@babel/runtime": { "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", "dev": true, "license": "MIT", "engines": { @@ -304,6 +342,8 @@ }, "node_modules/@babel/template": { "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", "dev": true, "license": "MIT", "dependencies": { @@ -317,6 +357,8 @@ }, "node_modules/@babel/traverse": { "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", "dev": true, "license": "MIT", "dependencies": { @@ -334,6 +376,8 @@ }, "node_modules/@babel/types": { "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", "dev": true, "license": "MIT", "dependencies": { @@ -346,6 +390,8 @@ }, "node_modules/@bramus/specificity": { "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", + "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", "dev": true, "license": "MIT", "dependencies": { @@ -357,6 +403,8 @@ }, "node_modules/@csstools/color-helpers": { "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.1.1.tgz", + "integrity": "sha512-gLNsunvwf3mCi5u5o46/Z/JcJMnhbHSaZ69rkgPzNM3J4s8hWwpPUQB6/tt0EDFyCiWzxANlx+2LJwpYj4zS1w==", "dev": true, "funding": [ { @@ -375,6 +423,8 @@ }, "node_modules/@csstools/css-calc": { "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.3.0.tgz", + "integrity": "sha512-c5ihYsPkdG6JCkU2zTMm4+k6r7RXuGxtWYhu5DHMIiF1FHzrfmHL5so11AoFpUv/tu61xfcmT4AmKoFfMPoqdQ==", "dev": true, "funding": [ { @@ -397,6 +447,8 @@ }, "node_modules/@csstools/css-color-parser": { "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.2.2.tgz", + "integrity": "sha512-3QKjR/vxyjcSXBLgb6lP0S3MGdvwbmqSsvLPbYdVORqPDc8FX1HAJ0Spk38bxaRXgvENTA47tlhhbb5Z2e8hEg==", "dev": true, "funding": [ { @@ -423,6 +475,8 @@ }, "node_modules/@csstools/css-parser-algorithms": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", "dev": true, "funding": [ { @@ -444,6 +498,8 @@ }, "node_modules/@csstools/css-syntax-patches-for-csstree": { "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.12.tgz", + "integrity": "sha512-3vLQK+dXxhBMR2Wx99PTCifE+vHtW2ndZWyla8yK813ev6oGhyn8Lja8jCyGAWTJ+LEYZK7EVtJxrDj8ztevJw==", "dev": true, "funding": [ { @@ -467,6 +523,8 @@ }, "node_modules/@csstools/css-tokenizer": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", "dev": true, "funding": [ { @@ -485,6 +543,8 @@ }, "node_modules/@eslint-community/eslint-utils": { "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", "dev": true, "license": "MIT", "dependencies": { @@ -502,6 +562,8 @@ }, "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, "license": "Apache-2.0", "engines": { @@ -513,6 +575,8 @@ }, "node_modules/@eslint-community/regexpp": { "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "dev": true, "license": "MIT", "engines": { @@ -521,6 +585,8 @@ }, "node_modules/@eslint/config-array": { "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -534,6 +600,8 @@ }, "node_modules/@eslint/config-helpers": { "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.6.0.tgz", + "integrity": "sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -545,6 +613,8 @@ }, "node_modules/@eslint/core": { "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -556,6 +626,8 @@ }, "node_modules/@eslint/js": { "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", "dev": true, "license": "MIT", "engines": { @@ -575,6 +647,8 @@ }, "node_modules/@eslint/object-schema": { "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", "dev": true, "license": "Apache-2.0", "engines": { @@ -583,6 +657,8 @@ }, "node_modules/@eslint/plugin-kit": { "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz", + "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -595,6 +671,8 @@ }, "node_modules/@exodus/bytes": { "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.1.tgz", + "integrity": "sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==", "dev": true, "license": "MIT", "engines": { @@ -611,6 +689,8 @@ }, "node_modules/@humanfs/core": { "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -622,6 +702,8 @@ }, "node_modules/@humanfs/node": { "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -635,6 +717,8 @@ }, "node_modules/@humanfs/types": { "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", "dev": true, "license": "Apache-2.0", "engines": { @@ -643,6 +727,8 @@ }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -655,6 +741,8 @@ }, "node_modules/@humanwhocodes/retry": { "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -667,6 +755,8 @@ }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "dev": true, "license": "MIT", "dependencies": { @@ -676,6 +766,8 @@ }, "node_modules/@jridgewell/remapping": { "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", "dev": true, "license": "MIT", "dependencies": { @@ -685,6 +777,8 @@ }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, "license": "MIT", "engines": { @@ -693,11 +787,15 @@ }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "dev": true, "license": "MIT", "dependencies": { @@ -706,15 +804,19 @@ } }, "node_modules/@oxc-project/types": { - "version": "0.147.0", + "version": "0.148.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.148.0.tgz", + "integrity": "sha512-Nm4s/jB+4FpFsPhWGEC4h7rzksesmtnMXomo6rCMcg/b8zLQuOziRgkCS1fxDCXOlJB/6Q8oABOZ/OP6RIPj9A==", "dev": true, "license": "MIT", "funding": { - "url": "https://github.com/sponsors/Boshen" + "url": "https://github.com/sponsors/oxc-project" } }, "node_modules/@playwright/test": { "version": "1.62.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.62.1.tgz", + "integrity": "sha512-DTcUc8qii+cpHvtOwggMtBRMjKZHXYWdw8syRYu2vtzuq4Wxphqq4NfCs5Zt44L6mA8rfDfj+PHnxFc/FeK6mQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -728,9 +830,9 @@ } }, "node_modules/@rolldown/binding-android-arm-eabi": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.6.tgz", - "integrity": "sha512-b+jTcARdTiFLI6jB4a5XjTm0RWd6KcRfQj/I2356fxUZemiho9zQLxo0RtCuMDAyKcLo6cEltkgbQp6d1+sjjQ==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.7.tgz", + "integrity": "sha512-EypzgnYCwyVY4NDHKzGmNJT5b+XaQEBniHxsMdeIQLB/tcCzZnhqrzHpZFbX9iaxx+5RiB8caATBtfvZP7zVxQ==", "cpu": [ "arm" ], @@ -745,9 +847,9 @@ } }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.6.tgz", - "integrity": "sha512-lkWU8ZJaRk9q3CIEY1Tc7vIFALp3Xw5NfGJo2hQg5oIqNgxWi1zI+IiDEK3r70BF5Dzol1tcXsnzsRc8NLhG+Q==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.7.tgz", + "integrity": "sha512-l17HE9EweWaqJZhuUuNBN/FzM62xw+DECVnJyvMsxn8vJFAGLy5QfLDoYAcronkAN8VxKZHezDpulHDPx95vFw==", "cpu": [ "arm64" ], @@ -762,9 +864,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.6.tgz", - "integrity": "sha512-dgR56NYnvAszm7Ob1B2/Vn0e8bUQYZH2UjVaMMtMVOCKFSfjhfLmuA/9+O+F+ajUdG6B/bSssrKW6JJYASa8jA==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.7.tgz", + "integrity": "sha512-8ED8ELFvHXc6OCETIn4gXObPiaR6bckM/ipXtbzlPVDRMBfEGjCKgO90F9YtfdpDatVx/ZQw7aZ1vUMf/+T3Mw==", "cpu": [ "arm64" ], @@ -779,9 +881,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.6.tgz", - "integrity": "sha512-vpVxFvUCFioJqug7OTvqptkc4yb8UX0AwfDmJpaR/0sWz+BUmqSVAf7c8JkUgnN8YLspb4a/N6NhTyMAmdyQ7Q==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.7.tgz", + "integrity": "sha512-/WPripjtiAIZ2tWY7ddijORT0Ujg87wxWW/qcoFVCKAWVDPhtY0xr7Dj0M3GyNGz60jGwTElhro/mkF9dT7dDQ==", "cpu": [ "x64" ], @@ -796,9 +898,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.6.tgz", - "integrity": "sha512-h1wG6Y6K3JlRswxsI64qQJqBAy4vrLuHgRbc8CZMGSWTOFRY6ghMApM1NKzB2I0n5xV1fjkE18SuVl2QpLeNpA==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.7.tgz", + "integrity": "sha512-14DI4NcqpvbICxSnGLx3PmtDaWqRP/KGSGb6C+JLLVPeZRl6dKdHba3pGsqT3vpdTqhEYIPG0MMQ8c0xYqoJxA==", "cpu": [ "x64" ], @@ -813,9 +915,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.6.tgz", - "integrity": "sha512-tbCiqub0q2MVWJKgF5PoAlNWCtQydiOYSLIkd8sByqK/6MMYLJRcSXSYodqYtd0O+Fw7QaVmKKlS4oL94YRZ0w==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.7.tgz", + "integrity": "sha512-bxrWIRvHWQvbJwi+VIie/kDJmQxcNE6xxWwZdqF/ExVAigtHkv54WTLQPb+QsZdnFy18fg7JPfWGL0RH6vwIlQ==", "cpu": [ "arm" ], @@ -830,9 +932,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.6.tgz", - "integrity": "sha512-oxK9+baEBPhZG5HB4URY+uU04zJWeZlH6Tb9rB5DK4DF9XR1uXNLXt5Q5ZsugTKayNCNLhkcwz/ye74hRI98dg==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.7.tgz", + "integrity": "sha512-toOY2BChBZyuxU7OYX6Tn389di4IzAqPTycVcci0O7FSfBqzRB3RZn+K5Is6ANf4tmgRd/K1yZTsNTXbkXsnLg==", "cpu": [ "arm64" ], @@ -850,9 +952,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.6.tgz", - "integrity": "sha512-muWCk27FVBEZtv0MsK8gnfSmgczA8KQ0uRVJbTABKhkRfQc38aUrcb7fhi3BNiyseFmgcRsoMfQsSNJ+DbZdSw==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.7.tgz", + "integrity": "sha512-lAIXTH/aiLRLxsTgQvfhjo4K1ydWIp00+V0voOr9beb/9ZmkUFrSIb03dXNFRgMNvkE6oGsF10ioQ6UsI+vS5Q==", "cpu": [ "arm64" ], @@ -870,9 +972,9 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.6.tgz", - "integrity": "sha512-eWDoSfU7Co2qj3vgB3Dt4lj1mG6CoWbcJQkRMP3XJplyCMtuaq3LHvPFjS9QIPvMGWVadJC04Xiy0IdcVPtnwQ==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.7.tgz", + "integrity": "sha512-kdnwS28Pkenp/mZMRwjXXXwxQ7pIsm+bF919LUK93BOyhcLsrVKdP2p9fxpiPNPAbNuch8ypQt0pm2P2LYCAGg==", "cpu": [ "ppc64" ], @@ -890,9 +992,9 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.6.tgz", - "integrity": "sha512-2bWNjRSIayvupRKxXUY2tWG9fYdoUlTqWywHRvE8Eq3GvuQ+f2HeIkve697fIt+IQs/PV8yFsdWuhp1aJ1PdnA==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.7.tgz", + "integrity": "sha512-516OdsyLdr5E65paF3yBF55t8mfm9+gmtCsK3xI7XKXIT7EfRlHhxL8K/NR6Hu8BWSgF5+1w74lTL0+nxcc8Qw==", "cpu": [ "s390x" ], @@ -910,7 +1012,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.2.6", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.7.tgz", + "integrity": "sha512-r8/z8n7GFaYRln3xmP1Cxy0HH/HLM0uBUPkEuSVEfKGDA89M0FsZRZJRSwe/tJjRx+fpH/gjorfhB8tmEbSFLA==", "cpu": [ "x64" ], @@ -928,9 +1032,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.6.tgz", - "integrity": "sha512-TvtPnfVr+HtyGiDmPK4VWmlNm7QhNNAcK5Q9A7aOXsI8545yCyaoMaicXrFZ72JzeYjaUVk7yT243zT0jzjFKQ==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.7.tgz", + "integrity": "sha512-pAsE8iiDxUg1xBqdhrTfg45AVDVpirjz00sblEYClGNNcMnDb+e8beQgqIAw6LvauX/APvgxUnwrgun/YYGBhw==", "cpu": [ "x64" ], @@ -948,9 +1052,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.6.tgz", - "integrity": "sha512-iOo0VEay2XFhaCcH0sps5XIimkSuOnNaZrf6+ZkoSOQBJPKNU48RkmJv0/lSpipexu5P+ouFgafe5IGr/DiQfg==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.7.tgz", + "integrity": "sha512-lTcIYmmnQQA8Or/2DatS6oSqcdLHvendjS+zLu+FwgToynWMRSmQdpM65fTANJgIS4mjbMOo5KT2lnT9SAb96w==", "cpu": [ "arm64" ], @@ -965,9 +1069,9 @@ } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.6.tgz", - "integrity": "sha512-y5NTmmasMS455JlOCO4ZM9krIchv3Mvm1crL1iUPGOPgEzSkves9n0SdC5Sjz6+qWDFhd8/JpfWMH8NSWNHe+A==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.7.tgz", + "integrity": "sha512-e3Gu3WxbNk/UqQhxqU7YIYO+9ZBvWNz3U+h/qRFosscMFzdRPbXYSaSWgSnklv2fz1TgzBTcti2z35c/7irsHw==", "cpu": [ "arm64" ], @@ -982,9 +1086,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.6.tgz", - "integrity": "sha512-np8iZSLfXlAD4kWhiyq/u0Yt8oZDtRQ8lGhQaCXo2rl37KNjeU0GjJuwr4P3oeZ++ROfofsKNBqR5LTO8aXyWQ==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.7.tgz", + "integrity": "sha512-W/jg5qoRSqjsEv0+dZi4e687mcHqmVuU0P4fK6qS/xjetW2Gmc1W8j//z5nAeNcC8Ttm0hV46IjcYeuVwYhuiw==", "cpu": [ "x64" ], @@ -1000,16 +1104,22 @@ }, "node_modules/@rolldown/pluginutils": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", "dev": true, "license": "MIT" }, "node_modules/@standard-schema/spec": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", "dev": true, "license": "MIT" }, "node_modules/@testing-library/dom": { "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", "dev": true, "license": "MIT", "peer": true, @@ -1029,6 +1139,8 @@ }, "node_modules/@testing-library/jest-dom": { "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", + "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==", "dev": true, "license": "MIT", "dependencies": { @@ -1047,11 +1159,15 @@ }, "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", "dev": true, "license": "MIT" }, "node_modules/@testing-library/react": { "version": "16.3.2", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz", + "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==", "dev": true, "license": "MIT", "dependencies": { @@ -1078,6 +1194,8 @@ }, "node_modules/@testing-library/user-event": { "version": "14.6.1", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz", + "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==", "dev": true, "license": "MIT", "engines": { @@ -1090,12 +1208,16 @@ }, "node_modules/@types/aria-query": { "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", "dev": true, "license": "MIT", "peer": true }, "node_modules/@types/chai": { "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", "dev": true, "license": "MIT", "dependencies": { @@ -1105,26 +1227,36 @@ }, "node_modules/@types/deep-eql": { "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", "dev": true, "license": "MIT" }, "node_modules/@types/esrecurse": { "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", "dev": true, "license": "MIT" }, "node_modules/@types/estree": { "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", "dev": true, "license": "MIT" }, "node_modules/@types/json-schema": { "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true, "license": "MIT" }, "node_modules/@types/node": { "version": "25.9.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.5.tgz", + "integrity": "sha512-OScDchr2fwuUmWdf4kZ9h7PcJiYDVInhJizG/biAq3cAvqwYktuy/TYGGdZNMtNTFUP7rnb0NU4TUdm82kt4Rg==", "dev": true, "license": "MIT", "dependencies": { @@ -1133,6 +1265,8 @@ }, "node_modules/@types/react": { "version": "19.2.17", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", + "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", "dev": true, "license": "MIT", "dependencies": { @@ -1141,6 +1275,8 @@ }, "node_modules/@types/react-dom": { "version": "19.2.5", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.5.tgz", + "integrity": "sha512-fMPwH9v7r/pp43yUd2/Mbiex5KouJwwR3dzHkhLREUC6764VyDsqxhAxv6OFEYR1RhjOyD1naqba8ECDBe7ZQg==", "dev": true, "license": "MIT", "peerDependencies": { @@ -1149,6 +1285,8 @@ }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.64.0.tgz", + "integrity": "sha512-CGvQPBxN3wZLu6Rz2kFUpZeoCm78xUic92ck39KPePkO1NPOwjCqdQnm5Q87tpWw9vcBvW8XLrDXjH9PWYtJ3Q==", "dev": true, "license": "MIT", "dependencies": { @@ -1176,6 +1314,8 @@ }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { "version": "7.0.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.8.tgz", + "integrity": "sha512-YYNsSlXBjMk92SKnkwvB5LOVSa6OznlFUGcsvrFgNJbJCd0M1XKeFVRc8ZByeCqz32FivYNHJVooLmdqrmvp/Q==", "dev": true, "license": "MIT", "engines": { @@ -1184,6 +1324,8 @@ }, "node_modules/@typescript-eslint/parser": { "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.64.0.tgz", + "integrity": "sha512-KA0OshtlcCCXmbfqyZkM5pV3/WNraJf7DkJRLpyrmwPtud57H5BDX7C3k0LPSPxpprfRL+cJDGabF10mvNCoCw==", "dev": true, "license": "MIT", "dependencies": { @@ -1207,6 +1349,8 @@ }, "node_modules/@typescript-eslint/project-service": { "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.64.0.tgz", + "integrity": "sha512-tk4WpOJ6IEbGrVHaNmM0YRrwAD3exZlIK3iadQNAxh4YKk6jvUQ4ecq18n+v7+meh+cJ3j+D8nbk8sRKhlwLQg==", "dev": true, "license": "MIT", "dependencies": { @@ -1227,6 +1371,8 @@ }, "node_modules/@typescript-eslint/scope-manager": { "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.64.0.tgz", + "integrity": "sha512-CXEaFdYXjSTgKhisNkwCcJwTP8Pl+fmRrEQrri4nm3vU743bALrxzLmq7fHG/7e6a5xO0lDYeURpZmBuhHk54w==", "dev": true, "license": "MIT", "dependencies": { @@ -1243,6 +1389,8 @@ }, "node_modules/@typescript-eslint/tsconfig-utils": { "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.64.0.tgz", + "integrity": "sha512-2yo8rRNKuzbVWQp5kslhANqZ2uDAeROQHBRZNPu8JDsHmeFNj/XJJhX/FhNUWmkHHvoNsKa6+tHJiig87EzsQw==", "dev": true, "license": "MIT", "engines": { @@ -1258,6 +1406,8 @@ }, "node_modules/@typescript-eslint/type-utils": { "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.64.0.tgz", + "integrity": "sha512-XWG4Fmmv/6SvyS9nH8jWrKs6terwJvE8cyRt1CzYYqzp9OrPhCT4cMc/f7C6RZCwG+qMmiffJS1/qJP8G1URtg==", "dev": true, "license": "MIT", "dependencies": { @@ -1281,6 +1431,8 @@ }, "node_modules/@typescript-eslint/types": { "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.64.0.tgz", + "integrity": "sha512-qjhfuTfLXjA4IOzXvz0rTjT01BqEiIgPoUeMwiEjnaHKJMTNo8rH5pYW1a2L/0Dnux2fPC85AeyJoWaGa8WxTA==", "dev": true, "license": "MIT", "engines": { @@ -1293,6 +1445,8 @@ }, "node_modules/@typescript-eslint/typescript-estree": { "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.64.0.tgz", + "integrity": "sha512-Pztpsn1aCE1oWDvDEfUk31nngvvF7vUB5SwHFEaZIFpvw7WJtqUHHL4plBZDA9HfWJJjL13BdG0YrJInTUvoVA==", "dev": true, "license": "MIT", "dependencies": { @@ -1319,6 +1473,8 @@ }, "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "bin": { @@ -1330,6 +1486,8 @@ }, "node_modules/@typescript-eslint/utils": { "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.64.0.tgz", + "integrity": "sha512-aJUGVB3+U0htrrCjoA8qukw8cm8fNCGAxK/tVoS70k8aeb7DETKeFozRiVFIwEeN9WJLsjaP3ph8I60tY2XZoQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1352,6 +1510,8 @@ }, "node_modules/@typescript-eslint/visitor-keys": { "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.64.0.tgz", + "integrity": "sha512-mrtuL8Nsn6gi2H4mo5KMTp823M+3Q19Ew/i+Zlikq20tIMm99C3Ez0dCmkWWnxut20esQvTg8aUSEhMcAOXhEw==", "dev": true, "license": "MIT", "dependencies": { @@ -1368,6 +1528,8 @@ }, "node_modules/@vitejs/plugin-react": { "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.3.tgz", + "integrity": "sha512-vmFvco5/QuC2f9Oj+wTk0+9XeDFkHxSamwZKYc7MxYwKICfvUvlMhqKI0VuICPltGqh1neqBKDvO4kes1ya8vg==", "dev": true, "license": "MIT", "dependencies": { @@ -1392,6 +1554,8 @@ }, "node_modules/@vitest/expect": { "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.11.tgz", + "integrity": "sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==", "dev": true, "license": "MIT", "dependencies": { @@ -1408,6 +1572,8 @@ }, "node_modules/@vitest/mocker": { "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.11.tgz", + "integrity": "sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1433,6 +1599,8 @@ }, "node_modules/@vitest/pretty-format": { "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.11.tgz", + "integrity": "sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==", "dev": true, "license": "MIT", "dependencies": { @@ -1444,6 +1612,8 @@ }, "node_modules/@vitest/runner": { "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.11.tgz", + "integrity": "sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==", "dev": true, "license": "MIT", "dependencies": { @@ -1456,6 +1626,8 @@ }, "node_modules/@vitest/snapshot": { "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.11.tgz", + "integrity": "sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==", "dev": true, "license": "MIT", "dependencies": { @@ -1470,6 +1642,8 @@ }, "node_modules/@vitest/spy": { "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.11.tgz", + "integrity": "sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==", "dev": true, "license": "MIT", "funding": { @@ -1478,6 +1652,8 @@ }, "node_modules/@vitest/utils": { "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.11.tgz", + "integrity": "sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1780,6 +1956,8 @@ }, "node_modules/escalade": { "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, "license": "MIT", "engines": { @@ -1799,6 +1977,8 @@ }, "node_modules/eslint": { "version": "10.7.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.7.0.tgz", + "integrity": "sha512-GVTD7s1vdIl6UYvAfriOPeY1Df8LIZjfofLvHwde+erDHGGuHyuM6xoxRxmHiebhYuD2p1vN4wWh0XzPARSGDQ==", "dev": true, "license": "MIT", "workspaces": [ @@ -1882,6 +2062,8 @@ }, "node_modules/eslint-scope": { "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -1899,6 +2081,8 @@ }, "node_modules/eslint-visitor-keys": { "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1910,6 +2094,8 @@ }, "node_modules/espree": { "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -1926,6 +2112,8 @@ }, "node_modules/esquery": { "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -1937,6 +2125,8 @@ }, "node_modules/esrecurse": { "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -1948,6 +2138,8 @@ }, "node_modules/estraverse": { "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, "license": "BSD-2-Clause", "engines": { @@ -1956,6 +2148,8 @@ }, "node_modules/estree-walker": { "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", "dev": true, "license": "MIT", "dependencies": { @@ -1964,6 +2158,8 @@ }, "node_modules/esutils": { "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, "license": "BSD-2-Clause", "engines": { @@ -1972,6 +2168,8 @@ }, "node_modules/expect-type": { "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1980,21 +2178,29 @@ }, "node_modules/fast-deep-equal": { "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true, "license": "MIT" }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true, "license": "MIT" }, "node_modules/fast-levenshtein": { "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true, "license": "MIT" }, "node_modules/fdir": { "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, "license": "MIT", "engines": { @@ -2011,6 +2217,8 @@ }, "node_modules/file-entry-cache": { "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2022,6 +2230,8 @@ }, "node_modules/find-up": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "license": "MIT", "dependencies": { @@ -2037,6 +2247,8 @@ }, "node_modules/flat-cache": { "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, "license": "MIT", "dependencies": { @@ -2049,6 +2261,8 @@ }, "node_modules/flatted": { "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", "dev": true, "license": "ISC" }, @@ -2069,6 +2283,8 @@ }, "node_modules/gensync": { "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true, "license": "MIT", "engines": { @@ -2077,6 +2293,8 @@ }, "node_modules/glob-parent": { "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, "license": "ISC", "dependencies": { @@ -2088,6 +2306,8 @@ }, "node_modules/globals": { "version": "17.7.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.7.0.tgz", + "integrity": "sha512-Czmyns5dUsq4seFBR/Kdydhmo8y9kC79hiSkPn0YcGtNnYWnrgt0vjrSjx9tspoDGWm2CMarffRuLjM4xUz8xg==", "dev": true, "license": "MIT", "engines": { @@ -2099,11 +2319,15 @@ }, "node_modules/hermes-estree": { "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", "dev": true, "license": "MIT" }, "node_modules/hermes-parser": { "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", "dev": true, "license": "MIT", "dependencies": { @@ -2112,6 +2336,8 @@ }, "node_modules/html-encoding-sniffer": { "version": "6.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", "dev": true, "license": "MIT", "dependencies": { @@ -2123,6 +2349,8 @@ }, "node_modules/ignore": { "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, "license": "MIT", "engines": { @@ -2131,6 +2359,8 @@ }, "node_modules/imurmurhash": { "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, "license": "MIT", "engines": { @@ -2139,6 +2369,8 @@ }, "node_modules/indent-string": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", "dev": true, "license": "MIT", "engines": { @@ -2147,6 +2379,8 @@ }, "node_modules/is-extglob": { "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, "license": "MIT", "engines": { @@ -2155,6 +2389,8 @@ }, "node_modules/is-glob": { "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, "license": "MIT", "dependencies": { @@ -2166,21 +2402,29 @@ }, "node_modules/is-potential-custom-element-name": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", "dev": true, "license": "MIT" }, "node_modules/isexe": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true, "license": "ISC" }, "node_modules/js-tokens": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true, "license": "MIT" }, "node_modules/jsdom": { "version": "29.1.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.1.1.tgz", + "integrity": "sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==", "dev": true, "license": "MIT", "dependencies": { @@ -2220,6 +2464,8 @@ }, "node_modules/jsdom/node_modules/lru-cache": { "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -2228,6 +2474,8 @@ }, "node_modules/jsesc": { "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", "dev": true, "license": "MIT", "bin": { @@ -2239,22 +2487,29 @@ }, "node_modules/json-buffer": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true, "license": "MIT" }, "node_modules/json-schema-traverse": { "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true, "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true, "license": "MIT" }, "node_modules/json5": { "version": "2.2.3", - "dev": true, + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "license": "MIT", "bin": { "json5": "lib/cli.js" @@ -2265,6 +2520,8 @@ }, "node_modules/keyv": { "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, "license": "MIT", "dependencies": { @@ -2273,6 +2530,8 @@ }, "node_modules/levn": { "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2285,6 +2544,8 @@ }, "node_modules/lightningcss": { "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", "dev": true, "license": "MPL-2.0", "dependencies": { @@ -2466,6 +2727,8 @@ }, "node_modules/lightningcss-linux-x64-gnu": { "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", "cpu": [ "x64" ], @@ -2554,6 +2817,8 @@ }, "node_modules/locate-path": { "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "license": "MIT", "dependencies": { @@ -2568,6 +2833,8 @@ }, "node_modules/lru-cache": { "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, "license": "ISC", "dependencies": { @@ -2576,6 +2843,8 @@ }, "node_modules/lz-string": { "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", "dev": true, "license": "MIT", "peer": true, @@ -2585,6 +2854,8 @@ }, "node_modules/magic-string": { "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2593,11 +2864,15 @@ }, "node_modules/mdn-data": { "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", "dev": true, "license": "CC0-1.0" }, "node_modules/min-indent": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", "dev": true, "license": "MIT", "engines": { @@ -2606,6 +2881,8 @@ }, "node_modules/minimatch": { "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -2620,11 +2897,15 @@ }, "node_modules/ms": { "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true, "license": "MIT" }, "node_modules/nanoid": { "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", "dev": true, "funding": [ { @@ -2642,11 +2923,15 @@ }, "node_modules/natural-compare": { "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true, "license": "MIT" }, "node_modules/node-releases": { "version": "2.0.54", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.54.tgz", + "integrity": "sha512-YHs7BmmcsdAI5Ozuf8JZo6PT0mv2GIWC9vMfvUC3dp65M8hn7Ux8CPL+2oBI7juNuj9d0ndhTcznq2ODBps9cQ==", "dev": true, "license": "MIT", "engines": { @@ -2655,6 +2940,8 @@ }, "node_modules/obug": { "version": "2.1.4", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz", + "integrity": "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==", "dev": true, "funding": [ "https://github.com/sponsors/sxzz", @@ -2667,6 +2954,8 @@ }, "node_modules/optionator": { "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, "license": "MIT", "dependencies": { @@ -2683,6 +2972,8 @@ }, "node_modules/p-limit": { "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2697,6 +2988,8 @@ }, "node_modules/p-locate": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "license": "MIT", "dependencies": { @@ -2711,6 +3004,8 @@ }, "node_modules/parse5": { "version": "8.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz", + "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==", "dev": true, "license": "MIT", "dependencies": { @@ -2722,6 +3017,8 @@ }, "node_modules/path-exists": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, "license": "MIT", "engines": { @@ -2730,6 +3027,8 @@ }, "node_modules/path-key": { "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, "license": "MIT", "engines": { @@ -2738,16 +3037,22 @@ }, "node_modules/pathe": { "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "dev": true, "license": "MIT" }, "node_modules/picocolors": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true, "license": "ISC" }, "node_modules/picomatch": { "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", "dev": true, "license": "MIT", "engines": { @@ -2759,6 +3064,8 @@ }, "node_modules/playwright": { "version": "1.62.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.62.1.tgz", + "integrity": "sha512-0M+L3LAD8/nm554LOla9Ayx0j0tmFZ0FBcoQ7F1VuVHpM/XpiC8RcDzBQB8W5+hA8L22THxELzeF+2WcUzvcLg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -2776,6 +3083,8 @@ }, "node_modules/playwright-core": { "version": "1.62.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.62.1.tgz", + "integrity": "sha512-wPYSwEBJY9GHraISXqyqtx0na0LpO3XEX7jNDhntbex7tzUS7kLnZsOlFruFJB4Hi/rhDMjXGqHewDZ68nYZVw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -2787,6 +3096,8 @@ }, "node_modules/postcss": { "version": "8.5.26", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", + "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", "dev": true, "funding": [ { @@ -2814,6 +3125,8 @@ }, "node_modules/prelude-ls": { "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, "license": "MIT", "engines": { @@ -2822,6 +3135,8 @@ }, "node_modules/prettier": { "version": "3.9.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.5.tgz", + "integrity": "sha512-/FVl766LpUfB5vXgCYOYa0MeV/441Ia99AeICQIQFTY/Nw0roZwULcXpku5i1/m5kt/baz+s4Zogspd839HSMg==", "dev": true, "license": "MIT", "bin": { @@ -2836,6 +3151,8 @@ }, "node_modules/pretty-format": { "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, "license": "MIT", "peer": true, @@ -2850,6 +3167,8 @@ }, "node_modules/punycode": { "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, "license": "MIT", "engines": { @@ -2858,6 +3177,8 @@ }, "node_modules/react": { "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", + "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -2865,6 +3186,8 @@ }, "node_modules/react-dom": { "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz", + "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==", "license": "MIT", "dependencies": { "scheduler": "^0.27.0" @@ -2875,12 +3198,16 @@ }, "node_modules/react-is": { "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true, "license": "MIT", "peer": true }, "node_modules/redent": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", "dev": true, "license": "MIT", "dependencies": { @@ -2893,6 +3220,8 @@ }, "node_modules/require-from-string": { "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "dev": true, "license": "MIT", "engines": { @@ -2900,11 +3229,13 @@ } }, "node_modules/rolldown": { - "version": "1.2.6", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.7.tgz", + "integrity": "sha512-g0EtLvBjTUB7jhyV0S/TCup3v/XSVl45vUIGbOGU4QPiyjTenCe4mKuFvW9fEgYmS2Fo42AUssRmNuMziXdrig==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.147.0", + "@oxc-project/types": "=0.148.0", "@rolldown/pluginutils": "^1.0.0" }, "bin": { @@ -2914,25 +3245,27 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm-eabi": "1.2.6", - "@rolldown/binding-android-arm64": "1.2.6", - "@rolldown/binding-darwin-arm64": "1.2.6", - "@rolldown/binding-darwin-x64": "1.2.6", - "@rolldown/binding-freebsd-x64": "1.2.6", - "@rolldown/binding-linux-arm-gnueabihf": "1.2.6", - "@rolldown/binding-linux-arm64-gnu": "1.2.6", - "@rolldown/binding-linux-arm64-musl": "1.2.6", - "@rolldown/binding-linux-ppc64-gnu": "1.2.6", - "@rolldown/binding-linux-s390x-gnu": "1.2.6", - "@rolldown/binding-linux-x64-gnu": "1.2.6", - "@rolldown/binding-linux-x64-musl": "1.2.6", - "@rolldown/binding-openharmony-arm64": "1.2.6", - "@rolldown/binding-win32-arm64-msvc": "1.2.6", - "@rolldown/binding-win32-x64-msvc": "1.2.6" + "@rolldown/binding-android-arm-eabi": "1.2.7", + "@rolldown/binding-android-arm64": "1.2.7", + "@rolldown/binding-darwin-arm64": "1.2.7", + "@rolldown/binding-darwin-x64": "1.2.7", + "@rolldown/binding-freebsd-x64": "1.2.7", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.7", + "@rolldown/binding-linux-arm64-gnu": "1.2.7", + "@rolldown/binding-linux-arm64-musl": "1.2.7", + "@rolldown/binding-linux-ppc64-gnu": "1.2.7", + "@rolldown/binding-linux-s390x-gnu": "1.2.7", + "@rolldown/binding-linux-x64-gnu": "1.2.7", + "@rolldown/binding-linux-x64-musl": "1.2.7", + "@rolldown/binding-openharmony-arm64": "1.2.7", + "@rolldown/binding-win32-arm64-msvc": "1.2.7", + "@rolldown/binding-win32-x64-msvc": "1.2.7" } }, "node_modules/saxes": { "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", "dev": true, "license": "ISC", "dependencies": { @@ -2944,10 +3277,14 @@ }, "node_modules/scheduler": { "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", "license": "MIT" }, "node_modules/semver": { "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "license": "ISC", "bin": { @@ -2956,6 +3293,8 @@ }, "node_modules/shebang-command": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "license": "MIT", "dependencies": { @@ -2967,6 +3306,8 @@ }, "node_modules/shebang-regex": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, "license": "MIT", "engines": { @@ -2975,11 +3316,15 @@ }, "node_modules/siginfo": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", "dev": true, "license": "ISC" }, "node_modules/source-map-js": { "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -2988,16 +3333,22 @@ }, "node_modules/stackback": { "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", "dev": true, "license": "MIT" }, "node_modules/std-env": { "version": "4.2.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.2.0.tgz", + "integrity": "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==", "dev": true, "license": "MIT" }, "node_modules/strip-indent": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3009,16 +3360,22 @@ }, "node_modules/symbol-tree": { "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "dev": true, "license": "MIT" }, "node_modules/tinybench": { "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", "dev": true, "license": "MIT" }, "node_modules/tinyexec": { "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.0.tgz", + "integrity": "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==", "dev": true, "license": "MIT", "engines": { @@ -3027,6 +3384,8 @@ }, "node_modules/tinyglobby": { "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "dev": true, "license": "MIT", "dependencies": { @@ -3042,6 +3401,8 @@ }, "node_modules/tinyrainbow": { "version": "3.1.1", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.1.tgz", + "integrity": "sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==", "dev": true, "license": "MIT", "engines": { @@ -3050,6 +3411,8 @@ }, "node_modules/tldts": { "version": "7.4.11", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.11.tgz", + "integrity": "sha512-aBiNayCfTQxuIJBm06M+xR14cYaYlDlSXZbgsnKzKNxDKUVq7KFwTjwBSsb7m9Y5xO8WfPnBc63WaYFMTGlvqw==", "dev": true, "license": "MIT", "dependencies": { @@ -3061,11 +3424,15 @@ }, "node_modules/tldts-core": { "version": "7.4.11", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.11.tgz", + "integrity": "sha512-CW3WN2rIIE/Of21mulhgnGOwoDyEFNygyIBOONSdyAuSATgMMUCpLeUlB+E8sAwA5xRV9hYPl+kyZ9citHCaKg==", "dev": true, "license": "MIT" }, "node_modules/tough-cookie": { "version": "6.0.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.2.tgz", + "integrity": "sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -3077,6 +3444,8 @@ }, "node_modules/tr46": { "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", "dev": true, "license": "MIT", "dependencies": { @@ -3088,6 +3457,8 @@ }, "node_modules/ts-api-utils": { "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", "dev": true, "license": "MIT", "engines": { @@ -3099,6 +3470,8 @@ }, "node_modules/type-check": { "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, "license": "MIT", "dependencies": { @@ -3110,6 +3483,8 @@ }, "node_modules/typescript": { "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -3122,6 +3497,8 @@ }, "node_modules/typescript-eslint": { "version": "8.64.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.64.0.tgz", + "integrity": "sha512-0qg+pDNMnqYzqH9AnNK+39tejHvsShUOUUoRUgtnTGE7QuMZhiFDnozq8nHJVq+Wae6NMLKNWLg5WmkcC/ndyQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3144,6 +3521,8 @@ }, "node_modules/undici": { "version": "7.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz", + "integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==", "dev": true, "license": "MIT", "engines": { @@ -3152,11 +3531,15 @@ }, "node_modules/undici-types": { "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", "dev": true, "license": "MIT" }, "node_modules/update-browserslist-db": { "version": "1.3.2", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.2.tgz", + "integrity": "sha512-UQ+MSxlhRm1bzjhU+DcuXfjFO1FzNtqhK5+9Yvlp90ItDLk5vT932A0rFu619nf7RVS+Y/VeaUW1jaRDqZ8VJw==", "dev": true, "funding": [ { @@ -3186,6 +3569,8 @@ }, "node_modules/uri-js": { "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -3194,6 +3579,8 @@ }, "node_modules/vite": { "version": "8.2.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.2.tgz", + "integrity": "sha512-cFKLV/PRgAUlIRm5WjMjJ86jrftzpqcgH+Us+DS8mI3CDNiH30Whrz8uHL3+MOLPAgqbMBAqWdAHAphOAM+z/Q==", "dev": true, "license": "MIT", "dependencies": { @@ -3285,6 +3672,8 @@ }, "node_modules/vitest": { "version": "4.1.11", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.11.tgz", + "integrity": "sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==", "dev": true, "license": "MIT", "dependencies": { @@ -3373,6 +3762,8 @@ }, "node_modules/w3c-xmlserializer": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", "dev": true, "license": "MIT", "dependencies": { @@ -3384,6 +3775,8 @@ }, "node_modules/webidl-conversions": { "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", "dev": true, "license": "BSD-2-Clause", "engines": { @@ -3392,6 +3785,8 @@ }, "node_modules/whatwg-mimetype": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", "dev": true, "license": "MIT", "engines": { @@ -3400,6 +3795,8 @@ }, "node_modules/whatwg-url": { "version": "16.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", + "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", "dev": true, "license": "MIT", "dependencies": { @@ -3413,6 +3810,8 @@ }, "node_modules/which": { "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "license": "ISC", "dependencies": { @@ -3427,6 +3826,8 @@ }, "node_modules/why-is-node-running": { "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", "dev": true, "license": "MIT", "dependencies": { @@ -3442,6 +3843,8 @@ }, "node_modules/word-wrap": { "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, "license": "MIT", "engines": { @@ -3450,6 +3853,8 @@ }, "node_modules/xml-name-validator": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", "dev": true, "license": "Apache-2.0", "engines": { @@ -3458,16 +3863,22 @@ }, "node_modules/xmlchars": { "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "dev": true, "license": "MIT" }, "node_modules/yallist": { "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true, "license": "ISC" }, "node_modules/yocto-queue": { "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, "license": "MIT", "engines": { @@ -3479,6 +3890,8 @@ }, "node_modules/zod": { "version": "4.5.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.5.4.tgz", + "integrity": "sha512-sC95tT5iHHH9gtpj6A81kh+NEaRAUFN+qlUPDUbRfOMvNf5QCBqsb3WgvnpVtK5Y+4UfA6KqufotuTvMGiTlsA==", "dev": true, "license": "MIT", "funding": { @@ -3487,6 +3900,8 @@ }, "node_modules/zod-validation-error": { "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", "dev": true, "license": "MIT", "engines": { diff --git a/package.json b/package.json index 7120d1a..e5d249a 100644 --- a/package.json +++ b/package.json @@ -1,16 +1,16 @@ { - "name": "text-tools", - "version": "0.2.0", - "description": "Transform and inspect plain text locally in the browser.", + "name": "translator-tools", + "version": "0.1.0", + "description": "Review and translate i18next-like JSON language files locally.", "license": "GPL-3.0-or-later", "author": "Albrecht Degering", "repository": { "type": "git", - "url": "git+https://git.add-ideas.de/lotobo/text-tools.git" + "url": "git+https://git.add-ideas.de/lotobo/translator-tools.git" }, - "homepage": "https://git.add-ideas.de/lotobo/text-tools", + "homepage": "https://git.add-ideas.de/lotobo/translator-tools", "bugs": { - "url": "https://git.add-ideas.de/lotobo/text-tools/issues" + "url": "https://git.add-ideas.de/lotobo/translator-tools/issues" }, "private": true, "type": "module", @@ -42,6 +42,7 @@ "@add-ideas/toolbox-contract": "0.3.0", "@add-ideas/toolbox-helpers": "0.2.0", "@add-ideas/toolbox-shell-react": "0.3.0", + "json5": "^2.2.3", "react": "19.2.8", "react-dom": "19.2.8" }, diff --git a/public/CHANGELOG.md b/public/CHANGELOG.md index e9e4ff0..54747a3 100644 --- a/public/CHANGELOG.md +++ b/public/CHANGELOG.md @@ -1,13 +1,11 @@ # Changelog -## 0.2.0 - 2026-09-02 +## Unreleased -- Added composable literal and line transforms with allocation preflights, plus BOM, UTF-8 validity, zero-byte and byte-newline evidence. -- Added paired text/artifact-evidence downloads with source/output hashes and - pipeline provenance; automatic Toolbox handoff remains intentionally disabled - pending the coordinated Portal consumer rollout. +- Added local translator workspace for i18next-like and single-locale JSON review. +- Added language pair selection, glossary storage and fuzzy in-context suggestions. +- Added local-only import/export pipeline with portal handoff integration. -## 0.1.0 - 2026-09-01 +## 0.1.0 - 2026-09-02 -- Added the initial local-first Text Tools workbench. -- Added Toolbox integration, bounded input handling, offline shell and deterministic release packaging. +- Initial local-first translator review implementation. diff --git a/public/LICENSES/npm-runtime-licenses.txt b/public/LICENSES/npm-runtime-licenses.txt index 4f412f1..644fc84 100644 --- a/public/LICENSES/npm-runtime-licenses.txt +++ b/public/LICENSES/npm-runtime-licenses.txt @@ -1077,6 +1077,36 @@ Declared licence: Apache-2.0 limitations under the License. +============================================================================== +json5@2.2.3 +Declared licence: MIT +============================================================================== +--- LICENSE.md --- +MIT License + +Copyright (c) 2012-2018 Aseem Kishore, and [others]. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +[others]: https://github.com/json5/json5/contributors + + ============================================================================== react@19.2.8 Declared licence: MIT diff --git a/public/README.md b/public/README.md index b0b4dc6..eb14a3f 100644 --- a/public/README.md +++ b/public/README.md @@ -1,24 +1,25 @@ -# Text Tools +# Translator Tools -Compose, inspect and evidence plain-text transformations locally in the browser. +Translator Tools is a standalone local-first application in the +[add·ideas Toolbox](https://git.add-ideas.de/lotobo/toolbox-portal). Inputs are +parsed and edited in the browser only; no locale strings are uploaded. -Text Tools is a standalone local-first application in the [add·ideas Toolbox](https://git.add-ideas.de/lotobo/toolbox-portal). Inputs are processed in the browser and are not uploaded. +## Quick scope -## Version 0.2 scope +- Import locale files (`.json`, `.js`, `.txt`) in i18next-like or single-locale forms. +- Review authoritative language against one target language at a time. +- Edit source and target strings and copy-source quickly. +- Add translation glossary entries per language pair and receive fuzzy suggestions. +- Add new languages and export a reviewer-updated translation bundle locally. +- Send exports through the same-tool transfer channel to the Portal. -- Ordered pipelines for line endings, trimming, whitespace, stable sorting, deduplication, literal replacement, line prefix/suffix/filter/number/join and line-order reversal -- Locale-aware case conversion, Unicode NFC/NFD/NFKC/NFKD and best-effort Latin transliteration -- JSON/HTML/URL/Base64/hex escaping plus strict inverse decode/unescape stages; binary decoders accept canonical encodings and require valid UTF-8 text -- Bounded RFC-style quoted CSV column selection across embedded delimiters and newlines, alongside the retained literal multi-character delimiter-per-line mode -- Explicit UTF-8, UTF-16LE and Latin-1 file decoding plus BOM, malformed UTF-8 offset, zero-byte parity, byte-level newline and replacement-character evidence -- Versioned JSON recipe import/export, per-step size/change reports, and a paired text-plus-JSON artifact download with source/output SHA-256 and transformation provenance +## Notes -Pipelines are limited to 2,000,000 UTF-16 input units and an 8×/32 MiB output expansion. File input is limited to 16 MiB before decoding. Encoding is selected explicitly rather than guessed; compatibility normalisation, transliteration, narrow encodings and several transforms can be lossy, so source, result, and warnings remain visible. See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) and [docs/PRIVACY-SECURITY.md](docs/PRIVACY-SECURITY.md). - -The artifact evidence is a portable download contract, not an automatic Toolbox -handoff. SDK 0.3.0 provides the shared transfer contract, but automatic Open With -remains disabled in this release until the Portal consumer rollout is -coordinated. Local downloads stay explicit and portable in the meantime. +- The parser accepts JavaScript-style locale assignment forms such as + `const de = { ... }` and tolerant spacing/commas. +- Parsing and transfers are bounded to 5 MiB per imported/exported transfer file. +- Language detection relies on file name hints (`de_*.json`) and explicit user + selection. ## Development @@ -32,7 +33,7 @@ npm run test:browser ## Release -`npm run release:artifact` creates a deterministic `release/text-tools-0.2.0.zip` and checksum sidecar. +`npm run release:artifact` creates a deterministic `release/translator-tools-0.1.0.zip` and checksum sidecar. ## Licence diff --git a/public/THIRD_PARTY_NOTICES.md b/public/THIRD_PARTY_NOTICES.md index b0d6612..96d9d7b 100644 --- a/public/THIRD_PARTY_NOTICES.md +++ b/public/THIRD_PARTY_NOTICES.md @@ -1,6 +1,6 @@ # Third-party notices -Text Tools 0.2.0 directly depends on these runtime packages: +Translator Tools 0.1.0 directly depends on these runtime packages: | Package | Pinned version | Declared licence | | -------------------------------- | -------------: | ---------------- | diff --git a/public/docs/ARCHITECTURE.md b/public/docs/ARCHITECTURE.md index cf1d583..65850b1 100644 --- a/public/docs/ARCHITECTURE.md +++ b/public/docs/ARCHITECTURE.md @@ -1,9 +1,16 @@ # Architecture -Text Tools is a static React/Vite application wrapped in the shared Toolbox shell. `text/pipeline.ts` defines an ordered, typed list of transformations. `applyPipeline` starts from the exact source, applies enabled steps one at a time, checks expansion after every step, and returns the last successful output with per-step before/after counts and warnings. +Translator Tools is a static React/Vite application wrapped in the shared Toolbox shell. -Text encoding/decoding, line-ending conversion, bounded CSV parsing and Unicode-aware case primitives come from `@add-ideas/toolbox-helpers`. File decoding uses an explicit encoding and fatal-or-replacement choice; it does not guess a charset. Inverse JSON/HTML/URL/Base64/hex stages are strict: malformed syntax and non-UTF-8 byte output fail the step rather than being silently repaired. Column selection offers a quoted CSV mode that preserves embedded delimiters/newlines and a separate literal-delimiter-per-line mode. Recipe JSON carries a schema/app identity, accepts at most 100 supported steps, discards supplied IDs and assigns local monotonic IDs. +The workbench parses a single imported locale file in-browser, normalizes either: -`text/evidence.ts` inspects original bytes before decoding, including BOM, exact UTF-8 validity/first invalid offset, zero-byte parity and CR/LF/CRLF counts. Artifact export emits the selected output bytes and a deterministic evidence document containing canonical-source and exact-output SHA-256 values, inventories, recipe and step reports. The document records that direct Toolbox handoff is unavailable in this release; the SDK 0.3.0 transfer contract is intentionally not activated until the Portal consumer rollout is coordinated. +- i18next-like language maps (`{ "en": {...}, "de": {...} }`) +- single-locale objects (`{ "key": "value" }`, or JS-style assignment forms like `const de = {...}`). -Version 0.2 operations are synchronous and bounded, so the app creates no worker and has no persistence or server API. Relative entry and asset URLs keep the build relocatable below a nested portal path. +Strings are flattened into stable dot paths for review/editing and can be serialized +back with an inverse unflattening step. Suggestions come from local glossary entries +and are matched by language pair and fuzzy string distance. + +No background workers are required today. Persistent state is limited to a local +Storage glossary cache; everything else remains in process memory until download or +transfer. diff --git a/public/docs/PRIVACY-SECURITY.md b/public/docs/PRIVACY-SECURITY.md index d6bc791..47d39f4 100644 --- a/public/docs/PRIVACY-SECURITY.md +++ b/public/docs/PRIVACY-SECURITY.md @@ -1,9 +1,16 @@ # Privacy and security -Source text, imported files, recipes and results stay in page memory. There is no telemetry, analytics, account, persistence or runtime network path. Imported text is rendered as text/preformatted content, not executable HTML; HTML escaping produces a string and does not preview it as markup. +Locale files and generated outputs stay in-browser and are not uploaded. -Files are rejected above 16 MiB before reading. Decoded/pasted pipeline input is limited to 2,000,000 UTF-16 units, recipe text to 1,000,000 units/100 steps, and output to both 8× the original (with a small-input floor) and 32 MiB. Quoted CSV selection additionally limits rows, columns and field size. Strict inverse stages reject malformed escapes/encodings and invalid UTF-8; they do not attempt permissive repair. These limits reduce accidental expansion but do not prove that output is safe for a downstream interpreter. +No accounts, telemetry or runtime network requests are used for the core workflow. +The app stores glossary terms in local browser storage so they are available on the same +device for repeated reviews. -Compatibility normalisation, case conversion, transliteration, whitespace/line transforms, column selection and narrow encodings can change or lose information. Locale sorting depends on the browser's `Intl.Collator`. The exact source, output, per-step counts and warnings remain visible so users can review those changes before copying or downloading. +Parser/serializer limits are bounded to prevent accidental expansion: -Byte evidence does not guess an encoding or prove the selected decoder is correct. A valid UTF-8 byte sequence may represent text intended for another encoding, zero-byte parity is only a UTF-16 review signal, and replacement characters may already have existed in the source. Artifact evidence hashes canonical UTF-8 source text and exact exported bytes, clearly labels both, and includes filenames and transformation details that may themselves be sensitive. +- Imports and exports are capped at 5 MiB each. +- Large objects are flattened and rendered with per-session bounds. + +Suggested translations are purely local matching; they do not call external services. +Transfers to the Portal are signed local handoffs only and never stored by a remote +processor by default. diff --git a/public/favicon.svg b/public/favicon.svg index 321e6b6..a15ea2c 100644 --- a/public/favicon.svg +++ b/public/favicon.svg @@ -1 +1 @@ -TE +TR diff --git a/public/manifest.webmanifest b/public/manifest.webmanifest index 66894b0..3d030d8 100644 --- a/public/manifest.webmanifest +++ b/public/manifest.webmanifest @@ -1,7 +1,7 @@ { - "name": "Text Tools", - "short_name": "Text", - "description": "Transform and inspect plain text locally in the browser.", + "name": "Translator Tools", + "short_name": "Translator", + "description": "Review and edit locale strings locally in the browser.", "start_url": "./", "scope": "./", "display": "standalone", diff --git a/public/sw.js b/public/sw.js index e018ca8..b4e01d2 100644 --- a/public/sw.js +++ b/public/sw.js @@ -1,5 +1,5 @@ -const CACHE_PREFIX = "text-tools-shell-"; -const CACHE_NAME = CACHE_PREFIX + "0.2.0"; +const CACHE_PREFIX = "translator-tools-shell-"; +const CACHE_NAME = CACHE_PREFIX + "0.1.0"; const CORE = ["./", "./manifest.webmanifest", "./favicon.svg"]; self.addEventListener("install", (event) => { event.waitUntil( diff --git a/public/toolbox-app.json b/public/toolbox-app.json index e2ddccd..0b9ce61 100644 --- a/public/toolbox-app.json +++ b/public/toolbox-app.json @@ -1,22 +1,21 @@ { "$schema": "https://git.add-ideas.de/lotobo/toolbox-sdk/raw/branch/main/schemas/toolbox-app.v1.schema.json", "schemaVersion": 1, - "id": "de.add-ideas.text-tools", - "name": "Text Tools", - "version": "0.2.0", - "description": "Compose text transforms and export encoding evidence locally.", + "id": "de.add-ideas.translator-tools", + "name": "Translator Tools", + "version": "0.1.0", + "description": "Review and edit translation bundles in the browser with glossary suggestions.", "entry": "./", "icon": "./favicon.svg", - "categories": ["text", "developer", "productivity"], + "categories": ["text", "developer", "productivity", "i18n"], "tags": [ - "text", - "unicode", - "pipeline", - "normalize", - "sort", - "escape", - "encoding", - "newline" + "translation", + "i18n", + "locale", + "glossary", + "review", + "json", + "localization" ], "integration": { "contextVersion": 1, @@ -26,32 +25,30 @@ "requirements": { "secureContext": false, "workers": false, - "indexedDb": false, + "indexedDb": true, "crossOriginIsolated": false, "topLevelContext": false }, "io": { "accepts": [ { - "mediaType": "text/*", - "extensions": [".txt", ".csv", ".md", ".log"], - "label": "Bounded text files" + "mediaType": "application/json", + "extensions": [".json", ".js"], + "label": "Locale JSON or i18next-like translation files" } ], "produces": [ - { - "mediaType": "text/plain", - "extensions": [".txt"], - "label": "Transformed text" - }, { "mediaType": "application/json", "extensions": [".json"], - "label": "Recipe and artifact evidence" + "label": "Reviewed translation bundle" } ] }, - "capabilities": { "required": [], "optional": ["web-crypto"] }, + "capabilities": { + "required": [], + "optional": ["indexeddb", "local-artifact-handoff"] + }, "privacy": { "processing": "local", "fileUploads": true, @@ -59,14 +56,14 @@ "label": "Inputs stay in this browser; nothing is uploaded." }, "source": { - "repository": "https://git.add-ideas.de/lotobo/text-tools", + "repository": "https://git.add-ideas.de/lotobo/translator-tools", "license": "GPL-3.0-or-later" }, "actions": [ { "id": "source", "label": "Source", - "url": "https://git.add-ideas.de/lotobo/text-tools" + "url": "https://git.add-ideas.de/lotobo/translator-tools" } ] } diff --git a/src/App.tsx b/src/App.tsx index 9c50fe7..e61353d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,10 +1,14 @@ -import { lazy, Suspense, useState } from "react"; +import { lazy, Suspense, useEffect, useMemo, useRef, useState } from "react"; import { AppShell } from "@add-ideas/toolbox-shell-react"; import "@add-ideas/toolbox-shell-react/styles.css"; import "./styles.css"; import { ErrorBoundary } from "./components/ErrorBoundary"; import { HelpDialog } from "./components/HelpDialog"; import { manifest } from "./toolbox/manifest"; +import { + receiveTranslatorTransfer, + type IncomingTranslatorTransfer, +} from "./toolbox/transfer"; const Workbench = lazy(async () => ({ default: (await import("./components/Workbench")).Workbench, @@ -12,6 +16,23 @@ const Workbench = lazy(async () => ({ export function App() { const [helpOpen, setHelpOpen] = useState(false); + const [incoming, setIncoming] = useState(); + const incomingStarted = useRef(false); + const activeSourceLabel = useMemo(() => "Translator Tools", []); + + useEffect(() => { + if (incomingStarted.current) return; + incomingStarted.current = true; + void receiveTranslatorTransfer() + .then((value) => setIncoming(value)) + .catch((reason: unknown) => + setIncoming({ + status: "missing", + error: reason instanceof Error ? reason.message : String(reason), + }), + ); + }, []); + return ( - Preparing Text Tools… + Preparing {activeSourceLabel}…

} > - +
setHelpOpen(false)} /> diff --git a/src/components/ErrorBoundary.tsx b/src/components/ErrorBoundary.tsx index 664d2ab..32d1a74 100644 --- a/src/components/ErrorBoundary.tsx +++ b/src/components/ErrorBoundary.tsx @@ -15,7 +15,7 @@ export class ErrorBoundary extends Component< if (this.state.error) return (
-

Text Tools could not continue

+

Translator Tools could not continue

{this.state.error.message}

-

Transform and inspect plain text locally in the browser.

- All processing is performed in this browser. Imported data is treated as - untrusted and bounded before parsing. + Review and translate i18next-style locale JSON files directly in your + browser. No network upload is required. +

+

+ Imported data is parsed with bounded input checks and can include + JavaScript-style objects (single quotes, trailing commas). Glossary + terms are saved to local storage in this browser only.

); diff --git a/src/components/Workbench.tsx b/src/components/Workbench.tsx index 867a9d7..702d9ec 100644 --- a/src/components/Workbench.tsx +++ b/src/components/Workbench.tsx @@ -1,743 +1,1023 @@ -import { useState } from "react"; +import { useEffect, useMemo, useRef, useState } from "react"; +import JSON5 from "json5"; +import { triggerBlobDownload } from "@add-ideas/toolbox-helpers"; import { - encodeText, - triggerBlobDownload, - triggerBlobDownloads, - type TextEncoding, -} from "@add-ideas/toolbox-helpers"; -import { - applyPipeline, - createStep, - textInventory, - type PipelineResult, - type StepType, - type TransformStep, -} from "../text/pipeline"; -import { - createTextArtifactEvidence, - decodeTextWithEvidence, - type TextByteEvidence, -} from "../text/evidence"; + prepareTranslatorPortalHandoff, + type IncomingTranslatorTransfer, +} from "../toolbox/transfer"; -const initial = " Crème brûlée \r\nAlpha\nalpha\r\n Cedar \n"; -const initialSteps: TransformStep[] = [ - createStep("trim-lines"), - createStep("dedupe-lines"), - createStep("normalize"), -]; -const STEP_LABELS: Record = { - "line-endings": "Line endings", - "trim-lines": "Trim every line", - "trim-document": "Trim document", - "collapse-whitespace": "Collapse whitespace", - "sort-lines": "Sort lines", - "dedupe-lines": "Deduplicate lines", - case: "Change case", - normalize: "Unicode normalization", - transliterate: "Best-effort transliteration", - escape: "Escape / encode", - unescape: "Decode / unescape strictly", - wrap: "Wrap text", - columns: "Select/reorder columns", - "replace-literal": "Replace literal text", - "prefix-lines": "Prefix lines", - "suffix-lines": "Suffix lines", - "filter-lines": "Keep matching lines", - "number-lines": "Number lines", - "join-lines": "Join lines", - "reverse-lines": "Reverse line order", +type Mode = "single-locale" | "language-map"; + +type FlatLocale = Record; + +type Workspace = { + mode: Mode; + sourceLanguage: string; + targetLanguage: string; + localeMap: Record; }; -function Option({ - step, - change, -}: { - step: TransformStep; - change: (option: string) => void; -}) { - if ( - [ - "trim-lines", - "trim-document", - "dedupe-lines", - "transliterate", - "reverse-lines", - ].includes(step.type) - ) - return No options; - if (step.type === "line-endings") - return ( - - ); - if (step.type === "collapse-whitespace") - return ( - - ); - if (step.type === "case") - return ( - - ); - if (step.type === "normalize") - return ( - - ); - if (step.type === "escape" || step.type === "unescape") - return ( - - ); - if (step.type === "sort-lines") - return ( - change(event.target.value)} - placeholder="BCP 47 locale, e.g. en" - /> - ); - if (step.type === "wrap") - return ( - change(event.target.value)} - /> - ); - if (step.type === "replace-literal") { - let values: [string, string] = ["", ""]; - try { - const parsed = JSON.parse(step.option) as unknown; - if ( - Array.isArray(parsed) && - parsed.length === 2 && - parsed.every((value) => typeof value === "string") - ) - values = parsed as [string, string]; - } catch { - /* Keep editable empty values; apply will report invalid stored JSON. */ - } - return ( -
- - change(JSON.stringify([event.target.value, values[1]])) - } - /> - - change(JSON.stringify([values[0], event.target.value])) - } - /> -
- ); +type GlossaryTerm = { + id: string; + sourceLanguage: string; + targetLanguage: string; + sourceText: string; + targetText: string; + notes?: string; +}; + +const MAX_TRANSFER_BYTES = 5 * 1024 * 1024; +const MAX_PATH_ROWS = 5000; +const GLossaryStorageKey = "de.add-ideas.translator-tools.glossary-v1"; + +function normalizeLocaleCode(value: string): string { + return value.trim().toLowerCase().replace(/_/gu, "-"); +} + +function isRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null && !Array.isArray(value); +} + +function flattenLocale( + source: unknown, + prefix: string, + out: FlatLocale, + onSkip: (path: string) => void, +): void { + if (source === null || source === undefined) return; + if (typeof source === "string") { + if (prefix) out[prefix] = source; + return; } - if ( - [ - "prefix-lines", - "suffix-lines", - "filter-lines", - "number-lines", - "join-lines", - ].includes(step.type) - ) - return ( - change(event.target.value)} - placeholder={ - step.type === "join-lines" - ? "Delimiter; \\n and \\t supported" - : "Text" - } - /> - ); - const pieces = step.option.split("|"); - const mode = pieces.length >= 3 ? pieces[0]! : "literal"; - const delimiter = pieces.length >= 3 ? pieces[1]! : pieces[0] || ","; - const order = - pieces.length >= 3 ? pieces.slice(2).join("|") : pieces[1] || "1"; - const encode = (nextMode: string, nextDelimiter: string, nextOrder: string) => - change(`${nextMode}|${nextDelimiter}|${nextOrder}`); - return ( -
- - encode(mode, event.target.value, order)} - placeholder=", or \\t" - /> - encode(mode, delimiter, event.target.value)} - placeholder="3,1,2" - /> -
- ); -} - -function Inventory({ value }: { value: string }) { - const facts = textInventory(value); - return ( -
-
-
UTF-16 units
-
{facts.utf16Units.toLocaleString()}
-
-
-
Code points
-
{facts.codePoints.toLocaleString()}
-
-
-
Lines
-
{facts.lines.toLocaleString()}
-
-
-
CRLF / LF / CR
-
- {facts.crlf} / {facts.bareLf} / {facts.bareCr} -
-
-
-
Final newline
-
{facts.finalNewline ? "Yes" : "No"}
-
-
- ); -} - -export function Workbench() { - const [source, setSource] = useState(initial); - const [steps, setSteps] = useState(initialSteps); - const [result, setResult] = useState(() => - applyPipeline(initial, initialSteps), - ); - const [newType, setNewType] = useState("line-endings"); - const [inputEncoding, setInputEncoding] = useState("utf-8"); - const [outputEncoding, setOutputEncoding] = useState("utf-8"); - const [fatalDecode, setFatalDecode] = useState(true); - const [error, setError] = useState(""); - const [recipe, setRecipe] = useState(""); - const [sourceEvidence, setSourceEvidence] = useState(); - const [sourceName, setSourceName] = useState("pasted-text.txt"); - const outputLoss = - outputEncoding === "latin1" && - [...result.output].some((character) => character.codePointAt(0)! > 255); - const updateStep = (id: string, changes: Partial) => - setSteps((current) => - current.map((step) => (step.id === id ? { ...step, ...changes } : step)), - ); - const move = (index: number, direction: -1 | 1) => - setSteps((current) => { - const target = index + direction; - if (target < 0 || target >= current.length) return current; - const next = [...current]; - [next[index], next[target]] = [next[target]!, next[index]!]; - return next; + if (isRecord(source)) { + for (const [key, value] of Object.entries(source)) { + const next = prefix ? `${prefix}.${key}` : key; + flattenLocale(value, next, out, onSkip); + } + return; + } + if (Array.isArray(source)) { + source.forEach((value, index) => { + const next = prefix ? `${prefix}.${index}` : `${index}`; + flattenLocale(value, next, out, onSkip); }); - const apply = () => { - try { - setResult(applyPipeline(source, steps)); - setError(""); - } catch (reason) { - setError(reason instanceof Error ? reason.message : "Pipeline failed."); + return; + } + onSkip(prefix); +} + +function unflattenLocale(locale: FlatLocale): unknown { + const root: Record = {}; + for (const [path, value] of Object.entries(locale)) { + const segments = path.split("."); + let cursor: Record = root; + for (let index = 0; index < segments.length; index += 1) { + const raw = segments[index]!.trim(); + const key = raw; + const isLast = index === segments.length - 1; + if (isLast) { + cursor[key] = value; + break; + } + const next = cursor[key]; + if (!isRecord(next)) { + cursor[key] = {}; + } + cursor = cursor[key] as Record; } + } + return sortObjectKeys(root); +} + +function sortObjectKeys(value: unknown): unknown { + if (!isRecord(value)) return value; + return Object.entries(value) + .sort(([a], [b]) => a.localeCompare(b)) + .reduce>((acc, [key, child]) => { + if (Array.isArray(child)) return { ...acc, [key]: child }; + acc[key] = sortObjectKeys(child); + return acc; + }, {}); +} + +function detectLikelyLanguageMap(value: Record): boolean { + const keys = Object.keys(value); + if (keys.length < 2) return false; + const codeCount = keys.filter(isLikelyLocaleCode).length; + const objectCount = keys.filter( + (key) => isRecord(value[key]) || typeof value[key] === "string", + ).length; + return codeCount >= 2 && objectCount >= keys.length * 0.8; +} + +function isLikelyLocaleCode(value: string): boolean { + return /^[a-z]{2,3}(?:-[a-z]{2,4})?$/iu.test(value); +} + +function guessLocaleFromFileName(name: string): string { + const file = name.replace(/^.*\//u, "").replace(/\.[^.]+$/u, ""); + const match = /(^|[._-])([a-z]{2,3}(?:-[a-z]{2})?)(?=$|[._-])/iu.exec(file); + return match?.[2]?.toLowerCase() ?? "en"; +} + +function parseTranslationInput(value: string): unknown { + try { + return JSON.parse(value); + } catch { + const trimmed = value.trim(); + const asAssignment = + /^(?:export\s+)?(?:const|let|var)\s+[a-zA-Z_$][\w$]*\s*=\s*([\s\S]*?)\s*;?\s*$/u.exec( + trimmed, + )?.[1]; + return JSON5.parse(asAssignment ?? trimmed); + } +} + +function normalizeSearchText(value: string): string { + return value + .toLocaleLowerCase("en") + .replace(/[\s\p{P}\p{S}]+/gu, " ") + .replace(/\s+/gu, " ") + .trim(); +} + +function levenshtein(a: string, b: string): number { + if (a === b) return 0; + if (!a.length) return b.length; + if (!b.length) return a.length; + const dp = Array.from({ length: b.length + 1 }, (_, index) => index); + for (let i = 1; i <= a.length; i += 1) { + let left = dp[0]!; + dp[0] = i; + for (let j = 1; j <= b.length; j += 1) { + const top = dp[j]!; + const cost = a.charAt(i - 1) === b.charAt(j - 1) ? 0 : 1; + const up = top + 1; + const leftDiag = left + cost; + dp[j] = Math.min(dp[j - 1]! + 1, up, leftDiag); + left = top; + } + } + return dp[b.length]!; +} + +function extractWorkspace( + value: unknown, + sourceFileName: string, +): Workspace & { warning?: string } { + if (!isRecord(value)) throw new Error("Translation input must be an object."); + + const entries = Object.entries(value); + if (entries.length === 0) + throw new Error("The translation object is empty and has no strings."); + + const warnings: string[] = []; + const localeMap: Record = {}; + if (detectLikelyLanguageMap(value)) { + for (const [lang, record] of entries) { + const normalizedLang = normalizeLocaleCode(lang); + const flat: FlatLocale = {}; + flattenLocale(record, "", flat, () => undefined); + localeMap[normalizedLang] = flat; + } + const languages = Object.keys(localeMap).sort(); + const sourceLanguage = languages[0] ?? "en"; + const targetLanguage = + languages[1] ?? (sourceLanguage === "en" ? "de" : "en"); + return { + mode: "language-map", + sourceLanguage, + targetLanguage: localeMap[targetLanguage] + ? targetLanguage + : sourceLanguage, + localeMap, + warning: warnings.join("; "), + }; + } + + const guessed = normalizeLocaleCode(guessLocaleFromFileName(sourceFileName)); + const flat: FlatLocale = {}; + flattenLocale(value, "", flat, (path) => { + warnings.push(`Skipped non-string value at ${path}`); + }); + localeMap[guessed] = flat; + return { + mode: "single-locale", + sourceLanguage: guessed, + targetLanguage: guessed === "en" ? "de" : "en", + localeMap, + warning: warnings.join("; ") || undefined, }; - const open = async (file: File | undefined) => { - if (!file) return; - if (file.size > 16 * 1024 * 1024) { - setError("File exceeds the 16 MiB byte-input limit."); - return; +} + +function serializeWorkspace(input: Workspace): string { + const locales = input.localeMap; + if (input.mode === "single-locale" && Object.keys(locales).length === 1) { + const source = locales[input.sourceLanguage]; + return JSON.stringify(unflattenLocale(source ?? {}), null, 2); + } + const payload: Record = {}; + for (const [lang, entries] of Object.entries(locales)) { + payload[lang] = unflattenLocale(entries); + } + return JSON.stringify(payload, null, 2); +} + +function pickSuggestions( + sourceText: string, + sourceLanguage: string, + targetLanguage: string, + glossary: ReadonlyArray, + max = 3, +): GlossaryTerm[] { + const normalized = normalizeSearchText(sourceText); + if (!normalized) return []; + const candidates: Array<{ item: GlossaryTerm; score: number }> = []; + for (const entry of glossary) { + const sourceMatch = + normalizeLocaleCode(entry.sourceLanguage) === sourceLanguage || + normalizeLocaleCode(entry.sourceLanguage) === "*"; + const targetMatch = + normalizeLocaleCode(entry.targetLanguage) === targetLanguage || + normalizeLocaleCode(entry.targetLanguage) === "*"; + if (!sourceMatch || !targetMatch) continue; + + const key = normalizeSearchText(entry.sourceText); + if (!entry.targetText.trim()) continue; + const distanceScore = + normalized === key + ? 1 + : normalized.includes(key) + ? 0.8 + : (() => { + const distance = levenshtein(normalized, key); + const ratio = Math.max(normalized.length, key.length); + const computed = ratio > 0 ? 1 - distance / ratio : 0; + return computed; + })(); + if (distanceScore < 0.64) continue; + if (distanceScore > 0) { + candidates.push({ item: entry, score: distanceScore }); } + } + return candidates + .sort((a, b) => b.score - a.score) + .slice(0, max) + .map((entry) => entry.item); +} + +function stableId(): string { + return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 9)}`; +} + +function safeParseGlossary(raw: string): GlossaryTerm[] { + const parsed = JSON.parse(raw); + if (!Array.isArray(parsed)) return []; + return parsed + .filter( + (value): value is GlossaryTerm => + isRecord(value) && + typeof value.id === "string" && + typeof value.sourceLanguage === "string" && + typeof value.targetLanguage === "string" && + typeof value.sourceText === "string" && + typeof value.targetText === "string", + ) + .map((entry) => ({ + ...entry, + sourceLanguage: normalizeLocaleCode(entry.sourceLanguage), + targetLanguage: normalizeLocaleCode(entry.targetLanguage), + sourceText: entry.sourceText, + targetText: entry.targetText, + })); +} + +function orderedLanguageOptions(input: string[]): string[] { + return [...input] + .filter((lang) => lang.trim().length > 0) + .map(normalizeLocaleCode) + .sort(); +} + +function formatPath(path: string): string { + return path; +} + +function formatCount(value: number): string { + return new Intl.NumberFormat("en").format(value); +} + +export function Workbench({ + incoming, +}: { + incoming?: IncomingTranslatorTransfer; +}) { + const [sourceFileName, setSourceFileName] = useState("translations.json"); + const [rawInput, setRawInput] = useState(""); + const [rawImportError, setRawImportError] = useState(""); + const [importWarning, setImportWarning] = useState(""); + const [workspace, setWorkspace] = useState({ + mode: "single-locale", + sourceLanguage: "de", + targetLanguage: "en", + localeMap: { de: {} }, + }); + const [filter, setFilter] = useState(""); + const [status, setStatus] = useState(""); + const [glossary, setGlossary] = useState(() => { try { - const decoded = decodeTextWithEvidence( - new Uint8Array(await file.arrayBuffer()), - inputEncoding, - fatalDecode, - ); - if (decoded.text.length > 2_000_000) - throw new Error( - "Decoded text exceeds the 2,000,000 UTF-16-unit pipeline limit.", - ); - setSource(decoded.text); - setSourceEvidence(decoded.evidence); - setSourceName(file.name); - setError(""); - } catch (reason) { - setError( - reason instanceof Error - ? reason.message - : `File is not valid ${inputEncoding} in ${fatalDecode ? "fatal" : "replacement"} mode.`, - ); + const stored = window.localStorage.getItem(GLossaryStorageKey); + if (!stored) return []; + return safeParseGlossary(stored); + } catch { + return []; } - }; - const download = () => { - if (outputLoss) { - setError( - "ISO-8859-1 output would lose characters above U+00FF. Choose another encoding or transform the text explicitly.", - ); - return; + }); + const incomingToken = useRef(undefined); + + const [newTermSource, setNewTermSource] = useState(""); + const [newTermTarget, setNewTermTarget] = useState(""); + const [newTermSourceLang, setNewTermSourceLang] = useState("de"); + const [newTermTargetLang, setNewTermTargetLang] = useState("en"); + const [newLanguageCode, setNewLanguageCode] = useState(""); + + const languageOptions = useMemo( + () => orderedLanguageOptions(Object.keys(workspace.localeMap)), + [workspace.localeMap], + ); + + const effectiveSourceLanguage = useMemo(() => { + if (workspace.localeMap[workspace.sourceLanguage]) + return workspace.sourceLanguage; + return languageOptions[0] ?? workspace.sourceLanguage; + }, [languageOptions, workspace.localeMap, workspace.sourceLanguage]); + + const availableTargetLanguages = useMemo( + () => languageOptions.filter((value) => value !== effectiveSourceLanguage), + [effectiveSourceLanguage, languageOptions], + ); + + const effectiveTargetLanguage = useMemo(() => { + if ( + availableTargetLanguages.includes(workspace.targetLanguage) && + workspace.targetLanguage !== effectiveSourceLanguage + ) + return workspace.targetLanguage; + return availableTargetLanguages[0] ?? effectiveSourceLanguage; + }, [ + availableTargetLanguages, + effectiveSourceLanguage, + workspace.targetLanguage, + ]); + + useEffect(() => { + try { + window.localStorage.setItem(GLossaryStorageKey, JSON.stringify(glossary)); + } catch { + // Local storage is optional and best effort. } - triggerBlobDownload( - new Blob([encodeText(result.output, outputEncoding) as BlobPart], { - type: "text/plain", - }), - `transformed-${outputEncoding}.txt`, + }, [glossary]); + + useEffect(() => { + if (incoming?.status !== "ready") return; + if (incomingToken.current === incoming.transfer.token) return; + incomingToken.current = incoming.transfer.token; + void openFile(incoming.file).catch((error: unknown) => + setStatus( + error instanceof Error + ? error.message + : "Failed to open transfer file.", + ), ); - }; - const exportRecipe = () => { - const value = JSON.stringify( - { schemaVersion: 1, app: "text-tools", version: "0.2.0", steps }, - null, - 2, - ); - setRecipe(value); - triggerBlobDownload( - new Blob([value], { type: "application/json" }), - "text-tools-recipe.json", - ); - }; - const exportArtifact = async () => { - try { - const bytes = encodeText(result.output, outputEncoding); - const evidence = await createTextArtifactEvidence({ - sourceName, - sourceEvidence, - sourceText: source, - outputName: `transformed-${outputEncoding}.txt`, - outputEncoding, - outputBytes: bytes, - pipeline: result, - steps, - }); - triggerBlobDownloads( - [ - { - blob: new Blob([bytes as BlobPart], { type: "text/plain" }), - filename: evidence.output.name, - }, - { - blob: new Blob([JSON.stringify(evidence, null, 2) + "\n"], { - type: "application/json", - }), - filename: "text-tools-artifact-evidence.json", - }, - ], - { maximumFiles: 2, order: "input", revokeDelayMs: 1_000 }, - ); - setError(""); - } catch (reason) { - setError( - reason instanceof Error ? reason.message : "Artifact export failed.", - ); - } - }; - const importRecipe = () => { - try { - if (recipe.length > 1_000_000) - throw new Error("Recipe exceeds 1,000,000 UTF-16 units."); - const parsed = JSON.parse(recipe) as { - schemaVersion?: unknown; - app?: unknown; - steps?: unknown; - }; - if ( - parsed.schemaVersion !== 1 || - parsed.app !== "text-tools" || - !Array.isArray(parsed.steps) || - parsed.steps.length > 100 + }, [incoming]); + + const rows = useMemo(() => { + const source = workspace.localeMap[effectiveSourceLanguage] ?? {}; + const target = workspace.localeMap[effectiveTargetLanguage] ?? {}; + const allKeys = new Set([...Object.keys(source), ...Object.keys(target)]); + const query = filter.trim().toLocaleLowerCase("en"); + const values = Array.from(allKeys) + .sort((left, right) => left.localeCompare(right)) + .map((path) => ({ + key: path, + source: source[path] ?? "", + target: target[path] ?? "", + })) + .filter( + (row) => + !query || + row.key.toLocaleLowerCase("en").includes(query) || + row.source.toLocaleLowerCase("en").includes(query) || + row.target.toLocaleLowerCase("en").includes(query), ) - throw new Error("Recipe envelope is invalid."); - const accepted = parsed.steps.map((entry): TransformStep => { - if (!entry || typeof entry !== "object") - throw new Error("Recipe step is invalid."); - const value = entry as Partial; - if ( - typeof value.type !== "string" || - !Object.hasOwn(STEP_LABELS, value.type) || - typeof value.option !== "string" || - typeof value.enabled !== "boolean" - ) - throw new Error("Recipe contains an unsupported step."); - return { - ...createStep(value.type), - option: value.option.slice(0, 10_000), - enabled: value.enabled, - }; + .slice(0, MAX_PATH_ROWS); + return values; + }, [ + filter, + workspace.localeMap, + effectiveSourceLanguage, + effectiveTargetLanguage, + ]); + + const totalMissing = useMemo(() => { + let missing = 0; + for (const row of rows) if (!row.target.trim()) missing += 1; + return missing; + }, [rows]); + + function setCell(language: string, path: string, next: string): void { + setWorkspace((current) => { + const locale = { ...(current.localeMap[language] ?? {}) }; + locale[path] = next; + return { + ...current, + localeMap: { + ...current.localeMap, + [language]: locale, + }, + }; + }); + } + + async function openFile(file: File): Promise { + if (!file) return; + if (!file.size || file.size > MAX_TRANSFER_BYTES) { + setStatus("Input is too large. Max supported size is 5 MiB."); + return; + } + const next = await file.text(); + setRawInput(next); + try { + const parsed = parseTranslationInput(next); + const nextWorkspace = extractWorkspace(parsed, file.name); + setWorkspace({ + mode: nextWorkspace.mode, + sourceLanguage: nextWorkspace.sourceLanguage, + targetLanguage: nextWorkspace.targetLanguage, + localeMap: nextWorkspace.localeMap, }); - setSteps(accepted); - setError(""); + setSourceFileName(file.name); + setImportWarning(nextWorkspace.warning ?? ""); + setRawImportError(""); + setStatus(`Loaded ${file.name}`); } catch (reason) { - setError( + setRawImportError( reason instanceof Error ? reason.message - : "Recipe could not be imported.", + : "Could not parse the translation document.", + ); + } + } + + const openRaw = async () => { + if (rawInput.length === 0) { + setRawImportError("Paste translation JSON first."); + return; + } + const parsedSize = new Blob([rawInput]).size; + if (!parsedSize || parsedSize > MAX_TRANSFER_BYTES) { + setRawImportError("Pasted text exceeds 5 MiB."); + return; + } + try { + const parsed = parseTranslationInput(rawInput); + const nextWorkspace = extractWorkspace(parsed, sourceFileName); + setWorkspace({ + mode: nextWorkspace.mode, + sourceLanguage: nextWorkspace.sourceLanguage, + targetLanguage: nextWorkspace.targetLanguage, + localeMap: nextWorkspace.localeMap, + }); + setImportWarning(nextWorkspace.warning ?? ""); + setRawImportError(""); + setStatus("Loaded pasted translation JSON."); + } catch (reason) { + setRawImportError( + reason instanceof Error + ? reason.message + : "Could not parse the pasted translation document.", ); } }; + const addLanguage = () => { + const next = normalizeLocaleCode(newLanguageCode); + if (!next) { + setStatus("Add a language code first."); + return; + } + if (!isLikelyLocaleCode(next)) { + setStatus("Language code must look like en or en-us."); + return; + } + if (workspace.localeMap[next]) { + setStatus(`Language ${next} already exists.`); + return; + } + setWorkspace((current) => ({ + ...current, + mode: "language-map", + targetLanguage: current.targetLanguage, + localeMap: { + ...current.localeMap, + [next]: {}, + }, + })); + setNewLanguageCode(""); + setStatus(`Added language ${next}.`); + }; + + const removeLanguage = (lang: string) => { + if (Object.keys(workspace.localeMap).length <= 1) { + setStatus("At least one language must stay in the workspace."); + return; + } + setWorkspace((current) => { + const clone = { ...current.localeMap }; + delete clone[lang]; + const languages = Object.keys(clone); + const sourceLanguage = + current.sourceLanguage === lang + ? languages[0]! + : current.sourceLanguage; + const targetLanguage = + current.targetLanguage === lang || + current.targetLanguage === sourceLanguage + ? (languages[1] ?? languages[0]!) + : current.targetLanguage; + return { + ...current, + mode: languages.length > 1 ? "language-map" : "single-locale", + sourceLanguage, + targetLanguage, + localeMap: clone, + }; + }); + setStatus(`Removed language ${lang}.`); + }; + + const addGlossaryTerm = () => { + const sourceLanguage = normalizeLocaleCode(newTermSourceLang); + const targetLanguage = normalizeLocaleCode(newTermTargetLang); + const sourceText = newTermSource.trim(); + const targetText = newTermTarget.trim(); + if (!sourceLanguage || !targetLanguage || !sourceText || !targetText) { + setStatus( + "Fill source language, target language, source text and target text.", + ); + return; + } + setGlossary((current) => [ + ...current, + { + id: stableId(), + sourceLanguage, + targetLanguage, + sourceText, + targetText, + }, + ]); + setNewTermSource(""); + setNewTermTarget(""); + setNewTermSourceLang(sourceLanguage); + setNewTermTargetLang(targetLanguage); + setStatus("Added glossary term."); + }; + + const removeGlossaryTerm = (id: string) => { + setGlossary((current) => current.filter((entry) => entry.id !== id)); + setStatus("Removed glossary term."); + }; + + const copySourceToTarget = (path: string) => { + const sourceValue = + workspace.localeMap[effectiveSourceLanguage]?.[path] ?? ""; + setCell(effectiveTargetLanguage, path, sourceValue); + }; + + const replaceTarget = (path: string, value: string) => { + setCell(effectiveTargetLanguage, path, value); + }; + + const exportOutput = () => { + const payload = serializeWorkspace(workspace); + const fileName = sourceFileName.replace(/\.[^.]+$/u, "") || "translations"; + triggerBlobDownload( + new Blob([payload + "\n"], { type: "application/json" }), + `${fileName}.reviewed.json`, + ); + setStatus("Downloaded reviewed translation JSON."); + }; + + const sendOutput = async () => { + if (Object.keys(workspace.localeMap).length === 0) { + setStatus("No translations loaded."); + return; + } + const payload = serializeWorkspace(workspace); + try { + const url = await prepareTranslatorPortalHandoff( + new Blob([payload], { type: "application/json" }), + `${sourceFileName.replace(/\.[^.]+$/u, "").trim() || "translations"}.reviewed.json`, + ); + window.location.assign(url); + } catch (reason) { + setStatus( + reason instanceof Error ? reason.message : "Sending output failed.", + ); + } + }; + + const glossaryRows = useMemo(() => { + const sourceLanguage = effectiveSourceLanguage; + const targetLanguage = effectiveTargetLanguage; + return rows.map((row) => ({ + ...row, + suggestions: pickSuggestions( + row.source, + sourceLanguage, + targetLanguage, + glossary, + ), + })); + }, [effectiveSourceLanguage, effectiveTargetLanguage, glossary, rows]); + return (
-

Exact local text transformations

-

Text Tools

+

Local translation review

+

Translator Tools

- Build an ordered, visible transformation pipeline for normalization, - lines, casing, strict escaping/decoding, wrapping, and quoted or - literal-delimited columns. + Open i18next-like files, inspect source and target language strings, + add glossary-based suggestions and export reviewed JSON in-browser.

- Browser-local + Browser-local only
-
-
-
-
-

Source

-

Exact input

-
- -
-
- - -
-