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). diff --git a/src/layouts/ExampleLayout.astro b/src/layouts/ExampleLayout.astro index 6d343ee72e..dd21d8a586 100644 --- a/src/layouts/ExampleLayout.astro +++ b/src/layouts/ExampleLayout.astro @@ -13,6 +13,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">; @@ -46,8 +47,15 @@ const relatedReferences = const { Content } = await render(example); +// 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; @@ -58,7 +66,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 ); @@ -98,7 +106,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 @@ -147,7 +155,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 7e1f67f815..61776eaaa0 100644 --- a/src/pages/_utils.ts +++ b/src/pages/_utils.ts @@ -455,3 +455,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