Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
19ea404
feat(i18n): add code.json translations for 18 locales and dev docs fo…
neoOpus Aug 21, 2026
d015418
feat(i18n/fr): add translated dev docs for fr
neoOpus Aug 21, 2026
2ad223a
feat(i18n/ar): add translated dev docs for ar
neoOpus Aug 21, 2026
149d01b
feat(i18n/fa): add translated dev docs for fa
neoOpus Aug 21, 2026
3b74aa4
feat(i18n/nl): add translated dev docs for nl
neoOpus Aug 21, 2026
9f972e8
feat(i18n/bn): add translated dev docs for bn
neoOpus Aug 21, 2026
5d6b256
feat(i18n/id): add translated dev docs for id
neoOpus Aug 21, 2026
4e89658
feat(i18n/hy): add translated dev docs for hy
neoOpus Aug 21, 2026
123dffa
feat(i18n/uk): add translated dev docs for uk
neoOpus Aug 21, 2026
e441acb
feat(i18n/tr): add translated dev docs for tr
neoOpus Aug 21, 2026
7c4b130
feat(i18n/ko): add translated dev docs for ko
neoOpus Aug 21, 2026
73b625d
feat(i18n): translate remaining dev docs (api, background, meta, cat-…
neoOpus Aug 21, 2026
b6655d9
feat(i18n): translate remaining 13 missing docs for 7 legacy locales
neoOpus Aug 21, 2026
d1551c2
fix(i18n): add missing use/use.md Quick Start page for 7 legacy locales
neoOpus Aug 21, 2026
f387d6f
feat(i18n): add navbar.json and footer.json for 17 locales
neoOpus Aug 21, 2026
8706974
Merge branch 'i18n-fr-dev-docs' into i18n-infra-v2
neoOpus Aug 21, 2026
09c4925
Merge branch 'i18n-ar-dev-docs' into i18n-infra-v2
neoOpus Aug 21, 2026
56336f6
Merge branch 'i18n-fa-dev-docs' into i18n-infra-v2
neoOpus Aug 21, 2026
c04bacc
Merge branch 'i18n-nl-dev-docs' into i18n-infra-v2
neoOpus Aug 21, 2026
e4fe4f2
Merge branch 'i18n-bn-dev-docs' into i18n-infra-v2
neoOpus Aug 21, 2026
7b645fa
Merge branch 'i18n-id-dev-docs' into i18n-infra-v2
neoOpus Aug 21, 2026
9d035d9
Merge branch 'i18n-hy-dev-docs' into i18n-infra-v2
neoOpus Aug 21, 2026
593a351
Merge branch 'i18n-uk-dev-docs' into i18n-infra-v2
neoOpus Aug 21, 2026
74cd576
Merge branch 'i18n-tr-dev-docs' into i18n-infra-v2
neoOpus Aug 21, 2026
37c827b
Merge branch 'i18n-ko-dev-docs' into i18n-infra-v2
neoOpus Aug 21, 2026
7dcb349
feat(i18n): merge all 10 locale dev-docs branches into i18n-infra-v2
neoOpus Aug 21, 2026
87a56aa
docs(i18n): add terminology glossary for all 20 locales
neoOpus Aug 21, 2026
03d2e9b
fix(i18n): make language dropdown follow light/dark theme
neoOpus Aug 21, 2026
449b2a8
fix(i18n): rename agent docs to match EN source filenames
neoOpus Aug 21, 2026
686fbad
fix(i18n): add explicit heading IDs to fix broken anchors across all …
neoOpus Aug 21, 2026
3bee01c
fix(ui): flip step arrows for RTL languages on desktop
neoOpus Aug 21, 2026
1e011f9
fix(i18n): use translated search keywords instead of hardcoded Chinese
neoOpus Aug 21, 2026
e3a1afe
feat(i18n): make language dropdown scrollable
neoOpus Aug 21, 2026
6b6b319
feat(i18n): add language selector to mobile hamburger menu
neoOpus Aug 21, 2026
5023705
fix(i18n): fix broken internal links across all 18 locales
neoOpus Aug 21, 2026
279df79
fix(i18n): fix broken agent doc cross-references and add missing side…
neoOpus Aug 21, 2026
e42151f
fix(i18n): add 175 missing code.json keys to zh-Hans (Simplified Chin…
neoOpus Aug 21, 2026
328b52f
fix(ui): improve light theme contrast to pass WCAG AA
neoOpus Aug 21, 2026
c559cb1
fix(ui): step arrows always point down on mobile for both LTR and RTL
neoOpus Aug 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
144 changes: 144 additions & 0 deletions .freebuff/archive/audit-all-json.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
#!/usr/bin/env node
import { readFileSync } from 'fs';
import { join } from 'path';

const BASE = join(import.meta.dirname, '..');

// All locale code.json paths
const LOCALES = ['fr','ar','de','es','it','ja','pt','vi','zh-Hant','ru','fa','nl','bn','id','hy','uk','tr','ko'];
const EN_LOCALE = 'en';

// JSON files to audit
const JSON_FILES = [
{ name: 'code.json', path: (l) => `i18n/${l}/code.json`, enPath: `i18n/en/code.json` },
{ name: 'current.json', path: (l) => `i18n/${l}/docusaurus-plugin-content-docs/current.json`, enPath: null },
{ name: 'footer.json', path: (l) => `i18n/${l}/docusaurus-theme-classic/footer.json`, enPath: `i18n/en/docusaurus-theme-classic/footer.json` },
{ name: 'navbar.json', path: (l) => `i18n/${l}/docusaurus-theme-classic/navbar.json`, enPath: `i18n/en/docusaurus-theme-classic/navbar.json` },
];

// Keys to skip (brand names, URLs, etc.)
const SKIP_PATTERNS = [
'homepage.download.chrome', 'homepage.download.edge', 'homepage.download.firefox',
'homepage.download.github',
];

function loadJson(filePath) {
try {
return JSON.parse(readFileSync(join(BASE, filePath), 'utf8'));
} catch(e) {
return null;
}
}

// Deep comparison: check if two objects are structurally identical (same keys)
function structurallyIdentical(obj1, obj2) {
if (typeof obj1 !== typeof obj2) return false;
if (typeof obj1 !== 'object' || obj1 === null) return obj1 === obj2;
const keys1 = Object.keys(obj1).sort();
const keys2 = Object.keys(obj2).sort();
if (keys1.length !== keys2.length) return false;
return keys1.every((k, i) => k === keys2[i]);
}

// Check if a value is untranslated (same as EN)
function isUntranslated(locVal, enVal) {
if (!locVal || !enVal) return false;
if (typeof locVal === 'object' && typeof enVal === 'object') {
if (locVal.message && enVal.message) {
if (locVal.message === enVal.message && enVal.message.length > 15) return true;
}
// Check nested objects
for (const key of Object.keys(enVal)) {
if (isUntranslated(locVal[key], enVal[key])) return true;
}
}
return false;
}

console.log('=== Comprehensive JSON Audit ===\n');

let totalIssues = 0;

for (const jsonFile of JSON_FILES) {
console.log(`\n--- ${jsonFile.name} ---`);

const enData = jsonFile.enPath ? loadJson(jsonFile.enPath) : null;

for (const locale of LOCALES) {
const locPath = jsonFile.path(locale);
const locData = loadJson(locPath);

if (!locData) {
console.log(` ${locale}: ❌ FILE MISSING`);
continue;
}

let issues = 0;
const issueList = [];

if (enData && jsonFile.name === 'code.json') {
// For code.json, check against EN
for (const [key, enVal] of Object.entries(enData)) {
if (SKIP_PATTERNS.some(p => key.startsWith(p))) continue;
const locVal = locData[key];
if (!locVal) continue;
if (isUntranslated(locVal, enVal)) {
issues++;
issueList.push(key);
}
}
} else if (jsonFile.name === 'footer.json' || jsonFile.name === 'navbar.json') {
// For footer/navbar, check if all values are in target language
const enD = enData || {};
for (const [key, enVal] of Object.entries(enD)) {
const locVal = locData[key];
if (!locVal) continue;
if (typeof locVal === 'object' && typeof enVal === 'object') {
for (const [subKey, enSubVal] of Object.entries(enVal)) {
if (typeof enSubVal === 'string' && typeof locVal[subKey] === 'string') {
if (locVal[subKey] === enSubVal && enSubVal.length > 5) {
issues++;
issueList.push(`${key}.${subKey}`);
}
}
}
}
}
} else if (jsonFile.name === 'current.json') {
// For current.json, check sidebar labels
// These are usually short labels, just check structure matches
const enD = enData || {};
const enKeys = flattenKeys(enD);
const locKeys = flattenKeys(locData);
const missing = enKeys.filter(k => !locKeys.includes(k));
if (missing.length > 0) {
issues += missing.length;
issueList.push(...missing.map(k => `MISSING: ${k}`));
}
}

if (issues > 0) {
console.log(` ${locale}: ⚠️ ${issues} issues`);
issueList.slice(0, 3).forEach(i => console.log(` - ${i}`));
if (issueList.length > 3) console.log(` ... and ${issueList.length - 3} more`);
totalIssues += issues;
} else {
console.log(` ${locale}: ✅ OK`);
}
}
}

function flattenKeys(obj, prefix = '') {
const keys = [];
for (const [k, v] of Object.entries(obj)) {
const fullKey = prefix ? `${prefix}.${k}` : k;
if (typeof v === 'object' && v !== null && !v.message) {
keys.push(...flattenKeys(v, fullKey));
} else {
keys.push(fullKey);
}
}
return keys;
}

console.log(`\n=== Total: ${totalIssues} issues ===`);
82 changes: 82 additions & 0 deletions .freebuff/archive/audit-codejson.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#!/usr/bin/env node
import { readFileSync } from 'fs';
import { join } from 'path';

const BASE = join(import.meta.dirname, '..');
const EN_PATH = join(BASE, 'i18n/en/code.json');
const en = JSON.parse(readFileSync(EN_PATH, 'utf8'));

// All locale code.json paths
const locales = ['fr','ar','de','es','it','ja','pt','vi','zh-Hant','ru','fa','nl','bn','id','hy','uk','tr','ko'];

// Known English-only phrases that should NOT appear in non-EN locales
const EN_MARKERS = [
'The ', 'This ', 'A ', 'An ', 'It ', 'In ', 'On ', 'At ',
'You ', 'We ', 'They ', 'Our ', 'Your ', 'The ',
'is a ', 'are ', 'was ', 'will ', 'can ', 'has ', 'have ',
'not ', 'the ', 'and ', 'for ', 'with ', 'from ',
'click ', 'search ', 'clicking ',
];

function isLikelyEnglish(text) {
if (!text || text.length < 15) return false;
// Skip obvious non-text (URLs, code, single words)
if (text.startsWith('http') || text.startsWith('/') || text.startsWith('@')) return false;
if (text.length < 20) return false;

// Count English words at start
const words = text.split(/\s+/);
if (words.length < 3) return false;

// Check if first 3 words are all English
const enWordCount = words.slice(0, 5).filter(w => /^[A-Z][a-z]+$/.test(w) || /^[a-z]+$/.test(w)).length;
if (enWordCount >= 3 && !text.match(/[\u3000-\u9fff\uac00-\ud7af\u0600-\u06ff\u0400-\u04ff\u0e00-\u0e7f\u0980-\u09ff\u0f00-\u0fff\u1000-\u109f\u1200-\u137f\u13a0-\u13ff\u1400-\u167f\u1680-\u169f\u16a0-\u16ff\u1700-\u171f\u1720-\u173f\u1740-\u175f\u1760-\u177f\u1780-\u17ff\u1800-\u18af\u1900-\u197f\u1980-\u19df\u1a00-\u1a1f\u1b00-\u1b7f\u1b80-\u1bff\u1c00-\u1c4f\u1c50-\u1cff\u1d00-\u1dbf\u1e00-\u1eff\u1f00-\u1fff\u2000-\u206f\u2070-\u209f\u20a0-\u20cf\u20d0-\u20ff\u2100-\u214f\u2150-\u218f\u2190-\u21ff\u2200-\u22ff\u2300-\u23ff\u2400-\u243f\u2440-\u245f\u2460-\u24ff\u2500-\u257f\u2580-\u259f\u25a0-\u25ff\u2600-\u26ff\u2700-\u27bf\u27c0-\u27ef\u27f0-\u27ff\u2800-\u28ff\u2900-\u297f\u2980-\u29ff\u2a00-\u2aff\u2b00-\u2bff\u2c00-\u2c5f\u2c60-\u2c7f\u2c80-\u2cff\u2d00-\u2d2f\u2d30-\u2d7f\u2d80-\u2ddf\u2de0-\u2dff\u2e00-\u2e7f\u2e80-\u2eff\u2f00-\u2fdf\u2ff0-\u2fff\u3000-\u303f\u3040-\u309f\u30a0-\u30ff\u3100-\u312f\u3130-\u318f\u31a0-\u31bf\u31f0-\u31ff\u3200-\u32ff\u3300-\u33ff\u3400-\u4dbf\u4dc0-\u4dff\u4e00-\u9fff\ua000-\ua4cf\ua4d0-\ua4ff\ua500-\ua63f\ua640-\ua69f\ua6a0-\ua6ff\ua700-\ua71f\ua720-\ua7ff\ua800-\ua82f\ua840-\ua87f\ua880-\ua8df\ua900-\ua92f\ua930-\ua95f\ua960-\ua97f\ua980-\ua9df\uaa00-\uaa5f\uaa60-\uaa7f\uaa80-\uaadf\uab00-\uab2f\uabc0-\uabff\uf900-\ufaff\ufb00-\ufb4f\ufb50-\ufdff\ufe00-\ufe0f\ufe10-\ufe1f\ufe20-\ufe2f\ufe30-\ufe4f\ufe50-\ufe6f\ufe70-\ufeff\uff00-\uffef]/)) {
return true;
}
return false;
}

console.log('=== Comprehensive code.json audit ===\n');

let totalIssues = 0;
for (const locale of locales) {
try {
const filePath = join(BASE, `i18n/${locale}/code.json`);
const locData = JSON.parse(readFileSync(filePath, 'utf8'));
const issues = [];

for (const [key, enVal] of Object.entries(en)) {
const locVal = locData[key];
if (!locVal || !locVal.message) continue;

// Skip theme.* keys (Docusaurus internal)
if (key.startsWith('theme.')) continue;

// Check if the locale value is identical to EN
if (locVal.message === enVal.message && enVal.message.length > 30) {
issues.push({ key, value: enVal.message.substring(0, 80), reason: 'IDENTICAL_TO_EN' });
}

// Check if value looks like untranslated English
if (isLikelyEnglish(locVal.message)) {
issues.push({ key, value: locVal.message.substring(0, 80), reason: 'LOOKS_ENGLISH' });
}
}

if (issues.length > 0) {
console.log(`${locale}: ${issues.length} issues`);
issues.slice(0, 5).forEach(i => {
console.log(` ${i.reason}: ${i.key}`);
console.log(` "${i.value}..."`);
});
if (issues.length > 5) console.log(` ... and ${issues.length - 5} more`);
totalIssues += issues.length;
} else {
console.log(`${locale}: ✅ OK`);
}
} catch(e) {
console.log(`${locale}: ❌ ERROR: ${e.message}`);
}
}

console.log(`\nTotal issues: ${totalIssues}`);
65 changes: 65 additions & 0 deletions .freebuff/archive/close-pr-comments.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Close PRs #88–#97 — Copy-Paste Ready

All 10 PRs are superseded by PR #87 which now contains the complete i18n foundation for all 20 locales.

---

## PR #88 (Korean)
```
Closing in favor of PR #87 — all Korean (ko) translations are now included in #87, which contains complete i18n for all 20 locales with full 44/44 doc parity, code.json, navbar.json, and footer.json.
```
Link: https://github.com/scriptscat/scriptcat.org/pull/88

## PR #89 (Turkish)
```
Closing in favor of PR #87 — all Turkish (tr) translations are now included in #87, which contains complete i18n for all 20 locales with full 44/44 doc parity, code.json, navbar.json, and footer.json.
```
Link: https://github.com/scriptscat/scriptcat.org/pull/89

## PR #90 (Ukrainian)
```
Closing in favor of PR #87 — all Ukrainian (uk) translations are now included in #87, which contains complete i18n for all 20 locales with full 44/44 doc parity, code.json, navbar.json, and footer.json.
```
Link: https://github.com/scriptscat/scriptcat.org/pull/90

## PR #91 (Armenian)
```
Closing in favor of PR #87 — all Armenian (hy) translations are now included in #87, which contains complete i18n for all 20 locales with full 44/44 doc parity, code.json, navbar.json, and footer.json.
```
Link: https://github.com/scriptscat/scriptcat.org/pull/91

## PR #92 (Indonesian)
```
Closing in favor of PR #87 — all Indonesian (id) translations are now included in #87, which contains complete i18n for all 20 locales with full 44/44 doc parity, code.json, navbar.json, and footer.json.
```
Link: https://github.com/scriptscat/scriptcat.org/pull/92

## PR #93 (Bengali)
```
Closing in favor of PR #87 — all Bengali (bn) translations are now included in #87, which contains complete i18n for all 20 locales with full 44/44 doc parity, code.json, navbar.json, and footer.json.
```
Link: https://github.com/scriptscat/scriptcat.org/pull/93

## PR #94 (Dutch)
```
Closing in favor of PR #87 — all Dutch (nl) translations are now included in #87, which contains complete i18n for all 20 locales with full 44/44 doc parity, code.json, navbar.json, and footer.json.
```
Link: https://github.com/scriptscat/scriptcat.org/pull/94

## PR #95 (Persian)
```
Closing in favor of PR #87 — all Persian (fa) translations are now included in #87, which contains complete i18n for all 20 locales with full 44/44 doc parity, code.json, navbar.json, and footer.json.
```
Link: https://github.com/scriptscat/scriptcat.org/pull/95

## PR #96 (Arabic)
```
Closing in favor of PR #87 — all Arabic (ar) translations are now included in #87, which contains complete i18n for all 20 locales with full 44/44 doc parity, code.json, navbar.json, and footer.json.
```
Link: https://github.com/scriptscat/scriptcat.org/pull/96

## PR #97 (French)
```
Closing in favor of PR #87 — all French (fr) translations are now included in #87, which contains complete i18n for all 20 locales with full 44/44 doc parity, code.json, navbar.json, and footer.json.
```
Link: https://github.com/scriptscat/scriptcat.org/pull/97
70 changes: 70 additions & 0 deletions .freebuff/archive/close-prs-checklist.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
## Close PRs #88–#97 — Step-by-Step Instructions

All 10 per-locale PRs are fully superseded by PR #87, which now contains all their content plus additional enhancements.

### How to close each PR

Go to each PR link below, click **"Close pull request"**, and paste the comment.

---

#### PR #88 — i18n ko dev docs
- **Link:** https://github.com/scriptscat/scriptcat.org/pull/88
- **Comment:**
> Closing — all Korean (ko) dev docs are now included in PR #87 which consolidates all 20 locales with full 44/44 doc parity.

#### PR #89 — i18n tr dev docs
- **Link:** https://github.com/scriptscat/scriptcat.org/pull/89
- **Comment:**
> Closing — all Turkish (tr) dev docs are now included in PR #87 which consolidates all 20 locales with full 44/44 doc parity.

#### PR #90 — i18n uk dev docs
- **Link:** https://github.com/scriptscat/scriptcat.org/pull/90
- **Comment:**
> Closing — all Ukrainian (uk) dev docs are now included in PR #87 which consolidates all 20 locales with full 44/44 doc parity.

#### PR #91 — i18n hy dev docs
- **Link:** https://github.com/scriptscat/scriptcat.org/pull/91
- **Comment:**
> Closing — all Armenian (hy) dev docs are now included in PR #87 which consolidates all 20 locales with full 44/44 doc parity.

#### PR #92 — i18n id dev docs
- **Link:** https://github.com/scriptscat/scriptcat.org/pull/92
- **Comment:**
> Closing — all Indonesian (id) dev docs are now included in PR #87 which consolidates all 20 locales with full 44/44 doc parity.

#### PR #93 — i18n bn dev docs
- **Link:** https://github.com/scriptscat/scriptcat.org/pull/93
- **Comment:**
> Closing — all Bengali (bn) dev docs are now included in PR #87 which consolidates all 20 locales with full 44/44 doc parity.

#### PR #94 — i18n nl dev docs
- **Link:** https://github.com/scriptscat/scriptcat.org/pull/94
- **Comment:**
> Closing — all Dutch (nl) dev docs are now included in PR #87 which consolidates all 20 locales with full 44/44 doc parity.

#### PR #95 — i18n fa dev docs
- **Link:** https://github.com/scriptscat/scriptcat.org/pull/95
- **Comment:**
> Closing — all Persian (fa) dev docs are now included in PR #87 which consolidates all 20 locales with full 44/44 doc parity.

#### PR #96 — i18n ar dev docs
- **Link:** https://github.com/scriptscat/scriptcat.org/pull/96
- **Comment:**
> Closing — all Arabic (ar) dev docs are now included in PR #87 which consolidates all 20 locales with full 44/44 doc parity.

#### PR #97 — i18n fr dev docs
- **Link:** https://github.com/scriptscat/scriptcat.org/pull/97
- **Comment:**
> Closing — all French (fr) dev docs are now included in PR #87 which consolidates all 20 locales with full 44/44 doc parity.

---

### Also update PR #87

**Title:** `feat(i18n): complete translations for all 20 locales with full 44/44 doc parity`

**Description:** Copy from `.freebuff/pr87-final-description.md`

### Time estimate: ~5 minutes
1 PR update + 10 PR closes = 1 clean comprehensive PR
Loading