const { DateTime } = require("luxon"); module.exports = function (eleventyConfig, { TIME_ZONE, defaultLanguage }) { // {{ post.date | date("dd/MM/yyyy") }} -> 18/10/2025 eleventyConfig.addFilter("date", function (dateObj, format = "dd/MM/yyyy") { let dt = dateObj; // handle string dates if (typeof dateObj === "string") { dt = DateTime.fromISO(dateObj, { zone: TIME_ZONE }).toJSDate(); } // handle DateTime objects (from addDateParsing) if (dateObj instanceof DateTime) { dt = dateObj.toJSDate(); } // check dt as valid Date object if (!(dt instanceof Date) || isNaN(dt)) { console.log("Invalid date input:", dateObj); return ""; } // format in TIME_ZONE const formatted = DateTime.fromJSDate(dt, { zone: TIME_ZONE }).toFormat( format, ); console.log( "Date input:", dt, "Formatted:", formatted, "Timezone:", TIME_ZONE, ); return formatted; }); // filters collections by current language 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) { filtered = filtered.slice(0, limit); } return filtered; }); // takes all collections and returns only the tags not matched // key-objects so may repurpose under different name eleventyConfig.addFilter( "exclude_collections", function (collections, ...keysToExclude) { if (!collections || typeof collections !== "object") { console.warn( "[exclude_collections] Invalid collections input:", collections, ); return collections; } const result = {}; Object.keys(collections).forEach((key) => { if (!keysToExclude.includes(key)) { result[key] = collections[key]; } }); if ( process.env.LOCALE_URL_DEBUG === "1" || process.env.LOCALE_URL_DEBUG === "true" ) { console.warn("[excludeCollections] Excluded keys:", keysToExclude); console.warn( "[excludeCollections] Resulting keys:", Object.keys(result), ); } return result; }, ); const LOCALE_URL_DEBUG = process.env.LOCALE_URL_DEBUG === "1" || process.env.LOCALE_URL_DEBUG === "true"; // locale_url replacement that uses pre-compile filesystem 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; const LOCALE_URL_DEBUG = process.env.LOCALE_URL_DEBUG === "1" || process.env.LOCALE_URL_DEBUG === "true"; if (LOCALE_URL_DEBUG) { console.warn( `[locale_url_resolve] resolving targetUrl="${targetUrl}" desiredLocale="${desiredLocale}" pageLang="${pageLang}"`, ); } // special case for homepage if (targetUrl === "/" || targetUrl === "/index/") { if (pageLang === defaultLanguage) { if (LOCALE_URL_DEBUG) console.warn( `[locale_url_resolve] homepage, default language (${defaultLanguage}) -> "/"`, ); return "/"; } const homepageUrl = `/${pageLang}/`; if (LOCALE_URL_DEBUG) console.warn( `[locale_url_resolve] homepage, language (${pageLang}) -> "${homepageUrl}"`, ); return homepageUrl; } // normalize targetUrl to include 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" to match across locales. // ie "/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; } // fallback to prefixed URL 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; }, ); // turn on disabled nunjucks filters eleventyConfig.addFilter("keys", (obj) => Object.keys(obj)); eleventyConfig.addFilter("values", (obj) => Object.values(obj)); };