Add page localization (#7128)
* added localized pages; added refactored page templates; adapted test cases; introduced localization test cases * added changelog entry * fixed test description typo * fixed bug in PromiseRouter where headers are not added for text reponse * added page parameters in page headers for programmatic use * refactored tests for PublicAPIRouter * added mustache lib for template rendering * fixed fs.promises module reference * fixed template placeholder typo * changed redirect response to provide headers instead of query parameters * fix lint * fixed syntax errors and typos in html templates * removed obsolete URI encoding * added locale inferring from request body and header * added end-to-end localizaton test * added server option validation; refactored pages server option * fixed invalid redirect URL for no locale matching file * added end-to-end localizaton tests * adapted tests to new response content * re-added PublicAPIRouter; added PagesRouter as experimental feature * refactored PagesRouter test structure * added configuration option for custom path to pages * added configuration option for custom endpoint to pages * fixed lint * added tests * added a distinct page for invalid password reset link * renamed generic page invalidLink to expiredVerificationLink * improved HTML files documentation * improved HTML files documentation * changed changelog entry for experimental feature * improved file naming to make it more descriptive * fixed file naming and env parameter naming * added readme entry * fixed readme TOC - hasn't been updated in a while * added localization with JSON resource * added JSON localization to feature pages (password reset, email verification) * updated readme * updated readme * optimized JSON localization for feature pages; added e2e test case * fixed readme typo * minor refactoring of existing tests * fixed bug where Object type was not recognized as config key type * added feature config placeholders * prettier * added passing locale to page config placeholder callback * refactored passing locale to placeholder to pass test * added config placeholder feature to README * fixed typo in README
This commit is contained in:
123
src/Utils.js
Normal file
123
src/Utils.js
Normal file
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* utils.js
|
||||
* @file General purpose utilities
|
||||
* @description General purpose utilities.
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
const fs = require('fs').promises;
|
||||
|
||||
/**
|
||||
* The general purpose utilities.
|
||||
*/
|
||||
class Utils {
|
||||
/**
|
||||
* @function getLocalizedPath
|
||||
* @description Returns a localized file path accoring to the locale.
|
||||
*
|
||||
* Localized files are searched in subfolders of a given path, e.g.
|
||||
*
|
||||
* root/
|
||||
* ├── base/ // base path to files
|
||||
* │ ├── example.html // default file
|
||||
* │ └── de/ // de language folder
|
||||
* │ │ └── example.html // de localized file
|
||||
* │ └── de-AT/ // de-AT locale folder
|
||||
* │ │ └── example.html // de-AT localized file
|
||||
*
|
||||
* Files are matched with the locale in the following order:
|
||||
* 1. Locale match, e.g. locale `de-AT` matches file in folder `de-AT`.
|
||||
* 2. Language match, e.g. locale `de-AT` matches file in folder `de`.
|
||||
* 3. Default; file in base folder is returned.
|
||||
*
|
||||
* @param {String} defaultPath The absolute file path, which is also
|
||||
* the default path returned if localization is not available.
|
||||
* @param {String} locale The locale.
|
||||
* @returns {Promise<Object>} The object contains:
|
||||
* - `path`: The path to the localized file, or the original path if
|
||||
* localization is not available.
|
||||
* - `subdir`: The subdirectory of the localized file, or undefined if
|
||||
* there is no matching localized file.
|
||||
*/
|
||||
static async getLocalizedPath(defaultPath, locale) {
|
||||
// Get file name and paths
|
||||
const file = path.basename(defaultPath);
|
||||
const basePath = path.dirname(defaultPath);
|
||||
|
||||
// If locale is not set return default file
|
||||
if (!locale) {
|
||||
return { path: defaultPath };
|
||||
}
|
||||
|
||||
// Check file for locale exists
|
||||
const localePath = path.join(basePath, locale, file);
|
||||
const localeFileExists = await Utils.fileExists(localePath);
|
||||
|
||||
// If file for locale exists return file
|
||||
if (localeFileExists) {
|
||||
return { path: localePath, subdir: locale };
|
||||
}
|
||||
|
||||
// Check file for language exists
|
||||
const language = locale.split('-')[0];
|
||||
const languagePath = path.join(basePath, language, file);
|
||||
const languageFileExists = await Utils.fileExists(languagePath);
|
||||
|
||||
// If file for language exists return file
|
||||
if (languageFileExists) {
|
||||
return { path: languagePath, subdir: language };
|
||||
}
|
||||
|
||||
// Return default file
|
||||
return { path: defaultPath };
|
||||
}
|
||||
|
||||
/**
|
||||
* @function fileExists
|
||||
* @description Checks whether a file exists.
|
||||
* @param {String} path The file path.
|
||||
* @returns {Promise<Boolean>} Is true if the file can be accessed, false otherwise.
|
||||
*/
|
||||
static async fileExists(path) {
|
||||
try {
|
||||
await fs.access(path);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @function isPath
|
||||
* @description Evaluates whether a string is a file path (as opposed to a URL for example).
|
||||
* @param {String} s The string to evaluate.
|
||||
* @returns {Boolean} Returns true if the evaluated string is a path.
|
||||
*/
|
||||
static isPath(s) {
|
||||
return /(^\/)|(^\.\/)|(^\.\.\/)/.test(s);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flattens an object and crates new keys with custom delimiters.
|
||||
* @param {Object} obj The object to flatten.
|
||||
* @param {String} [delimiter='.'] The delimiter of the newly generated keys.
|
||||
* @param {Object} result
|
||||
* @returns {Object} The flattened object.
|
||||
**/
|
||||
static flattenObject(obj, parentKey, delimiter = '.', result = {}) {
|
||||
for (const key in obj) {
|
||||
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
||||
const newKey = parentKey ? parentKey + delimiter + key : key;
|
||||
|
||||
if (typeof obj[key] === 'object' && obj[key] !== null) {
|
||||
this.flattenObject(obj[key], newKey, delimiter, result);
|
||||
} else {
|
||||
result[newKey] = obj[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Utils;
|
||||
Reference in New Issue
Block a user