diff --git a/.github/workflows/translate-i18n-base-on-english.yml b/.github/workflows/translate-i18n-base-on-english.yml
deleted file mode 100644
index 16d36361fd..0000000000
--- a/.github/workflows/translate-i18n-base-on-english.yml
+++ /dev/null
@@ -1,94 +0,0 @@
-name: Translate i18n Files Based on English
-
-on:
- push:
- branches: [main]
- paths:
- - 'web/i18n/en-US/*.json'
- workflow_dispatch:
-
-permissions:
- contents: write
- pull-requests: write
-
-jobs:
- check-and-update:
- if: github.repository == 'langgenius/dify'
- runs-on: ubuntu-latest
- defaults:
- run:
- working-directory: web
- steps:
- # Keep use old checkout action version for https://github.com/peter-evans/create-pull-request/issues/4272
- - uses: actions/checkout@v4
- with:
- fetch-depth: 0
- token: ${{ secrets.GITHUB_TOKEN }}
-
- - name: Check for file changes in i18n/en-US
- id: check_files
- run: |
- # Skip check for manual trigger, translate all files
- if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
- echo "FILES_CHANGED=true" >> $GITHUB_ENV
- echo "FILE_ARGS=" >> $GITHUB_ENV
- echo "Manual trigger: translating all files"
- else
- git fetch origin "${{ github.event.before }}" || true
- git fetch origin "${{ github.sha }}" || true
- changed_files=$(git diff --name-only "${{ github.event.before }}" "${{ github.sha }}" -- 'i18n/en-US/*.json')
- echo "Changed files: $changed_files"
- if [ -n "$changed_files" ]; then
- echo "FILES_CHANGED=true" >> $GITHUB_ENV
- file_args=""
- for file in $changed_files; do
- filename=$(basename "$file" .json)
- file_args="$file_args --file $filename"
- done
- echo "FILE_ARGS=$file_args" >> $GITHUB_ENV
- echo "File arguments: $file_args"
- else
- echo "FILES_CHANGED=false" >> $GITHUB_ENV
- fi
- fi
-
- - name: Install pnpm
- uses: pnpm/action-setup@v4
- with:
- package_json_file: web/package.json
- run_install: false
-
- - name: Set up Node.js
- if: env.FILES_CHANGED == 'true'
- uses: actions/setup-node@v6
- with:
- node-version: 'lts/*'
- cache: pnpm
- cache-dependency-path: ./web/pnpm-lock.yaml
-
- - name: Install dependencies
- if: env.FILES_CHANGED == 'true'
- working-directory: ./web
- run: pnpm install --frozen-lockfile
-
- - name: Generate i18n translations
- if: env.FILES_CHANGED == 'true'
- working-directory: ./web
- run: pnpm run i18n:gen ${{ env.FILE_ARGS }}
-
- - name: Create Pull Request
- if: env.FILES_CHANGED == 'true'
- uses: peter-evans/create-pull-request@v6
- with:
- token: ${{ secrets.GITHUB_TOKEN }}
- commit-message: 'chore(i18n): update translations based on en-US changes'
- title: 'chore(i18n): translate i18n files based on en-US changes'
- body: |
- This PR was automatically created to update i18n translation files based on changes in en-US locale.
-
- **Triggered by:** ${{ github.sha }}
-
- **Changes included:**
- - Updated translation files for all locales
- branch: chore/automated-i18n-updates-${{ github.sha }}
- delete-branch: true
diff --git a/.github/workflows/translate-i18n-claude.yml b/.github/workflows/translate-i18n-claude.yml
new file mode 100644
index 0000000000..8dccf8ef93
--- /dev/null
+++ b/.github/workflows/translate-i18n-claude.yml
@@ -0,0 +1,395 @@
+name: Translate i18n Files with Claude Code
+
+on:
+ push:
+ branches: [main]
+ paths:
+ - 'web/i18n/en-US/*.json'
+ workflow_dispatch:
+ inputs:
+ files:
+ description: 'Specific files to translate (space-separated, e.g., "app common"). Leave empty for all files.'
+ required: false
+ type: string
+ languages:
+ description: 'Specific languages to translate (space-separated, e.g., "zh-Hans ja-JP"). Leave empty for all supported languages.'
+ required: false
+ type: string
+ mode:
+ description: 'Sync mode: incremental (only changes) or full (re-check all keys)'
+ required: false
+ default: 'incremental'
+ type: choice
+ options:
+ - incremental
+ - full
+
+permissions:
+ contents: write
+ pull-requests: write
+
+jobs:
+ translate:
+ if: github.repository == 'langgenius/dify'
+ runs-on: ubuntu-latest
+ timeout-minutes: 60
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v6
+ with:
+ fetch-depth: 0
+ token: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Configure Git
+ run: |
+ git config --global user.name "github-actions[bot]"
+ git config --global user.email "github-actions[bot]@users.noreply.github.com"
+
+ - name: Install pnpm
+ uses: pnpm/action-setup@v4
+ with:
+ package_json_file: web/package.json
+ run_install: false
+
+ - name: Set up Node.js
+ uses: actions/setup-node@v6
+ with:
+ node-version: 'lts/*'
+ cache: pnpm
+ cache-dependency-path: ./web/pnpm-lock.yaml
+
+ - name: Detect changed files and generate diff
+ id: detect_changes
+ run: |
+ if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
+ # Manual trigger
+ if [ -n "${{ github.event.inputs.files }}" ]; then
+ echo "CHANGED_FILES=${{ github.event.inputs.files }}" >> $GITHUB_OUTPUT
+ else
+ # Get all JSON files in en-US directory
+ files=$(ls web/i18n/en-US/*.json 2>/dev/null | xargs -n1 basename | sed 's/.json$//' | tr '\n' ' ')
+ echo "CHANGED_FILES=$files" >> $GITHUB_OUTPUT
+ fi
+ echo "TARGET_LANGS=${{ github.event.inputs.languages }}" >> $GITHUB_OUTPUT
+ echo "SYNC_MODE=${{ github.event.inputs.mode || 'incremental' }}" >> $GITHUB_OUTPUT
+
+ # For manual trigger with incremental mode, get diff from last commit
+ # For full mode, we'll do a complete check anyway
+ if [ "${{ github.event.inputs.mode }}" == "full" ]; then
+ echo "Full mode: will check all keys" > /tmp/i18n-diff.txt
+ echo "DIFF_AVAILABLE=false" >> $GITHUB_OUTPUT
+ else
+ git diff HEAD~1..HEAD -- 'web/i18n/en-US/*.json' > /tmp/i18n-diff.txt 2>/dev/null || echo "" > /tmp/i18n-diff.txt
+ if [ -s /tmp/i18n-diff.txt ]; then
+ echo "DIFF_AVAILABLE=true" >> $GITHUB_OUTPUT
+ else
+ echo "DIFF_AVAILABLE=false" >> $GITHUB_OUTPUT
+ fi
+ fi
+ else
+ # Push trigger - detect changed files from the push
+ BEFORE_SHA="${{ github.event.before }}"
+ # Handle edge case: first push or force push may have null/zero SHA
+ if [ -z "$BEFORE_SHA" ] || [ "$BEFORE_SHA" = "0000000000000000000000000000000000000000" ]; then
+ # Fallback to comparing with parent commit
+ BEFORE_SHA="HEAD~1"
+ fi
+ changed=$(git diff --name-only "$BEFORE_SHA" ${{ github.sha }} -- 'web/i18n/en-US/*.json' 2>/dev/null | xargs -n1 basename 2>/dev/null | sed 's/.json$//' | tr '\n' ' ' || echo "")
+ echo "CHANGED_FILES=$changed" >> $GITHUB_OUTPUT
+ echo "TARGET_LANGS=" >> $GITHUB_OUTPUT
+ echo "SYNC_MODE=incremental" >> $GITHUB_OUTPUT
+
+ # Generate detailed diff for the push
+ git diff "$BEFORE_SHA"..${{ github.sha }} -- 'web/i18n/en-US/*.json' > /tmp/i18n-diff.txt 2>/dev/null || echo "" > /tmp/i18n-diff.txt
+ if [ -s /tmp/i18n-diff.txt ]; then
+ echo "DIFF_AVAILABLE=true" >> $GITHUB_OUTPUT
+ else
+ echo "DIFF_AVAILABLE=false" >> $GITHUB_OUTPUT
+ fi
+ fi
+
+ # Truncate diff if too large (keep first 50KB)
+ if [ -f /tmp/i18n-diff.txt ]; then
+ head -c 50000 /tmp/i18n-diff.txt > /tmp/i18n-diff-truncated.txt
+ mv /tmp/i18n-diff-truncated.txt /tmp/i18n-diff.txt
+ fi
+
+ echo "Detected files: $(cat $GITHUB_OUTPUT | grep CHANGED_FILES || echo 'none')"
+
+ - name: Run Claude Code for Translation Sync
+ if: steps.detect_changes.outputs.CHANGED_FILES != ''
+ uses: anthropics/claude-code-action@v1
+ with:
+ anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ prompt: |
+ You are a professional i18n synchronization engineer for the Dify project.
+ Your task is to keep all language translations in sync with the English source (en-US).
+
+ ## CRITICAL TOOL RESTRICTIONS
+ - Use **Read** tool to read files (NOT cat or bash)
+ - Use **Edit** tool to modify JSON files (NOT node, jq, or bash scripts)
+ - Use **Bash** ONLY for: git commands, gh commands, pnpm commands
+ - Run bash commands ONE BY ONE, never combine with && or ||
+
+ ## EFFICIENCY RULES
+ - **ONE Edit per language file** - batch all key additions into a single Edit
+ - Insert new keys at the beginning of JSON (after `{`), lint:fix will sort them
+ - Translate ALL keys for a language mentally first, then do ONE Edit
+
+ ## Context
+ - Changed/target files: ${{ steps.detect_changes.outputs.CHANGED_FILES }}
+ - Target languages (empty means all supported): ${{ steps.detect_changes.outputs.TARGET_LANGS }}
+ - Sync mode: ${{ steps.detect_changes.outputs.SYNC_MODE }}
+ - Translation files are located in: web/i18n/{locale}/{filename}.json
+ - Language configuration is in: web/i18n-config/languages.ts
+ - Git diff is available: ${{ steps.detect_changes.outputs.DIFF_AVAILABLE }}
+
+ ## CRITICAL DESIGN: Verify First, Then Sync
+
+ You MUST follow this three-phase approach:
+
+ ═══════════════════════════════════════════════════════════════
+ ║ PHASE 1: VERIFY - Analyze and Generate Change Report ║
+ ═══════════════════════════════════════════════════════════════
+
+ ### Step 1.1: Analyze Git Diff (for incremental mode)
+ Use the Read tool to read `/tmp/i18n-diff.txt` to see the git diff.
+
+ Parse the diff to categorize changes:
+ - Lines with `+` (not `+++`): Added or modified values
+ - Lines with `-` (not `---`): Removed or old values
+ - Identify specific keys for each category:
+ * ADD: Keys that appear only in `+` lines (new keys)
+ * UPDATE: Keys that appear in both `-` and `+` lines (value changed)
+ * DELETE: Keys that appear only in `-` lines (removed keys)
+
+ ### Step 1.2: Read Language Configuration
+ Use the Read tool to read `web/i18n-config/languages.ts`.
+ Extract all languages with `supported: true`.
+
+ ### Step 1.3: Run i18n:check for Each Language
+ ```bash
+ pnpm --dir web install --frozen-lockfile
+ pnpm --dir web run i18n:check
+ ```
+
+ This will report:
+ - Missing keys (need to ADD)
+ - Extra keys (need to DELETE)
+
+ ### Step 1.4: Generate Change Report
+
+ Create a structured report identifying:
+ ```
+ ╔══════════════════════════════════════════════════════════════╗
+ ║ I18N SYNC CHANGE REPORT ║
+ ╠══════════════════════════════════════════════════════════════╣
+ ║ Files to process: [list] ║
+ ║ Languages to sync: [list] ║
+ ╠══════════════════════════════════════════════════════════════╣
+ ║ ADD (New Keys): ║
+ ║ - [filename].[key]: "English value" ║
+ ║ ... ║
+ ╠══════════════════════════════════════════════════════════════╣
+ ║ UPDATE (Modified Keys - MUST re-translate): ║
+ ║ - [filename].[key]: "Old value" → "New value" ║
+ ║ ... ║
+ ╠══════════════════════════════════════════════════════════════╣
+ ║ DELETE (Extra Keys): ║
+ ║ - [language]/[filename].[key] ║
+ ║ ... ║
+ ╚══════════════════════════════════════════════════════════════╝
+ ```
+
+ **IMPORTANT**: For UPDATE detection, compare git diff to find keys where
+ the English value changed. These MUST be re-translated even if target
+ language already has a translation (it's now stale!).
+
+ ═══════════════════════════════════════════════════════════════
+ ║ PHASE 2: SYNC - Execute Changes Based on Report ║
+ ═══════════════════════════════════════════════════════════════
+
+ ### Step 2.1: Process ADD Operations (BATCH per language file)
+
+ **CRITICAL WORKFLOW for efficiency:**
+ 1. First, translate ALL new keys for ALL languages mentally
+ 2. Then, for EACH language file, do ONE Edit operation:
+ - Read the file once
+ - Insert ALL new keys at the beginning (right after the opening `{`)
+ - Don't worry about alphabetical order - lint:fix will sort them later
+
+ Example Edit (adding 3 keys to zh-Hans/app.json):
+ ```
+ old_string: '{\n "accessControl"'
+ new_string: '{\n "newKey1": "translation1",\n "newKey2": "translation2",\n "newKey3": "translation3",\n "accessControl"'
+ ```
+
+ **IMPORTANT**:
+ - ONE Edit per language file (not one Edit per key!)
+ - Always use the Edit tool. NEVER use bash scripts, node, or jq.
+
+ ### Step 2.2: Process UPDATE Operations
+
+ **IMPORTANT: Special handling for zh-Hans and ja-JP**
+ If zh-Hans or ja-JP files were ALSO modified in the same push:
+ - Run: `git diff HEAD~1 --name-only | grep -E "(zh-Hans|ja-JP)"`
+ - If found, it means someone manually translated them. Apply these rules:
+
+ 1. **Missing keys**: Still ADD them (completeness required)
+ 2. **Existing translations**: Compare with the NEW English value:
+ - If translation is **completely wrong** or **unrelated** → Update it
+ - If translation is **roughly correct** (captures the meaning) → Keep it, respect manual work
+ - When in doubt, **keep the manual translation**
+
+ Example:
+ - English changed: "Save" → "Save Changes"
+ - Manual translation: "保存更改" → Keep it (correct meaning)
+ - Manual translation: "删除" → Update it (completely wrong)
+
+ For other languages:
+ Use Edit tool to replace the old value with the new translation.
+ You can batch multiple updates in one Edit if they are adjacent.
+
+ ### Step 2.3: Process DELETE Operations
+ For extra keys reported by i18n:check:
+ - Run: `pnpm --dir web run i18n:check --auto-remove`
+ - Or manually remove from target language JSON files
+
+ ## Translation Guidelines
+
+ - PRESERVE all placeholders exactly as-is:
+ - `{{variable}}` - Mustache interpolation
+ - `${variable}` - Template literal
+ - `content` - HTML tags
+ - `_one`, `_other` - Pluralization suffixes (these are KEY suffixes, not values)
+ - Use appropriate language register (formal/informal) based on existing translations
+ - Match existing translation style in each language
+ - Technical terms: check existing conventions per language
+ - For CJK languages: no spaces between characters unless necessary
+ - For RTL languages (ar-TN, fa-IR): ensure proper text handling
+
+ ## Output Format Requirements
+ - Alphabetical key ordering (if original file uses it)
+ - 2-space indentation
+ - Trailing newline at end of file
+ - Valid JSON (use proper escaping for special characters)
+
+ ═══════════════════════════════════════════════════════════════
+ ║ PHASE 3: RE-VERIFY - Confirm All Issues Resolved ║
+ ═══════════════════════════════════════════════════════════════
+
+ ### Step 3.1: Run Lint Fix (IMPORTANT!)
+ ```bash
+ pnpm --dir web lint:fix --quiet -- 'i18n/**/*.json'
+ ```
+ This ensures:
+ - JSON keys are sorted alphabetically (jsonc/sort-keys rule)
+ - Valid i18n keys (dify-i18n/valid-i18n-keys rule)
+ - No extra keys (dify-i18n/no-extra-keys rule)
+
+ ### Step 3.2: Run Final i18n Check
+ ```bash
+ pnpm --dir web run i18n:check
+ ```
+
+ ### Step 3.3: Fix Any Remaining Issues
+ If check reports issues:
+ - Go back to PHASE 2 for unresolved items
+ - Repeat until check passes
+
+ ### Step 3.4: Generate Final Summary
+ ```
+ ╔══════════════════════════════════════════════════════════════╗
+ ║ SYNC COMPLETED SUMMARY ║
+ ╠══════════════════════════════════════════════════════════════╣
+ ║ Language │ Added │ Updated │ Deleted │ Status ║
+ ╠══════════════════════════════════════════════════════════════╣
+ ║ zh-Hans │ 5 │ 2 │ 1 │ ✓ Complete ║
+ ║ ja-JP │ 5 │ 2 │ 1 │ ✓ Complete ║
+ ║ ... │ ... │ ... │ ... │ ... ║
+ ╠══════════════════════════════════════════════════════════════╣
+ ║ i18n:check │ PASSED - All keys in sync ║
+ ╚══════════════════════════════════════════════════════════════╝
+ ```
+
+ ## Mode-Specific Behavior
+
+ **SYNC_MODE = "incremental"** (default):
+ - Focus on keys identified from git diff
+ - Also check i18n:check output for any missing/extra keys
+ - Efficient for small changes
+
+ **SYNC_MODE = "full"**:
+ - Compare ALL keys between en-US and each language
+ - Run i18n:check to identify all discrepancies
+ - Use for first-time sync or fixing historical issues
+
+ ## Important Notes
+
+ 1. Always run i18n:check BEFORE and AFTER making changes
+ 2. The check script is the source of truth for missing/extra keys
+ 3. For UPDATE scenario: git diff is the source of truth for changed values
+ 4. Create a single commit with all translation changes
+ 5. If any translation fails, continue with others and report failures
+
+ ═══════════════════════════════════════════════════════════════
+ ║ PHASE 4: COMMIT AND CREATE PR ║
+ ═══════════════════════════════════════════════════════════════
+
+ After all translations are complete and verified:
+
+ ### Step 4.1: Check for changes
+ ```bash
+ git status --porcelain
+ ```
+
+ If there are changes:
+
+ ### Step 4.2: Create a new branch and commit
+ Run these git commands ONE BY ONE (not combined with &&):
+
+ 1. Create branch:
+ ```bash
+ git checkout -b "chore/i18n-sync-$(date +%Y%m%d-%H%M%S)"
+ ```
+
+ 2. Stage changes:
+ ```bash
+ git add web/i18n/
+ ```
+
+ 3. Commit (use heredoc for multi-line message):
+ ```bash
+ git commit -m "chore(i18n): sync translations with en-US - Mode: ${{ steps.detect_changes.outputs.SYNC_MODE }}"
+ ```
+
+ 4. Push:
+ ```bash
+ git push origin HEAD
+ ```
+
+ ### Step 4.3: Create Pull Request
+ ```bash
+ gh pr create \
+ --title "chore(i18n): sync translations with en-US" \
+ --body "## Summary
+
+ This PR was automatically generated to sync i18n translation files.
+
+ ### Changes
+ - Mode: ${{ steps.detect_changes.outputs.SYNC_MODE }}
+ - Files processed: ${{ steps.detect_changes.outputs.CHANGED_FILES }}
+
+ ### Verification
+ - [x] \`i18n:check\` passed
+ - [x] \`lint:fix\` applied
+
+ 🤖 Generated with Claude Code GitHub Action" \
+ --base main
+ ```
+
+ claude_args: |
+ --max-turns 150
+ --allowedTools "Read,Write,Edit,Bash(git *),Bash(git:*),Bash(gh *),Bash(gh:*),Bash(pnpm *),Bash(pnpm:*),Glob,Grep"
diff --git a/web/i18n-config/README.md b/web/i18n-config/README.md
index 96c7157114..c90904459c 100644
--- a/web/i18n-config/README.md
+++ b/web/i18n-config/README.md
@@ -158,10 +158,32 @@ We have a list of languages that we support in the `languages.ts` file. But some
## Utility scripts
-- Auto-fill translations: `pnpm run i18n:gen --file app common --lang zh-Hans ja-JP [--dry-run]`
- - Use space-separated values; repeat `--file` / `--lang` as needed. Defaults to all en-US files and all supported locales except en-US.
- - Protects placeholders (`{{var}}`, `${var}`, ``) before translation and restores them after.
- Check missing/extra keys: `pnpm run i18n:check --file app billing --lang zh-Hans [--auto-remove]`
- Use space-separated values; repeat `--file` / `--lang` as needed. Returns non-zero on missing/extra keys; `--auto-remove` deletes extra keys automatically.
-Workflows: `.github/workflows/translate-i18n-base-on-english.yml` auto-runs the translation generator on `web/i18n/en-US/*.json` changes to main. `i18n:check` is a manual script (not run in CI).
+## Automatic Translation
+
+Translation is handled automatically by Claude Code GitHub Actions. When changes are pushed to `web/i18n/en-US/*.json` on the main branch:
+
+1. Claude Code analyzes the git diff to detect changes
+1. Identifies three types of changes:
+ - **ADD**: New keys that need translation
+ - **UPDATE**: Modified keys that need re-translation (even if target language has existing translation)
+ - **DELETE**: Removed keys that need to be deleted from other languages
+1. Runs `i18n:check` to verify the initial sync status.
+1. Translates missing/updated keys while preserving placeholders (`{{var}}`, `${var}`, ``) and removes deleted keys.
+1. Runs `lint:fix` to sort JSON keys and `i18n:check` again to ensure everything is synchronized.
+1. Creates a PR with the translations.
+
+### Manual Trigger
+
+To manually trigger translation:
+
+1. Go to Actions > "Translate i18n Files with Claude Code"
+1. Click "Run workflow"
+1. Optionally configure:
+ - **files**: Specific files to translate (space-separated, e.g., "app common")
+ - **languages**: Specific languages to translate (space-separated, e.g., "zh-Hans ja-JP")
+ - **mode**: `incremental` (default, only changes) or `full` (check all keys)
+
+Workflow: `.github/workflows/translate-i18n-claude.yml`
diff --git a/web/package.json b/web/package.json
index e94f2fd441..dcebe742a7 100644
--- a/web/package.json
+++ b/web/package.json
@@ -40,7 +40,6 @@
"gen-icons": "node ./scripts/gen-icons.mjs && eslint --fix app/components/base/icons/src/",
"uglify-embed": "node ./bin/uglify-embed",
"i18n:check": "tsx ./scripts/check-i18n.js",
- "i18n:gen": "tsx ./scripts/auto-gen-i18n.js",
"test": "vitest run",
"test:coverage": "vitest run --coverage",
"test:watch": "vitest --watch",
@@ -196,7 +195,6 @@
"@vitest/coverage-v8": "4.0.16",
"autoprefixer": "^10.4.21",
"babel-loader": "^10.0.0",
- "bing-translate-api": "^4.1.0",
"code-inspector-plugin": "1.2.9",
"cross-env": "^10.1.0",
"eslint": "^9.39.2",
diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml
index a2e95dadd7..5ad6d0481b 100644
--- a/web/pnpm-lock.yaml
+++ b/web/pnpm-lock.yaml
@@ -481,9 +481,6 @@ importers:
babel-loader:
specifier: ^10.0.0
version: 10.0.0(@babel/core@7.28.5)(webpack@5.103.0(esbuild@0.25.0)(uglify-js@3.19.3))
- bing-translate-api:
- specifier: ^4.1.0
- version: 4.2.0
code-inspector-plugin:
specifier: 1.2.9
version: 1.2.9
@@ -3160,10 +3157,6 @@ packages:
resolution: {integrity: sha512-TeheYy0ILzBEI/CO55CP6zJCSdSWeRtGnHy8U8dWSUH4I68iqTsy7HkMktR4xakThc9jotkPQUXT4ITdbV7cHA==}
engines: {node: '>=18'}
- '@sindresorhus/is@4.6.0':
- resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==}
- engines: {node: '>=10'}
-
'@solid-primitives/event-listener@2.4.3':
resolution: {integrity: sha512-h4VqkYFv6Gf+L7SQj+Y6puigL/5DIi7x5q07VZET7AWcS+9/G3WfIE9WheniHWJs51OEkRB43w6lDys5YeFceg==}
peerDependencies:
@@ -3324,10 +3317,6 @@ packages:
'@swc/helpers@0.5.17':
resolution: {integrity: sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==}
- '@szmarczak/http-timer@4.0.6':
- resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==}
- engines: {node: '>=10'}
-
'@tailwindcss/typography@0.5.19':
resolution: {integrity: sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==}
peerDependencies:
@@ -3511,9 +3500,6 @@ packages:
'@types/babel__traverse@7.28.0':
resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==}
- '@types/cacheable-request@6.0.3':
- resolution: {integrity: sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==}
-
'@types/chai@5.2.3':
resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==}
@@ -3652,9 +3638,6 @@ packages:
'@types/html-minifier-terser@6.1.0':
resolution: {integrity: sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==}
- '@types/http-cache-semantics@4.0.4':
- resolution: {integrity: sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==}
-
'@types/js-cookie@3.0.6':
resolution: {integrity: sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==}
@@ -3667,9 +3650,6 @@ packages:
'@types/katex@0.16.7':
resolution: {integrity: sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==}
- '@types/keyv@3.1.4':
- resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==}
-
'@types/mdast@4.0.4':
resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==}
@@ -3732,9 +3712,6 @@ packages:
'@types/resolve@1.20.6':
resolution: {integrity: sha512-A4STmOXPhMUtHH+S6ymgE2GiBSMqf4oTvcQZMcHzokuTLVYzXTB8ttjcgxOVaAp2lGwEdzZ0J+cRbbeevQj1UQ==}
- '@types/responselike@1.0.3':
- resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==}
-
'@types/semver@7.7.1':
resolution: {integrity: sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==}
@@ -4338,9 +4315,6 @@ packages:
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
engines: {node: '>=8'}
- bing-translate-api@4.2.0:
- resolution: {integrity: sha512-7a9yo1NbGcHPS8zXTdz8tCOymHZp2pvCuYOChCaXKjOX8EIwdV3SLd4D7RGIqZt1UhffypYBUcAV2gDcTgK0rA==}
-
bippy@0.3.34:
resolution: {integrity: sha512-vmptmU/20UdIWHHhq7qCSHhHzK7Ro3YJ1utU0fBG7ujUc58LEfTtilKxcF0IOgSjT5XLcm7CBzDjbv4lcKApGQ==}
peerDependencies:
@@ -4427,14 +4401,6 @@ packages:
resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
engines: {node: '>=8'}
- cacheable-lookup@5.0.4:
- resolution: {integrity: sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==}
- engines: {node: '>=10.6.0'}
-
- cacheable-request@7.0.4:
- resolution: {integrity: sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==}
- engines: {node: '>=8'}
-
callsites@3.1.0:
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
engines: {node: '>=6'}
@@ -4588,9 +4554,6 @@ packages:
client-only@0.0.1:
resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
- clone-response@1.0.3:
- resolution: {integrity: sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==}
-
clsx@1.2.1:
resolution: {integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==}
engines: {node: '>=6'}
@@ -4996,10 +4959,6 @@ packages:
resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==}
engines: {node: '>=0.10.0'}
- defer-to-connect@2.0.1:
- resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==}
- engines: {node: '>=10'}
-
define-lazy-prop@2.0.0:
resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==}
engines: {node: '>=8'}
@@ -5712,10 +5671,6 @@ packages:
get-own-enumerable-property-symbols@3.0.2:
resolution: {integrity: sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==}
- get-stream@5.2.0:
- resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==}
- engines: {node: '>=8'}
-
get-stream@8.0.1:
resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==}
engines: {node: '>=16'}
@@ -5772,10 +5727,6 @@ packages:
peerDependencies:
csstype: ^3.0.10
- got@11.8.6:
- resolution: {integrity: sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==}
- engines: {node: '>=10.19.0'}
-
graceful-fs@4.2.11:
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
@@ -5920,17 +5871,10 @@ packages:
htmlparser2@6.1.0:
resolution: {integrity: sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==}
- http-cache-semantics@4.2.0:
- resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==}
-
http-proxy-agent@7.0.2:
resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==}
engines: {node: '>= 14'}
- http2-wrapper@1.0.3:
- resolution: {integrity: sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==}
- engines: {node: '>=10.19.0'}
-
https-browserify@1.0.0:
resolution: {integrity: sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg==}
@@ -6453,10 +6397,6 @@ packages:
lower-case@2.0.2:
resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==}
- lowercase-keys@2.0.0:
- resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==}
- engines: {node: '>=8'}
-
lowlight@1.20.0:
resolution: {integrity: sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==}
@@ -6729,10 +6669,6 @@ packages:
resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==}
engines: {node: '>=18'}
- mimic-response@1.0.1:
- resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==}
- engines: {node: '>=4'}
-
mimic-response@3.1.0:
resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==}
engines: {node: '>=10'}
@@ -6880,10 +6816,6 @@ packages:
resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==}
engines: {node: '>=0.10.0'}
- normalize-url@6.1.0:
- resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==}
- engines: {node: '>=10'}
-
normalize-wheel@1.0.1:
resolution: {integrity: sha512-1OnlAPZ3zgrk8B91HyRj+eVv+kS5u+Z0SCsak6Xil/kmgEia50ga7zfkumayonZrImffAxPU/5WcyGhzetHNPA==}
@@ -6964,10 +6896,6 @@ packages:
oxc-resolver@11.15.0:
resolution: {integrity: sha512-Hk2J8QMYwmIO9XTCUiOH00+Xk2/+aBxRUnhrSlANDyCnLYc32R1WSIq1sU2yEdlqd53FfMpPEpnBYIKQMzliJw==}
- p-cancelable@2.1.1:
- resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==}
- engines: {node: '>=8'}
-
p-limit@2.3.0:
resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==}
engines: {node: '>=6'}
@@ -7359,10 +7287,6 @@ packages:
queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
- quick-lru@5.1.1:
- resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==}
- engines: {node: '>=10'}
-
randombytes@2.1.0:
resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==}
@@ -7705,9 +7629,6 @@ packages:
resize-observer-polyfill@1.5.1:
resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==}
- resolve-alpn@1.2.1:
- resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==}
-
resolve-from@4.0.0:
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
engines: {node: '>=4'}
@@ -7724,9 +7645,6 @@ packages:
engines: {node: '>= 0.4'}
hasBin: true
- responselike@2.0.1:
- resolution: {integrity: sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==}
-
restore-cursor@5.1.0:
resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==}
engines: {node: '>=18'}
@@ -11717,8 +11635,6 @@ snapshots:
'@sindresorhus/base62@1.0.0': {}
- '@sindresorhus/is@4.6.0': {}
-
'@solid-primitives/event-listener@2.4.3(solid-js@1.9.10)':
dependencies:
'@solid-primitives/utils': 6.3.2(solid-js@1.9.10)
@@ -11971,10 +11887,6 @@ snapshots:
dependencies:
tslib: 2.8.1
- '@szmarczak/http-timer@4.0.6':
- dependencies:
- defer-to-connect: 2.0.1
-
'@tailwindcss/typography@0.5.19(tailwindcss@3.4.18(tsx@4.21.0)(yaml@2.8.2))':
dependencies:
postcss-selector-parser: 6.0.10
@@ -12199,13 +12111,6 @@ snapshots:
dependencies:
'@babel/types': 7.28.5
- '@types/cacheable-request@6.0.3':
- dependencies:
- '@types/http-cache-semantics': 4.0.4
- '@types/keyv': 3.1.4
- '@types/node': 18.15.0
- '@types/responselike': 1.0.3
-
'@types/chai@5.2.3':
dependencies:
'@types/deep-eql': 4.0.2
@@ -12373,8 +12278,6 @@ snapshots:
'@types/html-minifier-terser@6.1.0': {}
- '@types/http-cache-semantics@4.0.4': {}
-
'@types/js-cookie@3.0.6': {}
'@types/js-yaml@4.0.9': {}
@@ -12383,10 +12286,6 @@ snapshots:
'@types/katex@0.16.7': {}
- '@types/keyv@3.1.4':
- dependencies:
- '@types/node': 18.15.0
-
'@types/mdast@4.0.4':
dependencies:
'@types/unist': 3.0.3
@@ -12450,10 +12349,6 @@ snapshots:
'@types/resolve@1.20.6': {}
- '@types/responselike@1.0.3':
- dependencies:
- '@types/node': 18.15.0
-
'@types/semver@7.7.1': {}
'@types/sortablejs@1.15.9': {}
@@ -13160,10 +13055,6 @@ snapshots:
binary-extensions@2.3.0: {}
- bing-translate-api@4.2.0:
- dependencies:
- got: 11.8.6
-
bippy@0.3.34(@types/react@19.2.7)(react@19.2.3):
dependencies:
'@types/react-reconciler': 0.28.9(@types/react@19.2.7)
@@ -13273,18 +13164,6 @@ snapshots:
cac@6.7.14: {}
- cacheable-lookup@5.0.4: {}
-
- cacheable-request@7.0.4:
- dependencies:
- clone-response: 1.0.3
- get-stream: 5.2.0
- http-cache-semantics: 4.2.0
- keyv: 4.5.4
- lowercase-keys: 2.0.0
- normalize-url: 6.1.0
- responselike: 2.0.1
-
callsites@3.1.0: {}
camel-case@4.1.2:
@@ -13425,10 +13304,6 @@ snapshots:
client-only@0.0.1: {}
- clone-response@1.0.3:
- dependencies:
- mimic-response: 1.0.1
-
clsx@1.2.1: {}
clsx@2.1.1: {}
@@ -13859,6 +13734,7 @@ snapshots:
decompress-response@6.0.0:
dependencies:
mimic-response: 3.1.0
+ optional: true
dedent@0.7.0: {}
@@ -13871,8 +13747,6 @@ snapshots:
deepmerge@4.3.1: {}
- defer-to-connect@2.0.1: {}
-
define-lazy-prop@2.0.0: {}
del@4.1.1:
@@ -14013,6 +13887,7 @@ snapshots:
end-of-stream@1.4.5:
dependencies:
once: 1.4.0
+ optional: true
endent@2.1.0:
dependencies:
@@ -14799,10 +14674,6 @@ snapshots:
get-own-enumerable-property-symbols@3.0.2: {}
- get-stream@5.2.0:
- dependencies:
- pump: 3.0.3
-
get-stream@8.0.1: {}
get-tsconfig@4.13.0:
@@ -14862,20 +14733,6 @@ snapshots:
dependencies:
csstype: 3.2.3
- got@11.8.6:
- dependencies:
- '@sindresorhus/is': 4.6.0
- '@szmarczak/http-timer': 4.0.6
- '@types/cacheable-request': 6.0.3
- '@types/responselike': 1.0.3
- cacheable-lookup: 5.0.4
- cacheable-request: 7.0.4
- decompress-response: 6.0.0
- http2-wrapper: 1.0.3
- lowercase-keys: 2.0.0
- p-cancelable: 2.1.1
- responselike: 2.0.1
-
graceful-fs@4.2.11: {}
graphemer@1.4.0: {}
@@ -15120,8 +14977,6 @@ snapshots:
domutils: 2.8.0
entities: 2.2.0
- http-cache-semantics@4.2.0: {}
-
http-proxy-agent@7.0.2:
dependencies:
agent-base: 7.1.4
@@ -15129,11 +14984,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
- http2-wrapper@1.0.3:
- dependencies:
- quick-lru: 5.1.1
- resolve-alpn: 1.2.1
-
https-browserify@1.0.0: {}
https-proxy-agent@7.0.6:
@@ -15604,8 +15454,6 @@ snapshots:
dependencies:
tslib: 2.8.1
- lowercase-keys@2.0.0: {}
-
lowlight@1.20.0:
dependencies:
fault: 1.0.4
@@ -16186,9 +16034,8 @@ snapshots:
mimic-function@5.0.1: {}
- mimic-response@1.0.1: {}
-
- mimic-response@3.1.0: {}
+ mimic-response@3.1.0:
+ optional: true
min-indent@1.0.1: {}
@@ -16360,8 +16207,6 @@ snapshots:
normalize-range@0.1.2: {}
- normalize-url@6.1.0: {}
-
normalize-wheel@1.0.1: {}
npm-run-path@5.3.0:
@@ -16445,8 +16290,6 @@ snapshots:
'@oxc-resolver/binding-win32-ia32-msvc': 11.15.0
'@oxc-resolver/binding-win32-x64-msvc': 11.15.0
- p-cancelable@2.1.1: {}
-
p-limit@2.3.0:
dependencies:
p-try: 2.2.0
@@ -16809,6 +16652,7 @@ snapshots:
dependencies:
end-of-stream: 1.4.5
once: 1.4.0
+ optional: true
punycode@1.4.1: {}
@@ -16828,8 +16672,6 @@ snapshots:
queue-microtask@1.2.3: {}
- quick-lru@5.1.1: {}
-
randombytes@2.1.0:
dependencies:
safe-buffer: 5.2.1
@@ -17296,8 +17138,6 @@ snapshots:
resize-observer-polyfill@1.5.1: {}
- resolve-alpn@1.2.1: {}
-
resolve-from@4.0.0: {}
resolve-pkg-maps@1.0.0: {}
@@ -17316,10 +17156,6 @@ snapshots:
path-parse: 1.0.7
supports-preserve-symlinks-flag: 1.0.0
- responselike@2.0.1:
- dependencies:
- lowercase-keys: 2.0.0
-
restore-cursor@5.1.0:
dependencies:
onetime: 7.0.0
diff --git a/web/scripts/auto-gen-i18n.js b/web/scripts/auto-gen-i18n.js
deleted file mode 100644
index bd73a18ab8..0000000000
--- a/web/scripts/auto-gen-i18n.js
+++ /dev/null
@@ -1,336 +0,0 @@
-import fs from 'node:fs'
-import path from 'node:path'
-import { fileURLToPath } from 'node:url'
-import { translate } from 'bing-translate-api'
-import data from '../i18n-config/languages'
-
-const __filename = fileURLToPath(import.meta.url)
-const __dirname = path.dirname(__filename)
-
-const targetLanguage = 'en-US'
-const i18nFolder = '../i18n' // Path to i18n folder relative to this script
-// https://github.com/plainheart/bing-translate-api/blob/master/src/met/lang.json
-const languageKeyMap = data.languages.reduce((map, language) => {
- if (language.supported) {
- if (language.value === 'zh-Hans' || language.value === 'zh-Hant')
- map[language.value] = language.value
- else
- map[language.value] = language.value.split('-')[0]
- }
-
- return map
-}, {})
-
-const supportedLanguages = Object.keys(languageKeyMap)
-
-function parseArgs(argv) {
- const args = {
- files: [],
- languages: [],
- isDryRun: false,
- help: false,
- errors: [],
- }
-
- const collectValues = (startIndex) => {
- const values = []
- let cursor = startIndex + 1
- while (cursor < argv.length && !argv[cursor].startsWith('--')) {
- const value = argv[cursor].trim()
- if (value)
- values.push(value)
- cursor++
- }
- return { values, nextIndex: cursor - 1 }
- }
-
- const validateList = (values, flag) => {
- if (!values.length) {
- args.errors.push(`${flag} requires at least one value. Example: ${flag} app billing`)
- return false
- }
-
- const invalid = values.find(value => value.includes(','))
- if (invalid) {
- args.errors.push(`${flag} expects space-separated values. Example: ${flag} app billing`)
- return false
- }
-
- return true
- }
-
- for (let index = 2; index < argv.length; index++) {
- const arg = argv[index]
-
- if (arg === '--dry-run') {
- args.isDryRun = true
- continue
- }
-
- if (arg === '--help' || arg === '-h') {
- args.help = true
- break
- }
-
- if (arg.startsWith('--file=')) {
- args.errors.push('--file expects space-separated values. Example: --file app billing')
- continue
- }
-
- if (arg === '--file') {
- const { values, nextIndex } = collectValues(index)
- if (validateList(values, '--file'))
- args.files.push(...values)
- index = nextIndex
- continue
- }
-
- if (arg.startsWith('--lang=')) {
- args.errors.push('--lang expects space-separated values. Example: --lang zh-Hans ja-JP')
- continue
- }
-
- if (arg === '--lang') {
- const { values, nextIndex } = collectValues(index)
- if (validateList(values, '--lang'))
- args.languages.push(...values)
- index = nextIndex
- continue
- }
- }
-
- return args
-}
-
-function printHelp() {
- console.log(`Usage: pnpm run i18n:gen [options]
-
-Options:
- --file Process only specific files; provide space-separated names and repeat --file if needed
- --lang Process only specific locales; provide space-separated locales and repeat --lang if needed (default: all supported except en-US)
- --dry-run Preview changes without writing files
- -h, --help Show help
-
-Examples:
- pnpm run i18n:gen --file app common --lang zh-Hans ja-JP
- pnpm run i18n:gen --dry-run
-`)
-}
-
-function protectPlaceholders(text) {
- const placeholders = []
- let safeText = text
- const patterns = [
- /\{\{[^{}]+\}\}/g, // mustache
- /\$\{[^{}]+\}/g, // template expressions
- /<[^>]+>/g, // html-like tags
- ]
-
- patterns.forEach((pattern) => {
- safeText = safeText.replace(pattern, (match) => {
- const token = `__PH_${placeholders.length}__`
- placeholders.push({ token, value: match })
- return token
- })
- })
-
- return {
- safeText,
- restore(translated) {
- return placeholders.reduce((result, { token, value }) => result.replace(new RegExp(token, 'g'), value), translated)
- },
- }
-}
-
-async function translateText(source, toLanguage) {
- if (typeof source !== 'string')
- return { value: source, skipped: false }
-
- const trimmed = source.trim()
- if (!trimmed)
- return { value: source, skipped: false }
-
- const { safeText, restore } = protectPlaceholders(source)
-
- try {
- const { translation } = await translate(safeText, null, languageKeyMap[toLanguage])
- return { value: restore(translation), skipped: false }
- }
- catch (error) {
- console.error(`❌ Error translating to ${toLanguage}:`, error.message)
- return { value: source, skipped: true, error: error.message }
- }
-}
-
-async function translateMissingKeys(sourceObj, targetObject, toLanguage) {
- const skippedKeys = []
- const translatedKeys = []
-
- for (const key of Object.keys(sourceObj)) {
- const sourceValue = sourceObj[key]
- const targetValue = targetObject[key]
-
- // Skip if target already has this key
- if (targetValue !== undefined)
- continue
-
- const translationResult = await translateText(sourceValue, toLanguage)
- targetObject[key] = translationResult.value ?? ''
- if (translationResult.skipped)
- skippedKeys.push(`${key}: ${sourceValue}`)
- else
- translatedKeys.push(key)
- }
-
- return { skipped: skippedKeys, translated: translatedKeys }
-}
-async function autoGenTrans(fileName, toGenLanguage, isDryRun = false) {
- const fullKeyFilePath = path.resolve(__dirname, i18nFolder, targetLanguage, `${fileName}.json`)
- const toGenLanguageFilePath = path.resolve(__dirname, i18nFolder, toGenLanguage, `${fileName}.json`)
-
- try {
- const content = fs.readFileSync(fullKeyFilePath, 'utf8')
- const fullKeyContent = JSON.parse(content)
-
- if (!fullKeyContent || typeof fullKeyContent !== 'object')
- throw new Error(`Failed to extract translation object from ${fullKeyFilePath}`)
-
- // if toGenLanguageFilePath does not exist, create it with empty object
- let toGenOutPut = {}
- if (fs.existsSync(toGenLanguageFilePath)) {
- const existingContent = fs.readFileSync(toGenLanguageFilePath, 'utf8')
- toGenOutPut = JSON.parse(existingContent)
- }
-
- console.log(`\n🌍 Processing ${fileName} for ${toGenLanguage}...`)
- const result = await translateMissingKeys(fullKeyContent, toGenOutPut, toGenLanguage)
-
- // Generate summary report
- console.log(`\n📊 Translation Summary for ${fileName} -> ${toGenLanguage}:`)
- console.log(` ✅ Translated: ${result.translated.length} keys`)
- console.log(` ⏭️ Skipped: ${result.skipped.length} keys`)
-
- if (result.skipped.length > 0) {
- console.log(`\n⚠️ Skipped keys in ${fileName} (${toGenLanguage}):`)
- result.skipped.slice(0, 5).forEach(item => console.log(` - ${item}`))
- if (result.skipped.length > 5)
- console.log(` ... and ${result.skipped.length - 5} more`)
- }
-
- const res = `${JSON.stringify(toGenOutPut, null, 2)}\n`
-
- if (!isDryRun) {
- fs.writeFileSync(toGenLanguageFilePath, res)
- console.log(`💾 Saved translations to ${toGenLanguageFilePath}`)
- }
- else {
- console.log(`🔍 [DRY RUN] Would save translations to ${toGenLanguageFilePath}`)
- }
-
- return result
- }
- catch (error) {
- console.error(`Error processing file ${fullKeyFilePath}:`, error.message)
- throw error
- }
-}
-
-// Add command line argument support
-const args = parseArgs(process.argv)
-const isDryRun = args.isDryRun
-const targetFiles = args.files
-const targetLangs = args.languages
-
-// Rate limiting helper
-function delay(ms) {
- return new Promise(resolve => setTimeout(resolve, ms))
-}
-
-async function main() {
- if (args.help) {
- printHelp()
- return
- }
-
- if (args.errors.length) {
- args.errors.forEach(message => console.error(`❌ ${message}`))
- printHelp()
- process.exit(1)
- return
- }
-
- console.log('🚀 Starting i18n:gen script...')
- console.log(`📋 Mode: ${isDryRun ? 'DRY RUN (no files will be modified)' : 'LIVE MODE'}`)
-
- const filesInEn = fs
- .readdirSync(path.resolve(__dirname, i18nFolder, targetLanguage))
- .filter(file => /\.json$/.test(file)) // Only process .json files
- .map(file => file.replace(/\.json$/, ''))
-
- // Filter by target files if specified
- const filesToProcess = targetFiles.length > 0 ? filesInEn.filter(f => targetFiles.includes(f)) : filesInEn
- const languagesToProcess = Array.from(new Set((targetLangs.length > 0 ? targetLangs : supportedLanguages)
- .filter(lang => lang !== targetLanguage)))
-
- const unknownLangs = languagesToProcess.filter(lang => !languageKeyMap[lang])
- if (unknownLangs.length) {
- console.error(`❌ Unsupported languages: ${unknownLangs.join(', ')}`)
- process.exit(1)
- }
-
- if (!filesToProcess.length) {
- console.log('ℹ️ No files to process based on provided arguments')
- return
- }
-
- if (!languagesToProcess.length) {
- console.log('ℹ️ No languages to process (did you only specify en-US?)')
- return
- }
-
- console.log(`📁 Files to process: ${filesToProcess.join(', ')}`)
- console.log(`🌍 Languages to process: ${languagesToProcess.join(', ')}`)
-
- let totalTranslated = 0
- let totalSkipped = 0
- let totalErrors = 0
-
- // Process files sequentially to avoid API rate limits
- for (const file of filesToProcess) {
- console.log(`\n📄 Processing file: ${file}`)
-
- // Process languages with rate limiting
- for (const language of languagesToProcess) {
- try {
- const result = await autoGenTrans(file, language, isDryRun)
- totalTranslated += result.translated.length
- totalSkipped += result.skipped.length
-
- // Rate limiting: wait 500ms between language processing
- await delay(500)
- }
- catch (e) {
- console.error(`❌ Error translating ${file} to ${language}:`, e.message)
- totalErrors++
- }
- }
- }
-
- // Final summary
- console.log('\n🎉 Auto-translation completed!')
- console.log('📊 Final Summary:')
- console.log(` ✅ Total keys translated: ${totalTranslated}`)
- console.log(` ⏭️ Total keys skipped: ${totalSkipped}`)
- console.log(` ❌ Total errors: ${totalErrors}`)
-
- if (isDryRun)
- console.log('\n💡 This was a dry run. To actually translate, run without --dry-run flag.')
-
- if (totalErrors > 0)
- process.exitCode = 1
-}
-
-main().catch((error) => {
- console.error('❌ Unexpected error:', error.message)
- process.exit(1)
-})