diff --git a/eleventy.config.js b/eleventy.config.js index 69513a5..1d1d2a3 100644 --- a/eleventy.config.js +++ b/eleventy.config.js @@ -1,4 +1,6 @@ -const { I18nPlugin } = require("@11ty/eleventy"); +const { + I18nPlugin +} = require("@11ty/eleventy"); const i18n = require("eleventy-plugin-i18n"); const eleventySass = require("eleventy-sass"); const toml = require("@iarna/toml"); @@ -6,9 +8,9 @@ const toml = require("@iarna/toml"); const fs = require("fs"); const path = require("path"); -module.exports = function(eleventyConfig) { +module.exports = function (eleventyConfig) { eleventyConfig.setLayoutsDirectory("_layouts"); - + eleventyConfig.addPassthroughCopy("img"); eleventyConfig.addPassthroughCopy("css/fonts"); eleventyConfig.addPassthroughCopy("js"); @@ -16,9 +18,15 @@ module.exports = function(eleventyConfig) { eleventyConfig.addPassthroughCopy("robots.txt"); eleventyConfig.addPassthroughCopy("roms"); + const defaultLanguage = "en"; + // expose as global site data for templates + eleventyConfig.addGlobalData("site", { + defaultLocale: defaultLanguage + }); + eleventyConfig.addNunjucksFilter("values", obj => Object.values(obj)); - eleventyConfig.addFilter("i18n_filter", function(collection, limit = null) { + eleventyConfig.addFilter("i18n_filter", function (collection, limit = null) { const lang = this.page.lang; // access page.lang from context let filtered = collection.filter(item => item.data.lang === lang); if (limit !== null) { @@ -29,8 +37,8 @@ module.exports = function(eleventyConfig) { eleventyConfig.addPlugin(eleventySass); eleventyConfig.addPlugin(I18nPlugin, { - defaultLanguage: "en", - errorMode: "allow-fallback" // /en/ -> / + defaultLanguage: defaultLanguage, + errorMode: "allow-fallback", // /en/ -> / }); eleventyConfig.addDataExtension("toml", (contents) => toml.parse(contents)); @@ -40,6 +48,94 @@ module.exports = function(eleventyConfig) { ); const translations = toml.parse(translationsToml); + const LOCALE_URL_DEBUG = process.env.LOCALE_URL_DEBUG === "1" || process.env.LOCALE_URL_DEBUG === "true"; + + eleventyConfig.addNunjucksFilter("locale_url_resolve", function (targetUrl, desiredLocale) { + const ctx = (this && this.ctx) ? this.ctx : {}; + const collections = (ctx.collections) ? ctx.collections : {}; + const all = collections.all || []; + + if (!targetUrl || typeof targetUrl !== "string") return targetUrl; + if (!targetUrl.startsWith("/")) return targetUrl; // external or relative link -> leave as is + + // determine locale to resolve to + const pageLang = desiredLocale || (ctx.page && ctx.page.lang) || ctx.locale || defaultLanguage; + + if (LOCALE_URL_DEBUG) { + console.warn(`[locale_url_resolve] resolving targetUrl="${targetUrl}" desiredLocale="${desiredLocale}" pageLang="${pageLang}"`); + } + + // if requested locale is default, return the raw url (your default publishes to root) + if (pageLang === defaultLanguage) { + if (LOCALE_URL_DEBUG) console.warn(`[locale_url_resolve] requested locale is default (${defaultLanguage}) — returning raw URL "${targetUrl}"`); + return targetUrl; + } + + // normalize targetUrl to ensure trailing slash for comparison + const normUrl = targetUrl.endsWith("/") ? targetUrl : (targetUrl + "/"); + + // try to find the canonical (default language) page corresponding to targetUrl + let canonical = all.find(p => { + return (p.url === normUrl || p.url === targetUrl) && p.data && p.data.lang === defaultLanguage; + }); + + // if not found, try to find any page with that url (maybe targetUrl already localized) + if (!canonical) { + canonical = all.find(p => (p.url === normUrl || p.url === targetUrl)); + } + + if (LOCALE_URL_DEBUG) { + if (canonical) { + const cs = (canonical.page && canonical.page.filePathStem) ? canonical.page.filePathStem : "(no stem)"; + const clang = (canonical.data && canonical.data.lang) ? canonical.data.lang : "(no lang)"; + console.warn(`[locale_url_resolve] canonical found: url="${canonical.url}" filePathStem="${cs}" lang="${clang}"`); + } else { + console.warn(`[locale_url_resolve] canonical NOT found for targetUrl="${targetUrl}". Will fallback to prefixed URL.`); + } + } + + // if cannot find canonical page, fall back to a prefixed URL (best effort) + if (!canonical) { + const fallback = (`/${pageLang}${targetUrl}`).replace(/\/{2,}/g, "/"); + if (LOCALE_URL_DEBUG) console.warn(`[locale_url_resolve] fallback -> "${fallback}"`); + return fallback; + } + + // determine canonical filePathStem (the source input path without extension) + const canonicalLang = canonical.data && canonical.data.lang ? canonical.data.lang : defaultLanguage; + const canonicalStem = (canonical.page && canonical.page.filePathStem) ? canonical.page.filePathStem : ""; + + // remove the canonical lang prefix from the stem to create a "key" we can match across locales. + // e.g. "/en/about" -> "/about", "/es/about" -> "/about" + const key = canonicalStem.replace(new RegExp(`^/${canonicalLang}`), "").replace(/^\/+/, ""); + + if (LOCALE_URL_DEBUG) { + console.warn(`[locale_url_resolve] canonicalLang="${canonicalLang}" canonicalStem="${canonicalStem}" key="${key}"`); + } + + // find the localized page whose filePathStem ends with that key and whose lang matches pageLang. + const localized = all.find(p => { + const pLang = p.data && p.data.lang; + const pStem = (p.page && p.page.filePathStem) ? p.page.filePathStem : ""; + // be defensive: ensure pLang exists + if (!pLang) return false; + return pLang === pageLang && pStem.endsWith(key); + }); + + if (localized && localized.url) { + if (LOCALE_URL_DEBUG) { + const ls = (localized.page && localized.page.filePathStem) ? localized.page.filePathStem : "(no stem)"; + console.warn(`[locale_url_resolve] localized found: url="${localized.url}" filePathStem="${ls}" lang="${localized.data.lang}"`); + } + return localized.url; + } + + // no localized page found — fall back to prefixed path + const fallback2 = (`/${pageLang}${targetUrl}`).replace(/\/{2,}/g, "/"); + if (LOCALE_URL_DEBUG) console.warn(`[locale_url_resolve] localized NOT found for key="${key}" — fallback -> "${fallback2}"`); + return fallback2; + }); + eleventyConfig.addPlugin(i18n, { translations, fallbackLocales: {