From 165f7e4be6477e790f6b4c4a4e2099991d97f4a2 Mon Sep 17 00:00:00 2001 From: itsneufox <156133096+itsneufox@users.noreply.github.com> Date: Sat, 20 Dec 2025 04:07:44 +0000 Subject: [PATCH 1/9] Stale translation warning - add script to mark outdated translations - add workflow for stale translations - add label checks to deployment remove deprecated deployment workflows for API and frontend add deployment workflow for marking translations and deploying frontend/backend add script to mark outdated translations add workflow for marking stale translations consolidate translation marking workflow into deploy process Stale translation warning Stale translation warning Stale translation warning Stale translation warning add chars threshold add translation by Nexius add translation label check to deployment workflow --- .github/workflows/deploy-web.yml | 84 +++++++- .../scripts/check-translation-label.js | 118 +++++++++++ .../scripts/filter-small-doc-changes.js | 192 ++++++++++++++++++ .../scripts/mark-translations-outdated.js | 134 ++++++++++++ 4 files changed, 527 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/scripts/check-translation-label.js create mode 100644 .github/workflows/scripts/filter-small-doc-changes.js create mode 100644 .github/workflows/scripts/mark-translations-outdated.js diff --git a/.github/workflows/deploy-web.yml b/.github/workflows/deploy-web.yml index b899f4958c2..2437186fae0 100644 --- a/.github/workflows/deploy-web.yml +++ b/.github/workflows/deploy-web.yml @@ -5,6 +5,9 @@ on: branches: - master +permissions: + contents: write + jobs: deploy: name: Deploy frontend @@ -16,7 +19,86 @@ jobs: workflow_id: 42688838 access_token: ${{ github.token }} - - uses: actions/checkout@v4 + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '18' + + - name: Check translation label + id: translation-label + env: + GITHUB_TOKEN: ${{ github.token }} + run: | + LABEL_RESULT=$(node .github/workflows/scripts/check-translation-label.js --repo "${{ github.repository }}" --token "$GITHUB_TOKEN" --sha "${{ github.sha }}" --label "docs:translation-impact") + echo "$LABEL_RESULT" >> "$GITHUB_OUTPUT" + + - name: Get changed English documentation files + id: changed-files + if: steps.translation-label.outputs.label_present == 'true' + run: | + set -euo pipefail + BEFORE="${{ github.event.before }}" + if git rev-parse "$BEFORE^{commit}" >/dev/null 2>&1; then + DIFF_BASE="$BEFORE" + else + DIFF_BASE="" + fi + + if [ -n "$DIFF_BASE" ]; then + CHANGED_FILES=$(git diff --name-only "$DIFF_BASE" "${{ github.sha }}" | grep "^frontend/docs/.*\.md$" || true) + else + CHANGED_FILES=$(git ls-tree --name-only -r "${{ github.sha }}" | grep "^frontend/docs/.*\.md$" || true) + fi + + if [ -z "$CHANGED_FILES" ]; then + echo "has_changes=false" >> "$GITHUB_OUTPUT" + else + if [ -n "$DIFF_BASE" ]; then + printf '%s\n' "$CHANGED_FILES" > changed_english_docs_raw.txt + node .github/workflows/scripts/filter-small-doc-changes.js "$DIFF_BASE" "${{ github.sha }}" changed_english_docs_raw.txt changed_english_docs.txt + if [ -s changed_english_docs.txt ]; then + echo "has_changes=true" >> "$GITHUB_OUTPUT" + else + echo "has_changes=false" >> "$GITHUB_OUTPUT" + fi + else + printf '%s\n' "$CHANGED_FILES" > changed_english_docs.txt + echo "has_changes=true" >> "$GITHUB_OUTPUT" + fi + fi + + - name: Mark translations as outdated + if: steps.translation-label.outputs.label_present == 'true' && steps.changed-files.outputs.has_changes == 'true' + run: | + node .github/workflows/scripts/mark-translations-outdated.js + + - name: Check if translations were modified + if: steps.translation-label.outputs.label_present == 'true' && steps.changed-files.outputs.has_changes == 'true' + id: check-changes + run: | + if [ -n "$(git status --porcelain)" ]; then + echo "translations_modified=true" >> "$GITHUB_OUTPUT" + else + echo "translations_modified=false" >> "$GITHUB_OUTPUT" + fi + + - name: Commit changes + if: steps.translation-label.outputs.label_present == 'true' && steps.check-changes.outputs.translations_modified == 'true' + run: | + git config --local user.email "omp-bot@users.noreply.github.com" + git config --local user.name "omp-bot" + git add frontend/i18n/ + git commit -m "Mark translations as potentially outdated (post-merge)" + + - name: Push changes + if: steps.translation-label.outputs.label_present == 'true' && steps.check-changes.outputs.translations_modified == 'true' + run: | + git push origin HEAD:${{ github.ref }} - name: Cache ~/.npm for npm ci uses: actions/cache@v4 diff --git a/.github/workflows/scripts/check-translation-label.js b/.github/workflows/scripts/check-translation-label.js new file mode 100644 index 00000000000..18e0e2e8289 --- /dev/null +++ b/.github/workflows/scripts/check-translation-label.js @@ -0,0 +1,118 @@ +#!/usr/bin/env node + +const https = require('https'); + +const args = process.argv.slice(2); +const options = { + repo: process.env.GITHUB_REPOSITORY || '', + token: process.env.GITHUB_TOKEN || '', + sha: process.env.GITHUB_SHA || '', + label: '', + pr: '', +}; + +for (let i = 0; i < args.length; i += 2) { + const key = args[i]; + const value = args[i + 1] || ''; + switch (key) { + case '--repo': + options.repo = value; + break; + case '--token': + options.token = value; + break; + case '--sha': + options.sha = value; + break; + case '--label': + options.label = value; + break; + case '--pr': + options.pr = value; + break; + default: + i -= 1; + } +} + +if (!options.repo || !options.token || !options.label) { + console.error('Missing required arguments: repo, token, and label'); + process.exit(1); +} + +const githubRequest = (path, accept) => + new Promise((resolve, reject) => { + const req = https.request( + { + hostname: 'api.github.com', + path, + method: 'GET', + headers: { + Authorization: `Bearer ${options.token}`, + 'User-Agent': 'translations-workflow', + Accept: accept || 'application/vnd.github+json', + }, + }, + res => { + let body = ''; + res.on('data', chunk => { + body += chunk; + }); + res.on('end', () => { + if (res.statusCode >= 200 && res.statusCode < 300) { + try { + resolve(JSON.parse(body)); + } catch (err) { + reject(err); + } + } else { + reject(new Error(`GitHub API responded with ${res.statusCode}: ${body}`)); + } + }); + } + ); + req.on('error', reject); + req.end(); + }); + +const detectPrNumber = async () => { + if (options.pr) { + return options.pr; + } + if (!options.sha) { + return ''; + } + + try { + const pulls = await githubRequest( + `/repos/${options.repo}/commits/${options.sha}/pulls`, + 'application/vnd.github.groot-preview+json' + ); + if (Array.isArray(pulls) && pulls.length > 0) { + return String(pulls[0].number); + } + } catch (error) { + console.error(`Failed to determine PR number: ${error.message}`); + } + return ''; +}; + +const main = async () => { + try { + const prNumber = await detectPrNumber(); + if (!prNumber) { + process.stdout.write('label_present=false'); + return; + } + + const issue = await githubRequest(`/repos/${options.repo}/issues/${prNumber}`); + const labels = Array.isArray(issue.labels) ? issue.labels.map(label => label.name) : []; + const hasLabel = labels.includes(options.label); + process.stdout.write(`label_present=${hasLabel ? 'true' : 'false'}`); + } catch (error) { + console.error(`Failed to load labels: ${error.message}`); + process.stdout.write('label_present=false'); + } +}; + +main(); diff --git a/.github/workflows/scripts/filter-small-doc-changes.js b/.github/workflows/scripts/filter-small-doc-changes.js new file mode 100644 index 00000000000..dda96f8119e --- /dev/null +++ b/.github/workflows/scripts/filter-small-doc-changes.js @@ -0,0 +1,192 @@ +#!/usr/bin/env node + +const fs = require('fs'); +const { execSync } = require('child_process'); + +const MEANINGFUL_CHAR_THRESHOLD = 10; // minimum characters to count the change as worthy + +const [, , baseRef, headRef, inputPath, outputPath] = process.argv; + +if (!headRef || !inputPath || !outputPath) { + console.error('Usage: node filter-small-doc-changes.js '); + process.exit(1); +} + +const fileList = fs + .readFileSync(inputPath, 'utf8') + .split('\n') + .map(line => line.trim()) + .filter(Boolean); + +const keptFiles = []; + +const getFileLines = (ref, file) => { + if (!ref) { + return null; + } + + try { + const content = execSync(`git show ${ref}:${file}`, { encoding: 'utf8' }); + return content.replace(/\r/g, '').split('\n'); + } catch { + return null; + } +}; + +const frontmatterEndLine = lines => { + if (!lines || lines[0] !== '---') { + return 0; + } + + for (let i = 1; i < lines.length; i++) { + if (lines[i] === '---') { + return i + 1; + } + } + return 0; +}; + +const isTrivialLine = (lineText, lineNumber, frontmatterLimit) => { + if (lineNumber > 0 && frontmatterLimit > 0 && lineNumber <= frontmatterLimit) { + return true; + } + return lineText.trim().length === 0; +}; + +const levenshtein = (a, b) => { + if (a === b) { + return 0; + } + const lenA = a.length; + const lenB = b.length; + if (lenA === 0) { + return lenB; + } + if (lenB === 0) { + return lenA; + } + + const matrix = Array.from({ length: lenA + 1 }, () => new Array(lenB + 1).fill(0)); + + for (let i = 0; i <= lenA; i++) { + matrix[i][0] = i; + } + for (let j = 0; j <= lenB; j++) { + matrix[0][j] = j; + } + + for (let i = 1; i <= lenA; i++) { + for (let j = 1; j <= lenB; j++) { + if (a[i - 1] === b[j - 1]) { + matrix[i][j] = matrix[i - 1][j - 1]; + } else { + matrix[i][j] = Math.min( + matrix[i - 1][j] + 1, + matrix[i][j - 1] + 1, + matrix[i - 1][j - 1] + 1 + ); + } + } + } + + return matrix[lenA][lenB]; +}; + +const hasMeaningfulDiff = (file, diffOutput) => { + const baseLines = getFileLines(baseRef, file); + const headLines = getFileLines(headRef, file); + + if (!diffOutput.trim()) { + return false; + } + + if (!baseLines) { + return true; + } + + const baseFrontmatterLimit = frontmatterEndLine(baseLines); + const headFrontmatterLimit = frontmatterEndLine(headLines); + + let currentOldLine = 0; + let currentNewLine = 0; + const diffLines = diffOutput.split('\n'); + const pendingRemoved = []; + let totalChangedChars = 0; + + for (const diffLine of diffLines) { + if (diffLine.startsWith('@@')) { + const match = diffLine.match(/@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/); + if (match) { + currentOldLine = Number(match[1]); + currentNewLine = Number(match[3]); + } + continue; + } + + if (diffLine.startsWith('---') || diffLine.startsWith('+++')) { + continue; + } + + if (diffLine.startsWith('-')) { + const text = baseLines[currentOldLine - 1] ?? ''; + if (!isTrivialLine(text, currentOldLine, baseFrontmatterLimit)) { + pendingRemoved.push(text); + } + currentOldLine++; + continue; + } + + if (diffLine.startsWith('+')) { + const text = headLines?.[currentNewLine - 1] ?? ''; + if (!isTrivialLine(text, currentNewLine, headFrontmatterLimit)) { + if (pendingRemoved.length > 0) { + const removed = pendingRemoved.shift(); + totalChangedChars += levenshtein(removed, text); + } else { + totalChangedChars += text.trim().length; + } + if (totalChangedChars >= MEANINGFUL_CHAR_THRESHOLD) { + return true; + } + } + currentNewLine++; + continue; + } + } + + while (pendingRemoved.length > 0) { + totalChangedChars += pendingRemoved.shift().trim().length; + if (totalChangedChars >= MEANINGFUL_CHAR_THRESHOLD) { + return true; + } + } + + return totalChangedChars >= MEANINGFUL_CHAR_THRESHOLD; +}; + +for (const file of fileList) { + if (!baseRef) { + keptFiles.push(file); + continue; + } + + let diffOutput = ''; + try { + diffOutput = execSync(`git diff ${baseRef} ${headRef} --unified=0 -- ${file}`, { + encoding: 'utf8', + stdio: ['ignore', 'pipe', 'ignore'], + }); + } catch (error) { + diffOutput = error.stdout?.toString() ?? ''; + if (!diffOutput && !error.stdout) { + keptFiles.push(file); + continue; + } + } + + if (hasMeaningfulDiff(file, diffOutput)) { + keptFiles.push(file); + } +} + +fs.writeFileSync(outputPath, keptFiles.join('\n'), 'utf8'); diff --git a/.github/workflows/scripts/mark-translations-outdated.js b/.github/workflows/scripts/mark-translations-outdated.js new file mode 100644 index 00000000000..0fbbb5079fe --- /dev/null +++ b/.github/workflows/scripts/mark-translations-outdated.js @@ -0,0 +1,134 @@ +#!/usr/bin/env node + +const fs = require('fs'); +const path = require('path'); + +const WARNING_BANNERS = { + 'es': `:::warning La traducción puede estar desactualizada +La versión en inglés de este documento se actualizó recientemente. Es posible que esta traducción aún no refleje esos cambios. + +¡Ayuda a mantener nuestras traducciones actualizadas! Si hablas este idioma con fluidez, considera revisar la [versión en inglés](ENGLISH_DOC_LINK) y actualizar esta traducción. +::: + +`, + 'pt-BR': `:::warning A tradução pode estar desatualizada +A versão em inglês deste documento foi atualizada recentemente. Esta tradução pode não refletir essas alterações ainda. + +Ajude-nos a manter nossas traduções atualizadas! Se você é fluente neste idioma, considere revisar a [versão em inglês](ENGLISH_DOC_LINK) e atualizar esta tradução. +::: + +`, + 'ru': `:::warning Этот перевод может быть устаревшим. +Английская версия этой статьи была недавно обновлена. Данный перевод может всё ещё не отражать эти изменения. + +Помогите нам поддерживать актуальность переводов! Если вы свободно владеете английским языком, пожалуйста, рассмотрите возможность проверки [английской версии](ENGLISH_DOC_LINK) и обновления этого перевода. +::: + +`, +}; + +const DEFAULT_WARNING = `:::warning Translation May Be Outdated +The English version of this document was recently updated. This translation may not reflect those changes yet. + +Please help keep our translations up to date! If you're fluent in this language, consider reviewing the [English version](ENGLISH_DOC_LINK) and updating this translation. +::: + +`; + +const WARNING_MARKER = ':::warning'; + +const changedFilesPath = path.join(process.cwd(), 'changed_english_docs.txt'); +if (!fs.existsSync(changedFilesPath)) { + console.log('No changed English docs file found'); + process.exit(0); +} + +const changedEnglishDocs = fs.readFileSync(changedFilesPath, 'utf8') + .trim() + .split('\n') + .filter(line => line.trim().length > 0); + +if (changedEnglishDocs.length === 0) { + console.log('No changed English docs to process'); + process.exit(0); +} + +const i18nDir = path.join(process.cwd(), 'frontend', 'i18n'); +if (!fs.existsSync(i18nDir)) { + console.log('No i18n directory found'); + process.exit(0); +} + +const languages = fs.readdirSync(i18nDir).filter(item => { + const itemPath = path.join(i18nDir, item); + return fs.statSync(itemPath).isDirectory(); +}); + +console.log(`Found ${languages.length} language directories: ${languages.join(', ')}`); + +let totalFilesMarked = 0; +const markedFiles = []; + +changedEnglishDocs.forEach(englishDocPath => { + const relativePath = englishDocPath.replace('frontend/docs/', ''); + const englishDocLink = `/docs/${relativePath.replace('.md', '')}`; + + console.log(`\nProcessing: ${englishDocPath}`); + console.log(` Relative path: ${relativePath}`); + + languages.forEach(lang => { + const translationPath = path.join( + i18nDir, + lang, + 'docusaurus-plugin-content-docs', + 'current', + relativePath + ); + + if (!fs.existsSync(translationPath)) { + console.log(` [${lang}] Translation does not exist, skipping`); + return; + } + + let content = fs.readFileSync(translationPath, 'utf8'); + + if (content.includes(WARNING_MARKER)) { + console.log(` [${lang}] Already has outdated warning, skipping`); + return; + } + + let frontmatterEnd = 0; + if (content.startsWith('---')) { + const secondDelimiter = content.indexOf('---', 3); + if (secondDelimiter !== -1) { + frontmatterEnd = secondDelimiter + 3; + } + } + + const warningBanner = WARNING_BANNERS[lang] || DEFAULT_WARNING; + const warningWithLink = warningBanner.replace('ENGLISH_DOC_LINK', englishDocLink); + + let updatedContent; + if (frontmatterEnd > 0) { + const frontmatter = content.substring(0, frontmatterEnd); + const restOfContent = content.substring(frontmatterEnd).trimStart(); + updatedContent = `${frontmatter}\n\n${warningWithLink}${restOfContent}`; + } else { + const restOfContent = content.trimStart(); + updatedContent = `${warningWithLink}${restOfContent}`; + } + + fs.writeFileSync(translationPath, updatedContent, 'utf8'); + console.log(` [${lang}] ✓ Marked as outdated`); + totalFilesMarked++; + markedFiles.push(translationPath.replace(process.cwd() + path.sep, '').replace(/\\/g, '/')); + }); +}); + +console.log(`\n✅ Total files marked: ${totalFilesMarked}`); + +if (markedFiles.length > 0) { + const markedFilesPath = path.join(process.cwd(), 'marked_translation_files.txt'); + fs.writeFileSync(markedFilesPath, markedFiles.join('\n'), 'utf8'); + console.log(`Marked files list written to: ${markedFilesPath}`); +} From ae832129e125b3e27974697dc5b07ee03e24b9e3 Mon Sep 17 00:00:00 2001 From: itsneufox <156133096+itsneufox@users.noreply.github.com> Date: Tue, 23 Dec 2025 22:59:56 +0000 Subject: [PATCH 2/9] remove hardcoded text --- .../scripts/mark-translations-outdated.js | 39 ++++--------------- 1 file changed, 7 insertions(+), 32 deletions(-) diff --git a/.github/workflows/scripts/mark-translations-outdated.js b/.github/workflows/scripts/mark-translations-outdated.js index 0fbbb5079fe..bc1c1130d34 100644 --- a/.github/workflows/scripts/mark-translations-outdated.js +++ b/.github/workflows/scripts/mark-translations-outdated.js @@ -3,39 +3,14 @@ const fs = require('fs'); const path = require('path'); -const WARNING_BANNERS = { - 'es': `:::warning La traducción puede estar desactualizada -La versión en inglés de este documento se actualizó recientemente. Es posible que esta traducción aún no refleje esos cambios. - -¡Ayuda a mantener nuestras traducciones actualizadas! Si hablas este idioma con fluidez, considera revisar la [versión en inglés](ENGLISH_DOC_LINK) y actualizar esta traducción. -::: - -`, - 'pt-BR': `:::warning A tradução pode estar desatualizada -A versão em inglês deste documento foi atualizada recentemente. Esta tradução pode não refletir essas alterações ainda. - -Ajude-nos a manter nossas traduções atualizadas! Se você é fluente neste idioma, considere revisar a [versão em inglês](ENGLISH_DOC_LINK) e atualizar esta tradução. -::: - -`, - 'ru': `:::warning Этот перевод может быть устаревшим. -Английская версия этой статьи была недавно обновлена. Данный перевод может всё ещё не отражать эти изменения. - -Помогите нам поддерживать актуальность переводов! Если вы свободно владеете английским языком, пожалуйста, рассмотрите возможность проверки [английской версии](ENGLISH_DOC_LINK) и обновления этого перевода. -::: - -`, +const WARNING_COMPONENTS = { + 'es': 'StaleTranslationWarningES', + 'pt-BR': 'StaleTranslationWarningPT', }; -const DEFAULT_WARNING = `:::warning Translation May Be Outdated -The English version of this document was recently updated. This translation may not reflect those changes yet. - -Please help keep our translations up to date! If you're fluent in this language, consider reviewing the [English version](ENGLISH_DOC_LINK) and updating this translation. -::: - -`; +const DEFAULT_WARNING_COMPONENT = 'StaleTranslationWarning'; -const WARNING_MARKER = ':::warning'; +const WARNING_MARKER = ' { } } - const warningBanner = WARNING_BANNERS[lang] || DEFAULT_WARNING; - const warningWithLink = warningBanner.replace('ENGLISH_DOC_LINK', englishDocLink); + const componentName = WARNING_COMPONENTS[lang] || DEFAULT_WARNING_COMPONENT; + const warningWithLink = `<${componentName} englishDocLink="${englishDocLink}" />\n\n`; let updatedContent; if (frontmatterEnd > 0) { From aaed9eded3a25f97613bd829d08bcef18bf94621 Mon Sep 17 00:00:00 2001 From: itsneufox <156133096+itsneufox@users.noreply.github.com> Date: Tue, 23 Dec 2025 23:00:08 +0000 Subject: [PATCH 3/9] add component mapping --- frontend/src/components/templates/index.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/frontend/src/components/templates/index.ts b/frontend/src/components/templates/index.ts index f006d01d9d7..df10fc86853 100644 --- a/frontend/src/components/templates/index.ts +++ b/frontend/src/components/templates/index.ts @@ -1,6 +1,7 @@ import VersionWarn from "./version-warning"; import LowercaseNote from "./lowercase-note"; import TipNPCCallbacks from "./npc-callbacks-tip"; +import StaleTranslationWarning from "./stale-translation-warning"; import VersionWarnID from "./translations/id/version-warning"; import LowercaseNoteID from "./translations/id/lowercase-note"; @@ -9,10 +10,12 @@ import TipNPCCallbacksID from "./translations/id/npc-callbacks-tip"; import VersionWarnPT_BR from "./translations/pt-BR/version-warning"; import LowercaseNotePT_BR from "./translations/pt-BR/lowercase-note"; import TipNPCCallbacksPT_BR from "./translations/pt-BR/npc-callbacks-tip"; +import StaleTranslationWarningPT_BR from "./translations/pt-BR/stale-translation-warning"; import VersionWarnES from "./translations/es/version-warning"; import LowercaseNoteES from "./translations/es/lowercase-note"; import TipNPCCallbacksES from "./translations/es/npc-callbacks-tip"; +import StaleTranslationWarningES from "./translations/es/stale-translation-warning"; import VersionWarnZH_CN from "./translations/zh-CN/version-warning"; import LowercaseNoteZH_CN from "./translations/zh-CN/lowercase-note"; @@ -34,15 +37,18 @@ const templates = { VersionWarn, LowercaseNote, TipNPCCallbacks, + StaleTranslationWarning, VersionWarnID, LowercaseNoteID, TipNPCCallbacksID, VersionWarnPT: VersionWarnPT_BR, LowercaseNotePT: LowercaseNotePT_BR, TipNPCCallbacksPT: TipNPCCallbacksPT_BR, + StaleTranslationWarningPT: StaleTranslationWarningPT_BR, VersionWarnES, LowercaseNoteES, TipNPCCallbacksES, + StaleTranslationWarningES, VersionWarnZH_CN, LowercaseNoteZH_CN, TipNPCCallbacksZH_CN, From 1fd3a845ce28653b4ef563e59ebdd233d94d00bc Mon Sep 17 00:00:00 2001 From: itsneufox <156133096+itsneufox@users.noreply.github.com> Date: Tue, 23 Dec 2025 23:00:20 +0000 Subject: [PATCH 4/9] english --- .../templates/stale-translation-warning.tsx | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 frontend/src/components/templates/stale-translation-warning.tsx diff --git a/frontend/src/components/templates/stale-translation-warning.tsx b/frontend/src/components/templates/stale-translation-warning.tsx new file mode 100644 index 00000000000..a49eed44117 --- /dev/null +++ b/frontend/src/components/templates/stale-translation-warning.tsx @@ -0,0 +1,25 @@ +import Admonition from "../Admonition"; + +export default function StaleTranslationWarning({ + englishDocLink, +}: { + englishDocLink: string; +}) { + return ( + +

