230 lines
7.6 KiB
JavaScript
230 lines
7.6 KiB
JavaScript
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));
|
|
};
|