Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions .github/workflows/translation-sync.yml
Original file line number Diff line number Diff line change
@@ -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:
Comment on lines +3 to +12

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 }}
Comment on lines +31 to +34

- 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
7 changes: 7 additions & 0 deletions public/translation-status/tutorials.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"tutorials": {
"variables-and-change": {
"outdated": ["hi"]
}
}
}
85 changes: 85 additions & 0 deletions src/components/OutdatedTranslationBanner/index.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
---
interface Props {
englishUrl: string;
locale: string;
}

const { englishUrl, locale } = Astro.props;

const copyByLocale: Record<string, {
title: string;
message: string;
viewEnglish: string;
contribute: string;
}> = {
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';
---

<section class="outdated-banner" role="status" aria-live="polite">
<div class="icon" aria-hidden="true">⚠️</div>
<div class="content">
<p class="title">{copy.title}</p>
<p class="desc">
{copy.message}
<a class="link" href={englishUrl}>{copy.viewEnglish}</a>
<a class="link" href={contributeUrl} target="_blank" rel="noopener noreferrer">
{copy.contribute}
</a>
</p>
</div>
</section>

<style>
.outdated-banner {
display: flex;
align-items: flex-start;
gap: 16px;
padding: 16px 20px;
border-radius: 12px;
background: #d8f532;
color: #000;
margin: 16px 0 24px;
}
.icon { font-size: 22px; line-height: 1; margin-top: 2px; }
.content { flex: 1; }
.title { font-weight: 700; font-size: 16px; margin-bottom: 6px; }
.desc { margin: 0; font-size: 14px; }
.link { color: inherit; font-weight: 700; text-underline-offset: 2px; }
.link:hover { text-decoration: underline}
</style>
6 changes: 6 additions & 0 deletions src/layouts/TutorialLayout.astro
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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",
Expand Down Expand Up @@ -60,6 +65,7 @@ const relatedExamples =
className="tutorials"
jumpToState={jumpToState}
>
{showBanner && <OutdatedTranslationBanner englishUrl={englishUrl} locale={currentLocale} />}
{entry.data.authors && <section role="group" aria-label="authors">By {entry.data.authors?.join(", ")}</section>}
{entry.data.authorsNote && <h7>{entry.data.authorsNote}</h7>}
<div class="rendered-markdown">
Expand Down
59 changes: 59 additions & 0 deletions src/utils/translationBanner.ts
Original file line number Diff line number Diff line change
@@ -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<string, any>();

export async function checkTranslationBanner(
contentType: string,
itemId: string,
currentLocale: string,
currentPathname: string,
origin: string
): Promise<BannerCheckResult> {

if (currentLocale === 'en') {
return { showBanner: false, englishUrl: currentPathname };
}

const englishPath = currentPathname.replace(`${currentLocale}/`, '/');
const englishUrl = `${origin}${englishPath}`;
Comment on lines +24 to +25

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 };
}