+ Translation May Be Outdated +

+

+ The English version of this document was recently updated. This + translation may not reflect those changes yet. +

+

+ Please help keep our translations up to date! If you're fluent in this + language, consider reviewing the{" "} + English version and updating this + translation. +

+
+ ); +} From bafe331259404a0536d3fa2dbf905f6133c9b99a Mon Sep 17 00:00:00 2001 From: itsneufox <156133096+itsneufox@users.noreply.github.com> Date: Tue, 23 Dec 2025 23:00:59 +0000 Subject: [PATCH 5/9] =?UTF-8?q?espa=C3=B1ol?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../es/stale-translation-warning.tsx | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 frontend/src/components/templates/translations/es/stale-translation-warning.tsx diff --git a/frontend/src/components/templates/translations/es/stale-translation-warning.tsx b/frontend/src/components/templates/translations/es/stale-translation-warning.tsx new file mode 100644 index 00000000000..d5113ec3444 --- /dev/null +++ b/frontend/src/components/templates/translations/es/stale-translation-warning.tsx @@ -0,0 +1,25 @@ +import Admonition from "../../../Admonition"; + +export default function StaleTranslationWarning({ + englishDocLink, +}: { + englishDocLink: string; +}) { + return ( + +

+ La traducción puede estar desactualizada +

+

+ La versión en inglés de este documento se actualizó recientemente. Es + posible que esta traducción aún no refleje esos cambios. +

+

+ ¡Ayuda a mantener nuestras traducciones actualizadas! Si hablas este + idioma con fluidez, considera revisar la{" "} + versión en inglés y actualizar esta + traducción. +

+
+ ); +} From 89242ba731320238f30e16b7b62dc5f452fdcc94 Mon Sep 17 00:00:00 2001 From: itsneufox <156133096+itsneufox@users.noreply.github.com> Date: Tue, 23 Dec 2025 23:01:06 +0000 Subject: [PATCH 6/9] =?UTF-8?q?portugu=C3=AAs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pt-BR/stale-translation-warning.tsx | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 frontend/src/components/templates/translations/pt-BR/stale-translation-warning.tsx diff --git a/frontend/src/components/templates/translations/pt-BR/stale-translation-warning.tsx b/frontend/src/components/templates/translations/pt-BR/stale-translation-warning.tsx new file mode 100644 index 00000000000..f6aa2e941cc --- /dev/null +++ b/frontend/src/components/templates/translations/pt-BR/stale-translation-warning.tsx @@ -0,0 +1,25 @@ +import Admonition from "../../../Admonition"; + +export default function StaleTranslationWarning({ + englishDocLink, +}: { + englishDocLink: string; +}) { + return ( + +

+ A tradução pode estar desatualizada +

+

+ A versão em inglês deste documento foi atualizada recentemente. Esta + tradução pode não refletir essas alterações ainda. +

+

+ Ajude-nos a manter nossas traduções atualizadas! Se você é fluente + neste idioma, considere revisar a{" "} + versão em inglês e atualizar esta + tradução. +

+
+ ); +} From 4590104857617eb328cf65472a0e772f9035b878 Mon Sep 17 00:00:00 2001 From: itsneufox <156133096+itsneufox@users.noreply.github.com> Date: Tue, 23 Dec 2025 23:08:49 +0000 Subject: [PATCH 7/9] =?UTF-8?q?=D1=80=D1=83=D1=81=D1=81=D0=BA=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ru/stale-translation-warning.tsx | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 frontend/src/components/templates/translations/ru/stale-translation-warning.tsx diff --git a/frontend/src/components/templates/translations/ru/stale-translation-warning.tsx b/frontend/src/components/templates/translations/ru/stale-translation-warning.tsx new file mode 100644 index 00000000000..099e6e9592e --- /dev/null +++ b/frontend/src/components/templates/translations/ru/stale-translation-warning.tsx @@ -0,0 +1,25 @@ +import Admonition from "../../../Admonition"; + +export default function StaleTranslationWarning({ + englishDocLink, +}: { + englishDocLink: string; +}) { + return ( + +

+ Этот перевод может быть устаревшим. +

+

+ Английская версия этой статьи была недавно обновлена. Данный перевод + может всё ещё не отражать эти изменения. +

+

+ Помогите нам поддерживать актуальность переводов! Если вы свободно + владеете английским языком, пожалуйста, рассмотрите возможность + проверки английской версии и обновления + этого перевода. +

+
+ ); +} From e2a1daa6b3410abd3ba2e927a30b6b9b844b4335 Mon Sep 17 00:00:00 2001 From: itsneufox <156133096+itsneufox@users.noreply.github.com> Date: Tue, 23 Dec 2025 23:09:05 +0000 Subject: [PATCH 8/9] add ru and some helpful comments --- .github/workflows/scripts/mark-translations-outdated.js | 3 +++ frontend/src/components/templates/index.ts | 9 +++++++++ 2 files changed, 12 insertions(+) diff --git a/.github/workflows/scripts/mark-translations-outdated.js b/.github/workflows/scripts/mark-translations-outdated.js index bc1c1130d34..6e60bd26fe7 100644 --- a/.github/workflows/scripts/mark-translations-outdated.js +++ b/.github/workflows/scripts/mark-translations-outdated.js @@ -3,9 +3,12 @@ const fs = require('fs'); const path = require('path'); +// Map language codes to their StaleTranslationWarning component names +// When adding a new language, add the mapping here AND in frontend/src/components/templates/index.ts const WARNING_COMPONENTS = { 'es': 'StaleTranslationWarningES', 'pt-BR': 'StaleTranslationWarningPT', + 'ru': 'StaleTranslationWarningRU', }; const DEFAULT_WARNING_COMPONENT = 'StaleTranslationWarning'; diff --git a/frontend/src/components/templates/index.ts b/frontend/src/components/templates/index.ts index df10fc86853..b69312b37e9 100644 --- a/frontend/src/components/templates/index.ts +++ b/frontend/src/components/templates/index.ts @@ -1,3 +1,9 @@ +// When adding a new language translation component: +// 1. Create the component file in translations/{lang}/{component-name}.tsx +// 2. Import it below with the other language-specific imports +// 3. Add it to the templates object export +// 4. For StaleTranslationWarning components, also add the mapping in .github/workflows/scripts/mark-translations-outdated.js + import VersionWarn from "./version-warning"; import LowercaseNote from "./lowercase-note"; import TipNPCCallbacks from "./npc-callbacks-tip"; @@ -33,6 +39,8 @@ import VersionWarnSR from "./translations/sr/version-warning"; import LowercaseNoteSR from "./translations/sr/lowercase-note"; import TipNPCCallbacksSR from "./translations/sr/npc-callbacks-tip"; +import StaleTranslationWarningRU from "./translations/ru/stale-translation-warning"; + const templates = { VersionWarn, LowercaseNote, @@ -61,6 +69,7 @@ const templates = { VersionWarnSR, LowercaseNoteSR, TipNPCCallbacksSR, + StaleTranslationWarningRU, }; export default templates; From 0fcff376ce15c52d8d1601b6a24b77f8c257907a Mon Sep 17 00:00:00 2001 From: itsneufox <156133096+itsneufox@users.noreply.github.com> Date: Wed, 24 Dec 2025 12:12:25 +0000 Subject: [PATCH 9/9] =?UTF-8?q?Fran=C3=A7ais?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../scripts/mark-translations-outdated.js | 1 + frontend/src/components/templates/index.ts | 3 +++ .../fr/stale-translation-warning.tsx | 25 +++++++++++++++++++ 3 files changed, 29 insertions(+) create mode 100644 frontend/src/components/templates/translations/fr/stale-translation-warning.tsx diff --git a/.github/workflows/scripts/mark-translations-outdated.js b/.github/workflows/scripts/mark-translations-outdated.js index 6e60bd26fe7..e4b4b4cde8b 100644 --- a/.github/workflows/scripts/mark-translations-outdated.js +++ b/.github/workflows/scripts/mark-translations-outdated.js @@ -9,6 +9,7 @@ const WARNING_COMPONENTS = { 'es': 'StaleTranslationWarningES', 'pt-BR': 'StaleTranslationWarningPT', 'ru': 'StaleTranslationWarningRU', + 'fr': 'StaleTranslationWarningFR', }; const DEFAULT_WARNING_COMPONENT = 'StaleTranslationWarning'; diff --git a/frontend/src/components/templates/index.ts b/frontend/src/components/templates/index.ts index b69312b37e9..d62d8420c91 100644 --- a/frontend/src/components/templates/index.ts +++ b/frontend/src/components/templates/index.ts @@ -41,6 +41,8 @@ import TipNPCCallbacksSR from "./translations/sr/npc-callbacks-tip"; import StaleTranslationWarningRU from "./translations/ru/stale-translation-warning"; +import StaleTranslationWarningFR from "./translations/fr/stale-translation-warning"; + const templates = { VersionWarn, LowercaseNote, @@ -70,6 +72,7 @@ const templates = { LowercaseNoteSR, TipNPCCallbacksSR, StaleTranslationWarningRU, + StaleTranslationWarningFR, }; export default templates; diff --git a/frontend/src/components/templates/translations/fr/stale-translation-warning.tsx b/frontend/src/components/templates/translations/fr/stale-translation-warning.tsx new file mode 100644 index 00000000000..fab8f939335 --- /dev/null +++ b/frontend/src/components/templates/translations/fr/stale-translation-warning.tsx @@ -0,0 +1,25 @@ +import Admonition from "../../../Admonition"; + +export default function StaleTranslationWarning({ + englishDocLink, +}: { + englishDocLink: string; +}) { + return ( + +

+ La traduction peut être obsolète +

+

+ La version anglaise de ce document a été mise à jour récemment. + Cette traduction peut ne pas encore refléter ces changements. +

+

+ Aidez-nous à garder nos traductions à jour. Si vous maîtrisez cette + langue, consultez la{" "} + version anglaise et mettez à jour cette + traduction. +

+
+ ); +} \ No newline at end of file