mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-12 13:13:45 +01:00
* Remove double spaces * Enforce curly apostrophes * Enforce French typographic spaces in fr.json Add vitest test enforcing French spacing rules per https://typographisme.net/post/Les-espaces-typographiques-et-le-web: - NNBSP (U+202F) before ? ! ; - NBSP (U+00A0) before : - NBSP (U+00A0) around guillemets « » Fix 38 fr.json values that were using NBSP (or no space) before ? ! ;. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Extend French typography test to numbers and percent signs Add rules per https://typographisme.net/post/Les-espaces-typographiques-et-le-web: - NNBSP (U+202F) between digit groups (thousands separator) - NBSP (U+00A0) between a number and a following word/unit - NBSP (U+00A0) before % Fix 9 fr.json values where a regular space was used instead of NBSP between a number and a following word (e.g. "1 résultat", "10 jetons", "7 jours"). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Use point médian for inclusive writing in fr.json Replace parenthetical gender/plural markers like "sûr(e)", "abonné(e)", "licence(s)" with the point médian "·" (e.g. "sûr·e", "abonné·e", "licence·s") per modern French écriture inclusive. Add a vitest rule rejecting "word(suffix)" patterns where suffix is 1-3 letters and there is no space before the opening parenthesis. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Simplify inclusive-writing check to "(e)" substring match The previous regex (matchAll without the /g flag) would have thrown at runtime. Drop matchAll entirely and use includes('(e)'). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Add point médian to participles in fr.json Apply inclusive writing (·e) to past participles agreeing with the addressed user ("affilié·e", "invité·e", "connecté·e", "abonné·e", etc.). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Use inclusive forms in fr.json * Change "de Overleaf" to "d’Overleaf" * Fix tests * Check for consecutive spaces, not just double spaces * Update comment about guillemets * Add ESLint rules for locale JSON conventions Add three rules to @overleaf/eslint-plugin that run against locale JSON files via @eslint/json: - no-consecutive-spaces-in-locales - no-straight-apostrophes-in-locales - french-typography-in-locales Wire them up in services/web/eslint.config.mjs against `locales/*.json` (and `locales/fr.json` for the French-typography rule). * Add `@eslint/json` so JSON files can be parsed * Scope JS/TS-specific config to JS/TS files only so that built-in rules aren't applied to JSON files * Disable `no-irregular-whitespace` in french-typography-in-locales.js * Make the rules fixable * Improve regexes: user `\s` instead of custom matchers * Add sorted-keys-in-locales ESLint rule Mirrors scripts/translations/sort.js as a fixable ESLint rule: require top-level keys in locale JSON files to be sorted alphabetically. The autofix re-serializes the file with sorted keys. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Add no-unused-locale-keys ESLint rule Mirrors scripts/translations/cleanupUnusedLocales.js as a fixable ESLint rule applied only to en.json. Shells out to `find` + `cat` once per process (cached) to grep all candidate source files for key references, then reports any en.json key that isn't found. The autofix re-serializes en.json without the unused keys. Hardcoded ALWAYS_USED set covers keys built at runtime (month names, third-party-datastore notifications) that the regex scan can't see. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Add locale-variables-match-en ESLint rule Mirrors scripts/translations/checkVariables.js as an ESLint rule applied to non-en locale JSON files. Reads en.json (cached) and reports any member whose `__name__` placeholders differ from the base. GLOBALS (e.g. __appName__) are allowed as extras; IGNORE_NESTING_FOR preserves the script's per-key exceptions. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Replace some double linebreak (@overleaf/no-consecutive-spaces-in-locales) * Add no-orphan-locale-keys ESLint rule Detect translation keys in non-en locale files that aren't present in en.json. Mirrors the `--sync-non-en` flag of cleanupUnusedLocales.js. The autofix re-serializes the locale file without the orphan keys, sorted alphabetically. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Add tests for the locale JSON ESLint rules Cover the four single-file rules: - no-consecutive-spaces-in-locales - no-straight-apostrophes-in-locales - sorted-keys-in-locales - french-typography-in-locales (including the inclusive-writing suggestion for "(e)") The three cross-file rules (no-unused-locale-keys, no-orphan-locale-keys, locale-variables-match-en) read from disk and aren't covered here; they need integration tests via fixtures. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Drop bin/lint_locales — checks are now covered by ESLint rules The four checks in bin/lint_locales are duplicated by the new @overleaf/eslint-plugin rules: - sort.js --check → sorted-keys-in-locales - cleanupUnusedLocales.js --check → no-unused-locale-keys + no-orphan-locale-keys - checkVariables.js → locale-variables-match-en - single-quote grep → no-straight-apostrophes-in-locales `yarn lint` runs the ESLint rules against locales/*.json, so the lint_locales script and Makefile target are redundant. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Remove Locales.test.mjs * Delete scripts replaced by @overleaf/eslint-plugin rules Remove the three locale scripts now superseded by ESLint rules: - scripts/translations/sort.js → sorted-keys-in-locales - scripts/translations/cleanupUnusedLocales.js → no-unused-locale-keys + no-orphan-locale-keys - scripts/translations/checkVariables.js → locale-variables-match-en Also drop the matching Makefile targets (sort_locales, cleanup_unused_locales), update AGENTS.md / copilot-instructions.md to point at `yarn lint --fix` instead, and prune the deleted entries from the esm-check-migration allowlist. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Delete redundant scripts/translations/sort-locale-file.js Single-file sort utility — superseded by `yarn lint --fix path/to/file.json` via the sorted-keys-in-locales ESLint rule. No callers in repo. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Update comments and messages * Add test file pattern to ESLint configuration * Reorder "Backend specific rules" so `no-extraneous-dependencies` doesn't fail on test files * Make french-typography fixes idempotent and catch edge cases Apply Copilot's review feedback on PR #34221: - autoFix() now uses `\\s*`/`\\s+` instead of `\\s?`/`\\s` so it collapses any run of mixed whitespace into the correct NBSP/NNBSP, making the fix idempotent and order-independent. - The `:` lint regex now captures optional whitespace and reports when it isn't NBSP (catches `"Liste :"` with a regular space). - The `%` lint regex now matches the missing-space case (`"10%"`) via `\\s*` instead of requiring one char between digit and `%`. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Remove sort-locale-file.js from pre-commit * Revert no-unused-locale-keys ESLint rule; Restore lint_locales scripts --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> GitOrigin-RevId: 8351fab074e6e7f77920b70237df1cd1c781e053
70 lines
1.8 KiB
JavaScript
70 lines
1.8 KiB
JavaScript
// Detect translation keys in non-en locale files that don't exist in
|
|
// en.json (orphans).
|
|
|
|
const fs = require('node:fs')
|
|
const Path = require('node:path')
|
|
|
|
const enCache = new Map()
|
|
function loadEnKeys(localesDir) {
|
|
if (enCache.has(localesDir)) return enCache.get(localesDir)
|
|
const path = Path.join(localesDir, 'en.json')
|
|
const data = JSON.parse(fs.readFileSync(path, 'utf8'))
|
|
const keys = new Set(Object.keys(data))
|
|
enCache.set(localesDir, keys)
|
|
return keys
|
|
}
|
|
|
|
module.exports = {
|
|
meta: {
|
|
type: 'problem',
|
|
fixable: 'code',
|
|
docs: {
|
|
description:
|
|
'Detect translation keys in non-en locale files that are not present in en.json.',
|
|
},
|
|
schema: [],
|
|
messages: {
|
|
orphan:
|
|
'Translation key "{{key}}" is not present in en.json (orphan key).',
|
|
},
|
|
},
|
|
create(context) {
|
|
const filename = context.filename
|
|
const localesDir = Path.dirname(filename)
|
|
if (Path.basename(filename) === 'en.json') return {}
|
|
|
|
let enKeys
|
|
try {
|
|
enKeys = loadEnKeys(localesDir)
|
|
} catch {
|
|
return {}
|
|
}
|
|
|
|
return {
|
|
'Document > Object'(node) {
|
|
const orphans = node.members.filter(m => !enKeys.has(m.name.value))
|
|
if (orphans.length === 0) return
|
|
|
|
const text = context.sourceCode.text
|
|
const parsed = JSON.parse(text)
|
|
for (const member of orphans) {
|
|
delete parsed[member.name.value]
|
|
}
|
|
const sortedRemaining = Object.keys(parsed).sort()
|
|
const cleaned = JSON.stringify(parsed, sortedRemaining, 2) + '\n'
|
|
|
|
for (const member of orphans) {
|
|
context.report({
|
|
node: member,
|
|
messageId: 'orphan',
|
|
data: { key: member.name.value },
|
|
fix(fixer) {
|
|
return fixer.replaceTextRange([0, text.length], cleaned)
|
|
},
|
|
})
|
|
}
|
|
},
|
|
}
|
|
},
|
|
}
|