break config up into new modular _config/ folder
This commit is contained in:
parent
7cc92813c6
commit
dd54240a08
5 changed files with 207 additions and 182 deletions
31
_config/date.js
Normal file
31
_config/date.js
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
const { DateTime } = require("luxon");
|
||||
|
||||
module.exports = function (eleventyConfig, { TIME_ZONE }) {
|
||||
eleventyConfig.addDateParsing(function (dateValue) {
|
||||
// i know this is a deranged solution. sorry LOL
|
||||
let localDate;
|
||||
try {
|
||||
if (dateValue instanceof Date && !isNaN(dateValue)) {
|
||||
// handle filename dates (ie 2025-10-18-post.md)
|
||||
localDate = DateTime.fromJSDate(dateValue, { zone: "utc" })
|
||||
.setZone(TIME_ZONE)
|
||||
.startOf("day"); // Set to midnight in America/Santiago
|
||||
} else if (typeof dateValue === "string" && dateValue) {
|
||||
// handle string dates (ie from front matter, if used)
|
||||
localDate = DateTime.fromISO(dateValue, { zone: TIME_ZONE }).startOf("day");
|
||||
} else {
|
||||
// handle invalid input
|
||||
console.warn(`Invalid date value: ${dateValue} for ${this.page.inputPath}`);
|
||||
localDate = DateTime.now().setZone(TIME_ZONE).startOf("day");
|
||||
}
|
||||
if (!localDate || localDate.isValid === false) {
|
||||
throw new Error(`Invalid date value (${dateValue}) for ${this.page.inputPath}: ${localDate?.invalidReason || "Unknown"}`);
|
||||
}
|
||||
return localDate.toJSDate();
|
||||
} catch (error) {
|
||||
console.error(`Date parsing error for ${this.page.inputPath}:`, error.message);
|
||||
// fallback to current date in TIME_ZONE
|
||||
return DateTime.now().setZone(TIME_ZONE).startOf("day").toJSDate();
|
||||
}
|
||||
});
|
||||
};
|
||||
128
_config/filters.js
Normal file
128
_config/filters.js
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
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;
|
||||
});
|
||||
|
||||
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;
|
||||
|
||||
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
|
||||
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" 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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
});
|
||||
|
||||
// turn on disabled nunjucks filter
|
||||
eleventyConfig.addNunjucksFilter("values", obj => Object.values(obj));
|
||||
};
|
||||
31
_config/plugins.js
Normal file
31
_config/plugins.js
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
const { I18nPlugin } = require("@11ty/eleventy");
|
||||
const i18n = require("eleventy-plugin-i18n");
|
||||
const eleventySass = require("eleventy-sass");
|
||||
const toml = require("@iarna/toml");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
module.exports = function (eleventyConfig, { defaultLanguage }) {
|
||||
// load translations
|
||||
const translationsToml = fs.readFileSync(
|
||||
path.join(__dirname, "..", "_data", "locale.toml"),
|
||||
"utf-8"
|
||||
);
|
||||
const translations = toml.parse(translationsToml);
|
||||
|
||||
// plugins
|
||||
eleventyConfig.addPlugin(eleventySass);
|
||||
eleventyConfig.addPlugin(I18nPlugin, {
|
||||
defaultLanguage,
|
||||
errorMode: "allow-fallback",
|
||||
});
|
||||
eleventyConfig.addPlugin(i18n, {
|
||||
translations,
|
||||
fallbackLocales: {
|
||||
"*": "en",
|
||||
}
|
||||
});
|
||||
|
||||
// data extensions
|
||||
eleventyConfig.addDataExtension("toml", (contents) => toml.parse(contents));
|
||||
};
|
||||
7
_config/utils.js
Normal file
7
_config/utils.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
const TIME_ZONE = "America/Santiago";
|
||||
const defaultLanguage = "en";
|
||||
|
||||
module.exports = {
|
||||
TIME_ZONE,
|
||||
defaultLanguage
|
||||
};
|
||||
|
|
@ -1,44 +1,9 @@
|
|||
const { I18nPlugin } = require("@11ty/eleventy");
|
||||
const { DateTime } = require("luxon");
|
||||
|
||||
const i18n = require("eleventy-plugin-i18n");
|
||||
const eleventySass = require("eleventy-sass");
|
||||
const toml = require("@iarna/toml");
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
const TIME_ZONE = "America/Santiago";
|
||||
const { TIME_ZONE, defaultLanguage } = require("./_config/utils");
|
||||
const addDateParsing = require("./_config/date");
|
||||
const addFilters = require("./_config/filters");
|
||||
const addPlugins = require("./_config/plugins");
|
||||
|
||||
module.exports = function (eleventyConfig) {
|
||||
eleventyConfig.addDateParsing(function (dateValue) {
|
||||
// i know this is a deranged solution. sorry LOL
|
||||
let localDate;
|
||||
try {
|
||||
if (dateValue instanceof Date && !isNaN(dateValue)) {
|
||||
// handle filename dates (ie 2025-10-18-post.md)
|
||||
localDate = DateTime.fromJSDate(dateValue, { zone: "utc" })
|
||||
.setZone(TIME_ZONE)
|
||||
.startOf("day"); // Set to midnight in America/Santiago
|
||||
} else if (typeof dateValue === "string" && dateValue) {
|
||||
// handle string dates (ie from front matter, if used)
|
||||
localDate = DateTime.fromISO(dateValue, { zone: TIME_ZONE }).startOf("day");
|
||||
} else {
|
||||
// handle invalid input
|
||||
console.warn(`Invalid date value: ${dateValue} for ${this.page.inputPath}`);
|
||||
localDate = DateTime.now().setZone(TIME_ZONE).startOf("day");
|
||||
}
|
||||
if (!localDate || localDate.isValid === false) {
|
||||
throw new Error(`Invalid date value (${dateValue}) for ${this.page.inputPath}: ${localDate?.invalidReason || "Unknown"}`);
|
||||
}
|
||||
return localDate.toJSDate();
|
||||
} catch (error) {
|
||||
console.error(`Date parsing error for ${this.page.inputPath}:`, error.message);
|
||||
// fallback to current date in TIME_ZONE
|
||||
return DateTime.now().setZone(TIME_ZONE).startOf("day").toJSDate();
|
||||
}
|
||||
});
|
||||
|
||||
eleventyConfig.setLayoutsDirectory("_layouts");
|
||||
|
||||
eleventyConfig.addPassthroughCopy("img");
|
||||
|
|
@ -48,155 +13,18 @@ module.exports = function (eleventyConfig) {
|
|||
eleventyConfig.addPassthroughCopy("robots.txt");
|
||||
eleventyConfig.addPassthroughCopy("roms");
|
||||
|
||||
const defaultLanguage = "en";
|
||||
// expose as global site data for templates
|
||||
// add global site data for templates
|
||||
// TODO: move _config/utils.js and global site data to _data/meta.toml
|
||||
eleventyConfig.addGlobalData("site", {
|
||||
defaultLocale: defaultLanguage
|
||||
});
|
||||
|
||||
eleventyConfig.addNunjucksFilter("values", obj => Object.values(obj));
|
||||
|
||||
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;
|
||||
});
|
||||
|
||||
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;
|
||||
});
|
||||
|
||||
eleventyConfig.addPlugin(eleventySass);
|
||||
eleventyConfig.addPlugin(I18nPlugin, {
|
||||
defaultLanguage: defaultLanguage,
|
||||
errorMode: "allow-fallback", // /en/ -> /
|
||||
});
|
||||
eleventyConfig.addDataExtension("toml", (contents) => toml.parse(contents));
|
||||
|
||||
const translationsToml = fs.readFileSync(
|
||||
path.join(__dirname, "_data", "locale.toml"),
|
||||
"utf-8"
|
||||
);
|
||||
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: {
|
||||
"*": "en",
|
||||
}
|
||||
});
|
||||
addDateParsing(eleventyConfig, { TIME_ZONE });
|
||||
addFilters(eleventyConfig, { TIME_ZONE, defaultLanguage });
|
||||
addPlugins(eleventyConfig, { defaultLanguage });
|
||||
|
||||
return {
|
||||
markdownTemplateEngine: "njk",
|
||||
htmlTemplateEngine: "njk"
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue