From c29775432dcc1d3ee7f80b36aaa6046a0780b813 Mon Sep 17 00:00:00 2001 From: Ryan-z-Feng-ccsf Date: Wed, 27 May 2026 12:13:38 -0700 Subject: [PATCH 1/2] fix: fallback to English remix metadata for localized examples Co-authored-by: pgzcoa --- src/layouts/ExampleLayout.astro | 16 ++++++++++++---- src/pages/_utils.ts | 34 +++++++++++++++++++++++++++++++++ test/pages/_utils.test.ts | 13 +++++++++++++ 3 files changed, 59 insertions(+), 4 deletions(-) diff --git a/src/layouts/ExampleLayout.astro b/src/layouts/ExampleLayout.astro index d976598af0..895c5ca892 100644 --- a/src/layouts/ExampleLayout.astro +++ b/src/layouts/ExampleLayout.astro @@ -12,6 +12,7 @@ import EditableSketch from "@components/EditableSketch/index.astro"; import RelatedItems from "@components/RelatedItems/index.astro"; import OutdatedTranslationBanner from "@components/OutdatedTranslationBanner/index.astro"; import { checkTranslationBanner } from "../utils/translationBanner"; +import { getFallbackRemixData } from "../pages/_utils"; interface Props { example: CollectionEntry<"examples">; @@ -45,8 +46,15 @@ const relatedReferences = const { Content } = await example.render(); +// Use the fallback function to retrieve English remix data if the current locale doesn't have any +let remixData = (await getFallbackRemixData( + example.id, + currentLocale, + example.data.remix +)) as typeof example.data.remix; + // Extract the collective attribution year. If multiple provided, uses last shown. -const collectivelyAttributedSince = example.data.remix?.reduce( +const collectivelyAttributedSince = remixData?.reduce( (acc: number | null, item) => { if (item.collectivelyAttributedSince) { return item.collectivelyAttributedSince; @@ -57,7 +65,7 @@ const collectivelyAttributedSince = example.data.remix?.reduce( ); // Boolean value on whether the remix history contains links to code -const remixHistoryHasCodeLinks = example.data.remix?.some( +const remixHistoryHasCodeLinks = remixData?.some( (item) => Array.isArray(item.code) && item.code.length > 0 ); @@ -97,7 +105,7 @@ const { showBanner, englishUrl } = checkTranslationBanner( {example.data.title}:{" "} - {example.data.remix?.map((item, i) => { + {remixData?.map((item, i) => { const parts = []; // Each remix entry requires at least one attribution @@ -146,7 +154,7 @@ const { showBanner, englishUrl } = checkTranslationBanner( {remixHistoryHasCodeLinks ? ( <> {t("attribution", "You can find the code history of these examples here")}{": "} - {example.data.remix + {remixData .map(item => item?.code) .flat() .filter(codeItem => codeItem && codeItem.URL) diff --git a/src/pages/_utils.ts b/src/pages/_utils.ts index 186261000d..dcf3590014 100644 --- a/src/pages/_utils.ts +++ b/src/pages/_utils.ts @@ -457,3 +457,37 @@ const getUrl = ( return ""; } }; + + /** + * Retrieves fallback remix (attribution/code history) data from the English example + * if the current localized example is missing it. + * + * @param currentId The id of the current example + * @param currentLocale The current locale string + * @param currentRemixData The remix data from the current locale (if any) + * @returns An array of remix data + */ + export const getFallbackRemixData = async ( + currentId: string, + currentLocale: string, + currentRemixData: any[] | undefined, + ) => { + // Return early if data already exists or if we are already on the English page + if (currentRemixData && currentRemixData.length > 0) { + return currentRemixData; + } + if (currentLocale === "en") { + return currentRemixData; + } + // Main logic + // replace the core path with the English path to find the corresponding English example + // e.g., "zh-Hans/02_Animation_And_Variables/00_Drawing_Lines/description.mdx" + // -> "en/02_Animation_And_Variables/00_Drawing_Lines/description.mdx" + const englishId = currentId.replace(`${currentLocale}/`, "en/"); + const allExamples = await getCollection("examples"); + const englishExample = allExamples.find((e) => e.id === englishId); + if (englishExample?.data.remix && englishExample.data.remix.length > 0) { + return englishExample.data.remix; + } + return currentRemixData; + } \ No newline at end of file diff --git a/test/pages/_utils.test.ts b/test/pages/_utils.test.ts index 16415e8470..4d40d76d70 100644 --- a/test/pages/_utils.test.ts +++ b/test/pages/_utils.test.ts @@ -4,6 +4,7 @@ import { removeContentFileExt, removeLeadingSlash, removeLocaleAndExtension, + getFallbackRemixData, } from "@pages/_utils"; suite("exampleContentSlugToLegacyWebsiteSlug", () => { @@ -50,3 +51,15 @@ suite("removeLocaleAndExtensionFromId", () => { ); }); }); + +suite("getFallbackRemixData", () => { + test("returns remix data for English example when current locale example has no remix data", () => { + expect( + getFallbackRemixData( + "zh-Hans/02_Animation_And_Variables/00_Drawing_Lines/description.mdx", + "zh-Hans", + undefined + ) + ).toBeDefined(); + }); +}); \ No newline at end of file From 5e712906cb693c0bc8ba8a71752806d9a46c7863 Mon Sep 17 00:00:00 2001 From: Ryan Date: Wed, 27 May 2026 14:04:25 -0700 Subject: [PATCH 2/2] Clarify translation process for Example pages Added note about translating Example pages and remix metadata. --- docs/localization.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/localization.md b/docs/localization.md index 4b1abbda99..2434522f2f 100644 --- a/docs/localization.md +++ b/docs/localization.md @@ -18,6 +18,9 @@ Each file within these collection folders corresponds to a real page on the rend The ["ui" content collection](src/content/ui/) is a little different than the others. It contains yaml files that cover strings that are used across different pages for things like the navigation bar. +**Note on Translating Examples:** +When translating Example pages (`.mdx`), you do not need to copy or include the `remix` metadata block in the frontmatter. The website will automatically fall back to the English version to fetch the remix data. + ## Routes and Layouts Astro uses a file-based approach to generating routes. The filepath of each file in `src/pages` becomes the url of the page it renders. Because we need to support a url scheme where English translations of pages are served at a URL with no locale prefix (for example, the English version of the tutorials page is at https://p5.js/tutorials _not_ https://p5.js/en/tutorials), there are 2 sets of routing files and folders: one in `src/pages` and another `src/pages/[locale]`. The `[locale]` set are needed to build and serve the non-English pages (whose URLs are also prefixed by the language codes).