diff --git a/.github/workflows/translation-sync.yml b/.github/workflows/translation-sync.yml new file mode 100644 index 0000000000..88ebe98b58 --- /dev/null +++ b/.github/workflows/translation-sync.yml @@ -0,0 +1,42 @@ +name: Translation Sync Tracker + +on: + push: + branches: [main] + paths: + - 'src/content/tutorials/en/**' + pull_request: + branches: [main] + paths: + - 'src/content/tutorials/en/**' + workflow_dispatch: + +permissions: + contents: write + +jobs: + track_translations: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: 24 + + - name: Run translation tracker + uses: ./.github/actions/translation-tracker + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Commit updated manifest + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add public/translation/status/ + git diff --staged --quiet || git commit -m "chore: update translation status manifest" + git push \ No newline at end of file diff --git a/public/translation-status/tutorials.json b/public/translation-status/tutorials.json new file mode 100644 index 0000000000..3f0da100e9 --- /dev/null +++ b/public/translation-status/tutorials.json @@ -0,0 +1,7 @@ +{ + "tutorials": { + "variables-and-change": { + "outdated": ["hi"] + } + } +} \ No newline at end of file diff --git a/src/components/OutdatedTranslationBanner/index.astro b/src/components/OutdatedTranslationBanner/index.astro new file mode 100644 index 0000000000..79ac6a55bc --- /dev/null +++ b/src/components/OutdatedTranslationBanner/index.astro @@ -0,0 +1,85 @@ +--- +interface Props { + englishUrl: string; + locale: string; +} + +const { englishUrl, locale } = Astro.props; + +const copyByLocale: Record = { + es: { + title: 'Esta traducción podría estar desactualizada', + message: 'Esta página no está actualizada en comparación con la versión en inglés.', + viewEnglish: 'Ver versión en inglés', + contribute: 'Contribuir a la traducción', + }, + hi: { + title: 'यह अनुवाद पुराना हो सकता है', + message: 'यह पृष्ठ अंग्रेज़ी संस्करण की तुलना में अद्यतन नहीं है।', + viewEnglish: 'अंग्रेज़ी संस्करण देखें', + contribute: 'अनुवाद में योगदान दें', + }, + ko: { + title: '이 번역은 오래되었을 수 있습니다', + message: '이 페이지는 영어 버전과 비교하여 최신 상태가 아닙니다.', + viewEnglish: '영어 페이지 보기', + contribute: '번역에 기여하기', + }, + 'zh-Hans': { + title: '此翻译可能已过期', + message: '与英文版本相比,此页面不是最新的。', + viewEnglish: '查看英文页面', + contribute: '参与翻译', + }, +}; + +const fallback = { + title: 'This translation might be outdated', + message: 'This page is not up to date compared to the English version.', + viewEnglish: 'View English page', + contribute: 'Contribute to translation', +}; + +const copy = copyByLocale[locale] ?? fallback; + +const contributeUrl = + 'https://github.com/processing/p5.js-website/tree/main?tab=readme-ov-file#content-changes'; +--- + +
+ +
+

{copy.title}

+

+ {copy.message} + {copy.viewEnglish} + + {copy.contribute} + +

+
+
+ + \ No newline at end of file diff --git a/src/layouts/TutorialLayout.astro b/src/layouts/TutorialLayout.astro index 0196172cfb..00f868355e 100644 --- a/src/layouts/TutorialLayout.astro +++ b/src/layouts/TutorialLayout.astro @@ -3,10 +3,12 @@ import { render } from 'astro:content'; import BaseLayout from "@layouts/BaseLayout.astro"; import Head from "@components/Head/index.astro"; import { getCurrentLocale, getUiTranslator } from "../i18n/utils"; +import OutdatedTranslationBanner from "@components/OutdatedTranslationBanner/index.astro"; import FreeRatioImage from "@components/Image/FreeRatioImage.astro"; import PreProxy from "@components/PreProxy/index.astro"; import LinkWrapper from "@components/LinkWrapper/index.astro"; import RelatedItems from "@components/RelatedItems/index.astro"; +import { checkTranslationBanner } from "../utils/translationBanner"; import { generateJumpToState, getRelatedEntriesinCollection, @@ -17,6 +19,9 @@ const { Content } = await render(entry); const currentLocale = getCurrentLocale(Astro.url.pathname); const t = await getUiTranslator(currentLocale); +const { showBanner, englishUrl } = await checkTranslationBanner( + 'tutorials', entry.id, currentLocale, Astro.url.pathname, Astro.url.origin +); const jumpToState = await generateJumpToState( "tutorials", @@ -60,6 +65,7 @@ const relatedExamples = className="tutorials" jumpToState={jumpToState} > + {showBanner && } {entry.data.authors &&
By {entry.data.authors?.join(", ")}
} {entry.data.authorsNote && {entry.data.authorsNote}}
diff --git a/src/utils/translationBanner.ts b/src/utils/translationBanner.ts new file mode 100644 index 0000000000..17929234d6 --- /dev/null +++ b/src/utils/translationBanner.ts @@ -0,0 +1,59 @@ +import fs from 'fs/promises'; +import { existsSync } from 'fs'; +import path from 'path' + +interface BannerCheckResult { + showBanner: boolean; + englishUrl: string; +} + +const manifestCache = new Map(); + +export async function checkTranslationBanner( + contentType: string, + itemId: string, + currentLocale: string, + currentPathname: string, + origin: string +): Promise { + + if (currentLocale === 'en') { + return { showBanner: false, englishUrl: currentPathname }; + } + + const englishPath = currentPathname.replace(`${currentLocale}/`, '/'); + const englishUrl = `${origin}${englishPath}`; + + try { + const manifestPath = path.join( + process.cwd(), `public`, `translation-status`, `${contentType}.json` + ); + + let manifest = manifestCache.get(manifestPath); + + if (!manifest) { + if (!existsSync(manifestPath)) { + return { showBanner: false, englishUrl }; + } + + const fileContent = await fs.readFile(manifestPath, 'utf8'); + manifest = JSON.parse(fileContent); + manifestCache.set(manifestPath, manifest); + } + + const idNoLocale = itemId.replace(/^[\w-]+\//, ''); + const key = idNoLocale.replace(/\.(mdx?|ya?ml)$/, ''); + + const entry = manifest[contentType]?.[key]; + + if (entry) { + const isOutdated = Array.isArray(entry.outdated) && entry.outdated.includes(currentLocale); + const isMissing = Array.isArray(entry.missing) && entry.missing.includes(currentLocale); + return { showBanner: isOutdated || isMissing, englishUrl }; + } + } catch (error) { + console.error('Error checking translation banner:', error); + } + + return { showBanner: false, englishUrl }; +} \ No newline at end of file