You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Is your feature request related to a problem?
A fair share of templates produce user-visible contents and thus it makes sense for the template resolution to be aware of locales, including falling back to more general locales when a certain template is not found.
Describe the solution you'd like
I have modified eta's code but I'm not submitting a PR since I worked on the published package code (just the UMD stuff that I use), instead of its TypeScript sources. I've created a patch that will be applied automatically by my package manager (long live pnpm!), but I seem not to be able to attach it here, so I simply paste it below.
diff --git a/dist/eta.umd.js b/dist/eta.umd.js
index 12d4ee8ce978e46720776fdc9e7e11830e25cba5..3b3943c7ea63290fae29f164fcc448316020e6c0 100644
--- a/dist/eta.umd.js
+++ b/dist/eta.umd.js
@@ -469,32 +469,44 @@ return __eta.res;
}
/* END TYPES */
+ function isFile(dir, filename) {
+ return fs__namespace.statSync(path__namespace.join(dir, filename), { throwIfNoEntry: false })?.isFile();
+ }
function handleCache(template, options) {
const templateStore = options && options.async ? this.templatesAsync : this.templatesSync;
- if (this.resolvePath && this.readFile && !template.startsWith("@")) {
+ let locale;
+ if (options && options.locale) {
+ if (!(options.locale instanceof Intl.Locale)) {
+ options.locale = new Intl.Locale(options.locale); // implicit validation
+ }
+ locale = options.locale;
+ }
+ if (template === null) {
const templatePath = options.filepath;
- const cachedTemplate = templateStore.get(templatePath);
- if (this.config.cache && cachedTemplate) {
- return cachedTemplate;
- } else {
- const templateString = this.readFile(templatePath);
- const templateFn = this.compile(templateString, options);
- if (this.config.cache) templateStore.define(templatePath, templateFn);
- return templateFn;
+ let actualTemplatePath;
+ if (! (locale?.region && isFile(this.config.views, (actualTemplatePath = path__namespace.join(path__namespace.sep, locale.language, locale.region, templatePath)))
+ || locale && isFile(this.config.views, (actualTemplatePath = path__namespace.join(path__namespace.sep, locale.language, templatePath)))
+ || isFile(this.config.views, (actualTemplatePath = templatePath)))) {
+ throw new EtaFileResolutionError(`Could not find template: ${templatePath}`);
}
+ if (this.config.cache) {
+ const cachedTemplate = templateStore.get(actualTemplatePath);
+ if (cachedTemplate != null) return cachedTemplate;
+ }
+ const templateFn = this.compile(this.readFile(actualTemplatePath), options);
+ if (this.config.cache) templateStore.define(actualTemplatePath, templateFn);
+ return templateFn;
} else {
- const cachedTemplate = templateStore.get(template);
- if (cachedTemplate) {
- return cachedTemplate;
- } else {
- throw new EtaNameResolutionError("Failed to get template '" + template + "'");
- }
+ const cachedTemplate = (locale?.region && templateStore.get(`@${locale.language}-${options.locale.region}${template}`))
+ ?? (locale && templateStore.get(`@${locale.language}${template}`))
+ ?? templateStore.get(template);
+ if (cachedTemplate != null) return cachedTemplate;
+ throw new EtaNameResolutionError("Failed to get template '" + template + "'");
}
}
function render(template,
// template name or template function
data, meta) {
- let templateFn;
const options = {
...meta,
async: false
@@ -502,18 +514,16 @@ return __eta.res;
if (typeof template === "string") {
if (this.resolvePath && this.readFile && !template.startsWith("@")) {
options.filepath = this.resolvePath(template, options);
+ template = handleCache.call(this, null, options);
+ } else {
+ template = handleCache.call(this, template, options);
}
- templateFn = handleCache.call(this, template, options);
- } else {
- templateFn = template;
}
- const res = templateFn.call(this, data, options);
- return res;
+ return template.call(this, data, options);
}
function renderAsync(template,
// template name or template function
data, meta) {
- let templateFn;
const options = {
...meta,
async: true
@@ -521,14 +531,13 @@ return __eta.res;
if (typeof template === "string") {
if (this.resolvePath && this.readFile && !template.startsWith("@")) {
options.filepath = this.resolvePath(template, options);
+ template = handleCache.call(this, null, options);
+ } else {
+ template = handleCache.call(this, template, options);
}
- templateFn = handleCache.call(this, template, options);
- } else {
- templateFn = template;
}
- const res = templateFn.call(this, data, options);
// Return a promise
- return Promise.resolve(res);
+ return Promise.resolve(template.call(this, data, options));
}
function renderString(template, data) {
const templateFn = this.compile(template, {
@@ -560,8 +569,8 @@ return __eta.res;
this.templatesSync = new Cacher({});
this.templatesAsync = new Cacher({});
// resolvePath takes a relative path from the "views" directory
- this.resolvePath = null;
- this.readFile = null;
+// this.resolvePath = null;
+// this.readFile = null;
if (customConfig) {
this.config = {
...defaultConfig,
@@ -592,6 +601,12 @@ return __eta.res;
loadTemplate(name, template,
// template string or template function
options) {
+ if (options && options.locale) {
+ if (!(options.locale instanceof Intl.Locale)) {
+ options.locale = new IntlLocale(locale);
+ }
+ name = `@${options.locale.language}${options.locale.region ? `-${options.locale.region}`: ""}${name}`;
+ }
if (typeof template === "string") {
const templates = options && options.async ? this.templatesAsync : this.templatesSync;
templates.define(name, this.compile(template, options));
@@ -607,65 +622,36 @@ return __eta.res;
/* END TYPES */
function readFile(path) {
- let res = "";
- try {
- res = fs__namespace.readFileSync(path, "utf8");
- // eslint-disable-line @typescript-eslint/no-explicit-any
- } catch (err) {
- if ((err == null ? void 0 : err.code) === "ENOENT") {
- throw new EtaFileResolutionError(`Could not find template: ${path}`);
- } else {
- throw err;
- }
- }
- return res;
+ return fs__namespace.readFileSync(path__namespace.join(this.config.views, path), "utf8");
}
function resolvePath(templatePath, options) {
- let resolvedFilePath = "";
- const views = this.config.views;
- if (!views) {
+ let resolvedFilePath;
+ if (!this.config.views) {
throw new EtaFileResolutionError("Views directory is not defined");
}
const baseFilePath = options && options.filepath;
- const defaultExtension = this.config.defaultExtension === undefined ? ".eta" : this.config.defaultExtension;
// how we index cached template paths
- const cacheIndex = JSON.stringify({
- filename: baseFilePath,
- path: templatePath,
- views: this.config.views
+ const cacheIndex = baseFilePath && this.config.cacheFilepaths && JSON.stringify({
+ base: baseFilePath,
+ path: templatePath
});
- templatePath += path__namespace.extname(templatePath) ? "" : defaultExtension;
+ templatePath += path__namespace.extname(templatePath) ? "" : this.config.defaultExtension;
// if the file was included from another template
if (baseFilePath) {
// check the cache
- if (this.config.cacheFilepaths && this.filepathCache[cacheIndex]) {
+ if (cacheIndex && this.filepathCache[cacheIndex]) {
return this.filepathCache[cacheIndex];
}
- const absolutePathTest = absolutePathRegExp.exec(templatePath);
- if (absolutePathTest && absolutePathTest.length) {
- const formattedPath = templatePath.replace(/^\/*|^\\*/, "");
- resolvedFilePath = path__namespace.join(views, formattedPath);
- } else {
- resolvedFilePath = path__namespace.join(path__namespace.dirname(baseFilePath), templatePath);
- }
+ resolvedFilePath = path__namespace.resolve(path__namespace.dirname(baseFilePath), templatePath);
} else {
- resolvedFilePath = path__namespace.join(views, templatePath);
+ resolvedFilePath = path__namespace.resolve(path__namespace.sep, templatePath);
}
- if (dirIsChild(views, resolvedFilePath)) {
- // add resolved path to the cache
- if (baseFilePath && this.config.cacheFilepaths) {
- this.filepathCache[cacheIndex] = resolvedFilePath;
- }
- return resolvedFilePath;
- } else {
- throw new EtaFileResolutionError(`Template '${templatePath}' is not in the views directory`);
+ // add resolved path to the cache
+ if (cacheIndex) {
+ this.filepathCache[cacheIndex] = resolvedFilePath;
}
+ return resolvedFilePath;
}
- function dirIsChild(parent, dir) {
- const relative = path__namespace.relative(parent, dir);
- return relative && !relative.startsWith("..") && !path__namespace.isAbsolute(relative);
- }
- const absolutePathRegExp = /^\\|^\//;
class Eta extends Eta$1 {
constructor(...args) {
Summary of my changes:
the optional locale is expected to be passed within the options argument of the rendering functions, either as a (string) tag or as an instance of Intl.Locale; only the language and the region matter
the code works now under the assumption that the resolved path of a template is further refined when actually retrieving the file's contents (handleCache()), depending on the requested locale, if any; therefore, all resolved paths are absolute paths, as if the views directory was the file system root, and the actual template file must be one of (in this order):
I could not resist making optimizations within all functions that I touched
I also had to eliminate the direct assignment of resolvePath and readFile functions in EtaCore's constructor as that renders useless its extension in the view of overriding those methods in ECMAScript; the Eta constructor does the same silly thing - it sets those methods on the created instance, undermining the potential changes made in its prototype by subclassing
Describe alternatives you've considered
Locale awareness cannot be implemented by simply overriding Eta.readFile() and Eta.resolvePath(), as the cache operations take place in the inaccesible handleCache() function.
The text was updated successfully, but these errors were encountered:
Is your feature request related to a problem?
A fair share of templates produce user-visible contents and thus it makes sense for the template resolution to be aware of locales, including falling back to more general locales when a certain template is not found.
Describe the solution you'd like
I have modified eta's code but I'm not submitting a PR since I worked on the published package code (just the UMD stuff that I use), instead of its TypeScript sources. I've created a patch that will be applied automatically by my package manager (long live pnpm!), but I seem not to be able to attach it here, so I simply paste it below.
Summary of my changes:
options
argument of the rendering functions, either as a (string) tag or as an instance ofIntl.Locale
; only the language and the region matterhandleCache()
), depending on the requested locale, if any; therefore, all resolved paths are absolute paths, as if the views directory was the file system root, and the actual template file must be one of (in this order):resolvePath
andreadFile
functions inEtaCore
's constructor as that renders useless its extension in the view of overriding those methods in ECMAScript; theEta
constructor does the same silly thing - it sets those methods on the created instance, undermining the potential changes made in its prototype by subclassingDescribe alternatives you've considered
Locale awareness cannot be implemented by simply overriding
Eta.readFile()
andEta.resolvePath()
, as the cache operations take place in the inaccesiblehandleCache()
function.The text was updated successfully, but these errors were encountered: