926 lines
No EOL
31 KiB
JavaScript
926 lines
No EOL
31 KiB
JavaScript
"use strict";
|
|
// Copyright IBM Corp. 2018,2020. All Rights Reserved.
|
|
// Node module: strong-globalize
|
|
// This file is licensed under the Artistic License 2.0.
|
|
// License text available at https://opensource.org/licenses/Artistic-2.0
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.getLangAlias = exports.removeDoubleCurlyBraces = exports.loadMsgFromFile = exports.myIntlDir = exports.getLanguageFromRequest = exports.repackArgs = exports.mapArgs = exports.mapPercent = exports.percent = exports.intlDir = exports.headerIncluded = exports.getPackageItem = exports.getPackageVersion = exports.getPackageName = exports.getTrailerAfterDot = exports.getAppLanguages = exports.getSupportedLanguages = exports.isSupportedLanguage = exports.initIntlDirs = exports.sortMsges = exports.scanJsonPriv = exports.replaceJson = exports.scanJson = exports.normalizeKeyArrays = exports.readToJson = exports.resolveDependencies = exports.unsymbolLink = exports.requireResolve = exports.maxDirectoryDepth = exports.directoryDepth = exports.removeObsoleteFile = exports.enumerateMsgSyncPriv = exports.enumerateMsgSync = exports.cloneEnglishTxtSyncDeep = exports.enumerateLanguageSync = exports.enumerateFilesSyncPriv = exports.alreadyScanned = exports.enumerateFilesSync = exports.stripBom = exports.resTagExists = exports.registerResTag = exports.msgFileIdHash = exports.validateAmlValue = exports.isLoadMessages = exports.initGlobForSltGlobalize = exports.isRootPackage = exports.getRootDir = exports.setRootDir = exports.hashKeys = exports.MSG_GPB_UNAVAILABLE = exports.BIG_NUM = exports.AML_DEFAULT = exports.AML_NONE = exports.AML_ALL = exports.HELPTXT_TAG = exports.MSG_TAG = exports.PSEUDO_TAG = exports.PSEUDO_LANG = exports.ENGLISH = void 0;
|
|
const dbg = require("debug");
|
|
const debug = dbg('strong-globalize');
|
|
const acceptLanguage = require("accept-language");
|
|
const assert = require("assert");
|
|
const fs = require("fs");
|
|
const _ = require("lodash");
|
|
const md5 = require("md5");
|
|
const mkdirp = require("mkdirp");
|
|
const path = require("path");
|
|
const config_1 = require("./config");
|
|
exports.ENGLISH = 'en';
|
|
exports.PSEUDO_LANG = 'zz';
|
|
exports.PSEUDO_TAG = '♚♛♜♝♞♟';
|
|
exports.MSG_TAG = 'message';
|
|
exports.HELPTXT_TAG = 'helptxt';
|
|
exports.AML_ALL = 'all';
|
|
exports.AML_NONE = 'none';
|
|
exports.AML_DEFAULT = exports.AML_NONE;
|
|
exports.BIG_NUM = 999999999999;
|
|
exports.MSG_GPB_UNAVAILABLE = '*** Login to GPB failed or GPB.supportedTranslations error.';
|
|
const HASH_KEYS = false;
|
|
const KEY_HEADERS = ['msg'];
|
|
function hashKeys(p) {
|
|
let trailer = null;
|
|
return !(headerIncluded(p, KEY_HEADERS) ||
|
|
(trailer = getTrailerAfterDot(p)) === 'txt' ||
|
|
trailer === 'json' ||
|
|
trailer === 'yml' ||
|
|
trailer === 'yaml' ||
|
|
p.indexOf(exports.PSEUDO_TAG) === 0);
|
|
}
|
|
exports.hashKeys = hashKeys;
|
|
// tslint:disable:no-any
|
|
/**
|
|
* Supported languages in CLDR notation
|
|
*/
|
|
let TARGET_LANGS = null;
|
|
let MY_ROOT = process.cwd();
|
|
let INTL_DIR = path.join(MY_ROOT, 'intl');
|
|
/**
|
|
* @param {string} Override the root directory path
|
|
*/
|
|
function setRootDir(rootPath) {
|
|
let validPath = true;
|
|
let rootStats = undefined;
|
|
try {
|
|
rootStats = fs.statSync(rootPath);
|
|
}
|
|
catch (e) {
|
|
validPath = false;
|
|
}
|
|
assert(validPath, '*** setRootDir: Root path invalid: ' + rootPath);
|
|
if (!rootStats.isDirectory())
|
|
validPath = false;
|
|
assert(validPath, '*** setRootDir: Root path is not a directory: ' + rootPath.toString());
|
|
let files = undefined;
|
|
try {
|
|
files = fs.readdirSync(rootPath);
|
|
}
|
|
catch (e) {
|
|
validPath = false;
|
|
}
|
|
validPath = validPath && !!files;
|
|
if (validPath) {
|
|
let intlDirFound = false;
|
|
files.forEach(function (item) {
|
|
if (intlDirFound)
|
|
return;
|
|
if (item === 'intl')
|
|
intlDirFound = true;
|
|
});
|
|
validPath = intlDirFound;
|
|
}
|
|
assert(validPath, '*** setRootDir: Intl dir not found under: ' + rootPath.toString());
|
|
MY_ROOT = rootPath;
|
|
INTL_DIR = path.join(MY_ROOT, 'intl');
|
|
}
|
|
exports.setRootDir = setRootDir;
|
|
function getRootDir() {
|
|
return MY_ROOT;
|
|
}
|
|
exports.getRootDir = getRootDir;
|
|
function isRootPackage() {
|
|
return MY_ROOT === config_1.STRONGLOOP_GLB.MASTER_ROOT_DIR;
|
|
}
|
|
exports.isRootPackage = isRootPackage;
|
|
function initGlobForSltGlobalize(rootDir) {
|
|
if (config_1.STRONGLOOP_GLB.MASTER_ROOT_DIR)
|
|
return;
|
|
Object.assign(config_1.STRONGLOOP_GLB, {
|
|
MASTER_ROOT_DIR: rootDir || getRootDir(),
|
|
MSG_RES_LOADED: [],
|
|
});
|
|
}
|
|
exports.initGlobForSltGlobalize = initGlobForSltGlobalize;
|
|
function isLoadMessages(rootDir) {
|
|
if (!config_1.STRONGLOOP_GLB.MASTER_ROOT_DIR)
|
|
return false;
|
|
if (path.resolve(rootDir) === path.resolve(config_1.STRONGLOOP_GLB.MASTER_ROOT_DIR))
|
|
return true;
|
|
if (!config_1.STRONGLOOP_GLB.AUTO_MSG_LOADING)
|
|
return false;
|
|
if (config_1.STRONGLOOP_GLB.AUTO_MSG_LOADING === exports.AML_NONE)
|
|
return false;
|
|
if (config_1.STRONGLOOP_GLB.AUTO_MSG_LOADING === exports.AML_ALL)
|
|
return true;
|
|
const packagesToLoad = config_1.STRONGLOOP_GLB.AUTO_MSG_LOADING;
|
|
const packageName = getPackageName(rootDir);
|
|
const load = packagesToLoad.indexOf(packageName) >= 0;
|
|
return load;
|
|
}
|
|
exports.isLoadMessages = isLoadMessages;
|
|
function validateAmlValue(aml) {
|
|
if (aml === exports.AML_ALL || aml === exports.AML_NONE)
|
|
return aml;
|
|
if (Array.isArray(aml)) {
|
|
if (aml.length === 0)
|
|
return false;
|
|
aml.forEach(function (v) {
|
|
if (typeof aml !== 'string')
|
|
return false;
|
|
});
|
|
return aml;
|
|
}
|
|
return false;
|
|
}
|
|
exports.validateAmlValue = validateAmlValue;
|
|
function msgFileIdHash(fileName, rootDir) {
|
|
assert(fileName);
|
|
rootDir = rootDir || getRootDir();
|
|
const packageName = getPackageName(rootDir);
|
|
const packageVersion = getPackageVersion(rootDir);
|
|
const msgFileId = fileName + packageName + packageVersion;
|
|
return md5(msgFileId);
|
|
}
|
|
exports.msgFileIdHash = msgFileIdHash;
|
|
function registerResTag(fileIdHash, fileName, lang, tagType) {
|
|
assert(config_1.STRONGLOOP_GLB);
|
|
assert(fileIdHash);
|
|
assert(fileName);
|
|
assert(lang);
|
|
assert(tagType);
|
|
if (resTagExists(fileIdHash, fileName, lang, tagType))
|
|
return false;
|
|
const resTag = {
|
|
fileIdHash: fileIdHash,
|
|
fileName: fileName,
|
|
lang: lang,
|
|
tagType: tagType,
|
|
};
|
|
config_1.STRONGLOOP_GLB.MSG_RES_LOADED.push(resTag);
|
|
return true;
|
|
}
|
|
exports.registerResTag = registerResTag;
|
|
function resTagExists(fileIdHash, fileName, lang, tagType) {
|
|
assert(config_1.STRONGLOOP_GLB);
|
|
assert(fileIdHash);
|
|
assert(fileName);
|
|
assert(lang);
|
|
assert(tagType);
|
|
const resTag = {
|
|
fileIdHash: fileIdHash,
|
|
lang: lang,
|
|
tagType: tagType,
|
|
};
|
|
const exists = _.find(config_1.STRONGLOOP_GLB.MSG_RES_LOADED, resTag) !== undefined;
|
|
return exists;
|
|
}
|
|
exports.resTagExists = resTagExists;
|
|
function stripBom(str) {
|
|
return str.charCodeAt(0) === 0xfeff ? str.slice(1) : str;
|
|
}
|
|
exports.stripBom = stripBom;
|
|
/**
|
|
* Enumerate all JS files in this application
|
|
* @param {Function}
|
|
* param.content is a UTF8 string of each JS source file.
|
|
*/
|
|
const showDotCount = 500;
|
|
const showCountCount = 10000;
|
|
let enumeratedFilesCount = 0;
|
|
let scannedFileNameHash = null;
|
|
function enumerateFilesSync(rootDir, blackList, targetFileType, verbose, checkNodeModules, callback) {
|
|
enumeratedFilesCount = 0;
|
|
scannedFileNameHash = [];
|
|
return enumerateFilesSyncPriv(rootDir, rootDir, blackList, targetFileType, verbose, checkNodeModules, callback);
|
|
}
|
|
exports.enumerateFilesSync = enumerateFilesSync;
|
|
function alreadyScanned(fileName) {
|
|
const realFileName = process.browser ? fileName : fs.realpathSync(fileName);
|
|
const fileNameHash = md5(realFileName);
|
|
if (scannedFileNameHash.indexOf(fileNameHash) >= 0) {
|
|
return true;
|
|
}
|
|
else {
|
|
scannedFileNameHash.push(fileNameHash);
|
|
return false;
|
|
}
|
|
}
|
|
exports.alreadyScanned = alreadyScanned;
|
|
function enumerateFilesSyncPriv(currentPath, rootDir, blackList, targetFileType, verbose, checkNodeModules, callback) {
|
|
if (!currentPath)
|
|
currentPath = MY_ROOT;
|
|
if (!rootDir)
|
|
rootDir = MY_ROOT;
|
|
currentPath = path.resolve(currentPath);
|
|
if (alreadyScanned(currentPath))
|
|
return;
|
|
rootDir = path.resolve(rootDir);
|
|
blackList = Array.isArray(blackList) ? blackList : [];
|
|
if (!Array.isArray(targetFileType))
|
|
targetFileType = [targetFileType];
|
|
let skipDir = false;
|
|
blackList.forEach(function (part) {
|
|
if (typeof part !== 'string')
|
|
return;
|
|
if (currentPath.indexOf(part) >= 0)
|
|
skipDir = true;
|
|
});
|
|
if (skipDir) {
|
|
if (verbose)
|
|
console.log('*** skipping directory:', currentPath);
|
|
return;
|
|
}
|
|
let files = null;
|
|
try {
|
|
files = fs.readdirSync(currentPath);
|
|
}
|
|
catch (e) {
|
|
return;
|
|
}
|
|
files.forEach(function (item) {
|
|
if (item.indexOf('.') === 0)
|
|
return;
|
|
const child = path.join(currentPath, item);
|
|
let stats = null;
|
|
try {
|
|
stats = fs.statSync(child);
|
|
}
|
|
catch (e) {
|
|
return;
|
|
}
|
|
if (stats.isDirectory()) {
|
|
item = item.toLowerCase();
|
|
if (item === 'test' || item === 'node_modules' || item === 'coverage')
|
|
return;
|
|
enumerateFilesSyncPriv(child, rootDir, blackList, targetFileType, verbose, checkNodeModules, callback);
|
|
}
|
|
else {
|
|
const fileType = getTrailerAfterDot(item);
|
|
if (!fileType || targetFileType.indexOf(fileType) < 0)
|
|
return;
|
|
const content = stripBom(fs.readFileSync(child, 'utf8'));
|
|
if (verbose)
|
|
console.log('~~~ examining file:', child);
|
|
if (checkNodeModules) {
|
|
enumeratedFilesCount++;
|
|
if (enumeratedFilesCount % showDotCount === 0) {
|
|
process.stdout.write('.');
|
|
if (enumeratedFilesCount % showCountCount === 0) {
|
|
process.stdout.write(' ' + enumeratedFilesCount.toString() + '\n');
|
|
}
|
|
}
|
|
}
|
|
callback(content, child);
|
|
}
|
|
});
|
|
if (checkNodeModules) {
|
|
const depthRoot = directoryDepth(rootDir);
|
|
const moduleRootPaths = resolveDependencies(currentPath, rootDir);
|
|
if (moduleRootPaths) {
|
|
moduleRootPaths.forEach(function (modulePath) {
|
|
const depthModule = directoryDepth(modulePath);
|
|
if (depthModule - depthRoot > maxDirectoryDepth())
|
|
return;
|
|
enumerateFilesSyncPriv(modulePath, rootDir, blackList, targetFileType, verbose, checkNodeModules, callback);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
exports.enumerateFilesSyncPriv = enumerateFilesSyncPriv;
|
|
/**
|
|
* @param action A function to be invoked for each target language.
|
|
* If it returns `true`, the enumeration will be terminated.
|
|
*/
|
|
function enumerateLanguageSync(action) {
|
|
if (!TARGET_LANGS)
|
|
TARGET_LANGS = getSupportedLanguages();
|
|
for (const lang of TARGET_LANGS) {
|
|
const stopEnumeration = action(lang);
|
|
if (stopEnumeration)
|
|
return;
|
|
}
|
|
}
|
|
exports.enumerateLanguageSync = enumerateLanguageSync;
|
|
/**
|
|
* @param {string} lang Supported languages in CLDR notation
|
|
* @param {Function}
|
|
* If callback returns err; if err, stop enumeration.
|
|
*/
|
|
function cloneEnglishTxtSyncDeep(rootDir) {
|
|
if (!rootDir)
|
|
rootDir = MY_ROOT;
|
|
const enDirPath = path.join(rootDir, 'intl', exports.ENGLISH);
|
|
mkdirp.sync(enDirPath);
|
|
return enumerateMsgSyncPriv(rootDir, rootDir, exports.ENGLISH, true, true, 0, function () { });
|
|
}
|
|
exports.cloneEnglishTxtSyncDeep = cloneEnglishTxtSyncDeep;
|
|
function enumerateMsgSync(rootDir, lang, checkNodeModules, callback) {
|
|
return enumerateMsgSyncPriv(rootDir, rootDir, lang, checkNodeModules, false, 0, callback);
|
|
}
|
|
exports.enumerateMsgSync = enumerateMsgSync;
|
|
function enumerateMsgSyncPriv(currentPath, rootDir, lang, checkNodeModules, cloneEnglishTxt, clonedTxtCount, callback) {
|
|
assert(currentPath);
|
|
assert(rootDir);
|
|
assert(typeof callback === 'function');
|
|
let intlDirectory = path.join(currentPath, 'intl');
|
|
const langDirPath = path.join(intlDirectory, lang);
|
|
let msgFiles = null;
|
|
try {
|
|
msgFiles = fs.readdirSync(langDirPath);
|
|
}
|
|
catch (e) {
|
|
return clonedTxtCount;
|
|
}
|
|
const enDirPath = path.join(rootDir, 'intl', exports.ENGLISH);
|
|
const clonedFileNames = [];
|
|
msgFiles.forEach(function (msgFile) {
|
|
if (msgFile.indexOf('.') === 0)
|
|
return;
|
|
const stats = fs.lstatSync(path.join(langDirPath, msgFile));
|
|
if (!stats.isFile())
|
|
return;
|
|
// commented out to avoid interference with intercept-stdout in test
|
|
// debug('enumerating...', path.join(langDirPath, msgFile));
|
|
if (cloneEnglishTxt && lang === exports.ENGLISH) {
|
|
if (currentPath === rootDir)
|
|
return;
|
|
if (getTrailerAfterDot(msgFile) !== 'txt')
|
|
return;
|
|
const sourceTxtFilePath = path.join(langDirPath, msgFile);
|
|
const filePathHash = msgFileIdHash(msgFile, currentPath);
|
|
if (resTagExists(filePathHash, msgFile, lang, exports.HELPTXT_TAG))
|
|
return;
|
|
registerResTag(filePathHash, msgFile, lang, exports.HELPTXT_TAG);
|
|
const targetTxtFilePath = path.join(enDirPath, msgFile);
|
|
clonedFileNames.push(msgFile);
|
|
fs.writeFileSync(targetTxtFilePath, fs.readFileSync(sourceTxtFilePath));
|
|
clonedTxtCount++;
|
|
console.log('--- cloned', sourceTxtFilePath);
|
|
}
|
|
else {
|
|
const jsonObj = readToJson(langDirPath, msgFile, lang);
|
|
if (jsonObj) {
|
|
callback(jsonObj, path.join(langDirPath, msgFile));
|
|
}
|
|
}
|
|
});
|
|
if (cloneEnglishTxt && lang === exports.ENGLISH && clonedFileNames.length > 0) {
|
|
removeObsoleteFile(enDirPath, clonedFileNames);
|
|
}
|
|
if (checkNodeModules) {
|
|
const depthRoot = directoryDepth(rootDir);
|
|
const moduleRootPaths = resolveDependencies(currentPath, rootDir);
|
|
if (moduleRootPaths) {
|
|
moduleRootPaths.forEach(function (modulePath) {
|
|
const depthModule = directoryDepth(modulePath);
|
|
if (depthModule - depthRoot > maxDirectoryDepth())
|
|
return;
|
|
clonedTxtCount = enumerateMsgSyncPriv(modulePath, rootDir, lang, false, cloneEnglishTxt, clonedTxtCount, callback);
|
|
});
|
|
}
|
|
}
|
|
return clonedTxtCount;
|
|
}
|
|
exports.enumerateMsgSyncPriv = enumerateMsgSyncPriv;
|
|
function removeObsoleteFile(dir, fileNames) {
|
|
const files = fs.readdirSync(dir);
|
|
files.forEach(function (file) {
|
|
const matched = file.match(/^([0-9a-f]{32})_(.*\.txt)$/);
|
|
if (!matched)
|
|
return;
|
|
if (fileNames.indexOf(matched[2]) >= 0) {
|
|
console.log('--- removed', path.join(dir, file));
|
|
fs.unlinkSync(path.join(dir, file));
|
|
}
|
|
});
|
|
}
|
|
exports.removeObsoleteFile = removeObsoleteFile;
|
|
function directoryDepth(fullPath) {
|
|
assert(typeof fullPath === 'string');
|
|
return _.compact(fullPath.split(path.sep)).length;
|
|
}
|
|
exports.directoryDepth = directoryDepth;
|
|
function maxDirectoryDepth() {
|
|
let depth = parseInt(process.env.STRONGLOOP_GLOBALIZE_MAX_DEPTH, 10);
|
|
if (isNaN(depth))
|
|
depth = exports.BIG_NUM;
|
|
depth = Math.max(1, depth);
|
|
return depth;
|
|
}
|
|
exports.maxDirectoryDepth = maxDirectoryDepth;
|
|
function requireResolve(depName, currentDir, rootDir) {
|
|
// simulates npm v3 dependency resolution
|
|
let depPath = null;
|
|
let stats = null;
|
|
try {
|
|
depPath = path.join(currentDir, 'node_modules', depName);
|
|
stats = fs.lstatSync(depPath);
|
|
}
|
|
catch (e) {
|
|
stats = null;
|
|
try {
|
|
depPath = path.join(rootDir, 'node_modules', depName);
|
|
stats = fs.lstatSync(depPath);
|
|
}
|
|
catch (e) {
|
|
return null;
|
|
}
|
|
}
|
|
if (!stats)
|
|
return null;
|
|
return unsymbolLink(depPath);
|
|
}
|
|
exports.requireResolve = requireResolve;
|
|
function unsymbolLink(filePath) {
|
|
if (!filePath)
|
|
return null;
|
|
let stats = null;
|
|
try {
|
|
stats = fs.lstatSync(filePath);
|
|
}
|
|
catch (e) {
|
|
return null;
|
|
}
|
|
if (!stats)
|
|
return null;
|
|
if (stats.isSymbolicLink()) {
|
|
let realPath = null;
|
|
try {
|
|
realPath = process.browser ? filePath : fs.realpathSync(filePath);
|
|
}
|
|
catch (e) {
|
|
return null;
|
|
}
|
|
return unsymbolLink(realPath);
|
|
}
|
|
else {
|
|
return stats.isDirectory() ? filePath : null;
|
|
}
|
|
}
|
|
exports.unsymbolLink = unsymbolLink;
|
|
function resolveDependencies(currentDir, rootDir, moduleRootPaths) {
|
|
moduleRootPaths = moduleRootPaths || [];
|
|
const packageJson = path.join(currentDir, 'package.json');
|
|
let deps = null;
|
|
try {
|
|
deps = require(packageJson).dependencies;
|
|
}
|
|
catch (e) {
|
|
return null;
|
|
}
|
|
if (deps === undefined || !deps)
|
|
return null;
|
|
deps = Object.keys(deps);
|
|
if (deps.length === 0)
|
|
return null;
|
|
deps.forEach(function (dep) {
|
|
const depPath = requireResolve(dep, currentDir, rootDir);
|
|
if (depPath && moduleRootPaths.indexOf(depPath) < 0) {
|
|
moduleRootPaths.push(depPath);
|
|
resolveDependencies(depPath, rootDir, moduleRootPaths);
|
|
}
|
|
});
|
|
moduleRootPaths = _.uniq(_.compact(moduleRootPaths));
|
|
return moduleRootPaths;
|
|
}
|
|
exports.resolveDependencies = resolveDependencies;
|
|
/**
|
|
* Read a txt or json file and convert to JSON
|
|
*/
|
|
const acceptableTrailers = ['json', 'txt'];
|
|
function readToJson(langDirPath, msgFile, lang) {
|
|
const fileType = getTrailerAfterDot(msgFile);
|
|
if (!fileType || acceptableTrailers.indexOf(fileType) < 0)
|
|
return null;
|
|
let jsonObj = null;
|
|
const sourceFilePath = path.join(langDirPath, msgFile);
|
|
if (fileType === 'json') {
|
|
jsonObj = JSON.parse(stripBom(fs.readFileSync(sourceFilePath, 'utf-8')));
|
|
}
|
|
else {
|
|
// txt
|
|
const origStr = stripBom(fs.readFileSync(sourceFilePath, 'utf8'));
|
|
jsonObj = {};
|
|
const re = /^([0-9a-f]{32})_(.*)\.txt/;
|
|
const results = re.exec(msgFile);
|
|
if (results && results.length === 3) {
|
|
// deep-extracted txt file ?
|
|
msgFile = results[2] + '.txt';
|
|
}
|
|
jsonObj[msgFile] = mapPercent(JSON.parse(JSON.stringify(origStr)));
|
|
}
|
|
if (fileType === 'json' && HASH_KEYS && lang === exports.ENGLISH) {
|
|
const keys = Object.keys(jsonObj);
|
|
keys.forEach(function (key) {
|
|
const newKey = md5(key);
|
|
jsonObj[newKey] = jsonObj[key];
|
|
delete jsonObj[key];
|
|
});
|
|
}
|
|
return jsonObj;
|
|
}
|
|
exports.readToJson = readToJson;
|
|
function normalizeKeyArrays(keyArrays) {
|
|
// keep 0 as "0"
|
|
if (keyArrays == null)
|
|
return [];
|
|
if (typeof keyArrays === 'string' && keyArrays.length === 0)
|
|
return [];
|
|
if (!Array.isArray(keyArrays))
|
|
return [[keyArrays.toString()]];
|
|
const retKeyArrays = [];
|
|
keyArrays.forEach(function (keyArray) {
|
|
if (keyArray === null)
|
|
return;
|
|
if (typeof keyArray === 'string' && keyArray.length === 0)
|
|
return;
|
|
if (!Array.isArray(keyArray)) {
|
|
retKeyArrays.push([keyArray.toString()]);
|
|
return;
|
|
}
|
|
const retKeyArray = [];
|
|
keyArray.forEach(function (key) {
|
|
if (key === null)
|
|
return;
|
|
if (typeof key === 'string' && key.length === 0)
|
|
return;
|
|
assert(typeof key === 'string' || typeof key === 'number', 'type of key must be a string or a number.');
|
|
retKeyArray.push(key.toString());
|
|
});
|
|
if (retKeyArray.length > 0)
|
|
retKeyArrays.push(retKeyArray);
|
|
});
|
|
return retKeyArrays;
|
|
}
|
|
exports.normalizeKeyArrays = normalizeKeyArrays;
|
|
function scanJson(keys, data, returnErrors) {
|
|
return scanJsonPriv(keys, data, null, returnErrors);
|
|
}
|
|
exports.scanJson = scanJson;
|
|
function replaceJson(keys, data, newValues) {
|
|
return scanJsonPriv(keys, data, newValues, false);
|
|
}
|
|
exports.replaceJson = replaceJson;
|
|
function scanJsonPriv(keys, data, newValues, returnErrors) {
|
|
if (!data || typeof data !== 'object')
|
|
return [];
|
|
if (newValues)
|
|
assert(keys.length === newValues.length);
|
|
const keyArrays = normalizeKeyArrays(keys);
|
|
const ret = [];
|
|
keyArrays.forEach((k, kix) => {
|
|
let d = null;
|
|
let err = null;
|
|
let prevObj = null;
|
|
let prevKey = null;
|
|
try {
|
|
for (let ix = 0; ix < k.length; ix++) {
|
|
if (ix === 0)
|
|
d = data;
|
|
if (typeof d === 'string') {
|
|
err = '*** unexpected string value ' + JSON.stringify(k);
|
|
if (returnErrors)
|
|
ret.push(err);
|
|
else
|
|
console.log(err);
|
|
return;
|
|
}
|
|
prevObj = d;
|
|
prevKey = k[ix];
|
|
d = d[k[ix]];
|
|
}
|
|
if (typeof d === 'string') {
|
|
if (newValues)
|
|
prevObj[prevKey] = newValues[kix];
|
|
else
|
|
ret.push(d);
|
|
}
|
|
else {
|
|
err = '*** not a string value ' + JSON.stringify(k);
|
|
if (returnErrors)
|
|
ret.push(err);
|
|
else
|
|
console.log(err);
|
|
}
|
|
}
|
|
catch (e) {
|
|
err = '*** ' + e.toString() + ' ' + JSON.stringify(k);
|
|
if (returnErrors)
|
|
ret.push(err);
|
|
else
|
|
console.log(err);
|
|
}
|
|
});
|
|
return newValues ? data : ret;
|
|
}
|
|
exports.scanJsonPriv = scanJsonPriv;
|
|
function sortMsges(msgs) {
|
|
const keys = Object.keys(msgs);
|
|
const msgKeys = _.remove(keys, function (key) {
|
|
return KEY_HEADERS.some(function (header) {
|
|
return key.indexOf(header) === 0;
|
|
});
|
|
});
|
|
const sorted = {};
|
|
keys.sort().forEach(function (key) {
|
|
sorted[key] = msgs[key];
|
|
});
|
|
msgKeys.sort().forEach(function (key) {
|
|
sorted[key] = msgs[key];
|
|
});
|
|
return sorted;
|
|
}
|
|
exports.sortMsges = sortMsges;
|
|
/**
|
|
* Initialize intl directory structure for non-En languages
|
|
* intl/en must exist.
|
|
* it returns false if failed.
|
|
*/
|
|
function initIntlDirs() {
|
|
let intlEnStats;
|
|
try {
|
|
intlEnStats = fs.statSync(path.join(INTL_DIR, exports.ENGLISH));
|
|
}
|
|
catch (e) {
|
|
return false;
|
|
}
|
|
if (!intlEnStats.isDirectory())
|
|
return false;
|
|
if (!TARGET_LANGS)
|
|
TARGET_LANGS = getSupportedLanguages();
|
|
TARGET_LANGS.forEach(function (lang) {
|
|
mkdirp.sync(path.join(INTL_DIR, lang));
|
|
});
|
|
return true;
|
|
}
|
|
exports.initIntlDirs = initIntlDirs;
|
|
/**
|
|
* @param {string} lang Supported languages in CLDR notation
|
|
* Returns true for 'en' and supported languages
|
|
* in CLDR notation.
|
|
*/
|
|
function isSupportedLanguage(lang) {
|
|
lang = lang || 'en';
|
|
if (!TARGET_LANGS)
|
|
TARGET_LANGS = getSupportedLanguages();
|
|
return TARGET_LANGS.indexOf(lang) >= 0 || getAppLanguages().indexOf(lang) > 0;
|
|
}
|
|
exports.isSupportedLanguage = isSupportedLanguage;
|
|
/**
|
|
* Returns an array of locales supported by the local cldr data.
|
|
*/
|
|
function getSupportedLanguages() {
|
|
const cldrDir = path.join(__dirname, '..', 'cldr');
|
|
let langs = [];
|
|
enumerateFilesSync(cldrDir, null, ['json'], false, false, (content, filePath) => {
|
|
let cldr = null;
|
|
try {
|
|
cldr = JSON.parse(content);
|
|
}
|
|
catch (e) {
|
|
throw new Error('*** CLDR read error on ' + process.platform);
|
|
}
|
|
const theseLangs = Object.keys(cldr.main || {});
|
|
langs = _.concat(langs, theseLangs);
|
|
});
|
|
return _.uniq(langs);
|
|
}
|
|
exports.getSupportedLanguages = getSupportedLanguages;
|
|
function getAppLanguages() {
|
|
if (config_1.STRONGLOOP_GLB && config_1.STRONGLOOP_GLB.APP_LANGS) {
|
|
return config_1.STRONGLOOP_GLB.APP_LANGS;
|
|
}
|
|
return [];
|
|
}
|
|
exports.getAppLanguages = getAppLanguages;
|
|
/**
|
|
* Returns trailer of file name.
|
|
*/
|
|
function getTrailerAfterDot(name) {
|
|
if (typeof name !== 'string')
|
|
return null;
|
|
const parts = name.split('.');
|
|
if (parts.length < 2)
|
|
return null;
|
|
return parts[parts.length - 1].toLowerCase();
|
|
}
|
|
exports.getTrailerAfterDot = getTrailerAfterDot;
|
|
/**
|
|
* Returns package name defined in package.json.
|
|
*/
|
|
function getPackageName(root) {
|
|
return getPackageItem(root, 'name');
|
|
}
|
|
exports.getPackageName = getPackageName;
|
|
function getPackageVersion(root) {
|
|
return getPackageItem(root, 'version');
|
|
}
|
|
exports.getPackageVersion = getPackageVersion;
|
|
function getPackageItem(root, itemName) {
|
|
root = root || MY_ROOT;
|
|
let item = null;
|
|
try {
|
|
item = require(path.join(root, 'package.json'))[itemName];
|
|
}
|
|
catch (e) { }
|
|
return item;
|
|
}
|
|
exports.getPackageItem = getPackageItem;
|
|
/**
|
|
* @param {string} name to be checked
|
|
* @param {Array} headersAllowed a list of strings to check
|
|
* Returns directory path for the language.
|
|
*/
|
|
function headerIncluded(name, headersAllowed) {
|
|
if (typeof name !== 'string')
|
|
return false;
|
|
let matched = false;
|
|
if (Array.isArray(headersAllowed)) {
|
|
headersAllowed.forEach(function (header) {
|
|
if (matched)
|
|
return;
|
|
matched = name.indexOf(header) === 0;
|
|
});
|
|
}
|
|
else if (typeof headersAllowed === 'string') {
|
|
matched = name.indexOf(headersAllowed) === 0;
|
|
}
|
|
return matched;
|
|
}
|
|
exports.headerIncluded = headerIncluded;
|
|
/**
|
|
* @param {string} lang Supported languages in CLDR notation
|
|
* Returns directory path for the language.
|
|
*/
|
|
function intlDir(lang) {
|
|
lang = lang || exports.ENGLISH;
|
|
return path.join(INTL_DIR, lang);
|
|
}
|
|
exports.intlDir = intlDir;
|
|
/**
|
|
* %s is included in the string
|
|
*/
|
|
function percent(msg) {
|
|
return /\%[sdj\%]/.test(msg);
|
|
}
|
|
exports.percent = percent;
|
|
/**
|
|
* %replace %s with {N} where N=0,1,2,...
|
|
*/
|
|
function mapPercent(msg) {
|
|
let ix = 0;
|
|
const output = msg.replace(/\%[sdj\%]/g, (match) => {
|
|
if (match === '%%')
|
|
return '';
|
|
const str = '{' + ix.toString() + '}';
|
|
ix++;
|
|
return str;
|
|
});
|
|
return output;
|
|
}
|
|
exports.mapPercent = mapPercent;
|
|
function mapArgs(p, args) {
|
|
let ix = 1;
|
|
const output = [];
|
|
p.replace(/\%[sdj\%]/g, (match) => {
|
|
if (match === '%%')
|
|
return '';
|
|
let arg = args[ix++];
|
|
if (arg === undefined)
|
|
arg = 'undefined';
|
|
if (arg === null)
|
|
arg = 'null';
|
|
output.push(match === '%j' ? JSON.stringify(arg) : arg.toString());
|
|
return '';
|
|
});
|
|
return output;
|
|
}
|
|
exports.mapArgs = mapArgs;
|
|
function repackArgs(args, initIx) {
|
|
const argsLength = Array.isArray(args)
|
|
? args.length
|
|
: Object.keys(args).length;
|
|
if (initIx >= argsLength)
|
|
return [];
|
|
const output = [];
|
|
for (let ix = initIx; ix < argsLength; ix++) {
|
|
output.push(args[ix]);
|
|
}
|
|
return output;
|
|
}
|
|
exports.repackArgs = repackArgs;
|
|
/**
|
|
* Get the language (from the supported languages) that
|
|
* best matches the requested Accept-Language expression.
|
|
*
|
|
* @param req
|
|
* @param globalize
|
|
* @returns {*}
|
|
*/
|
|
function getLanguageFromRequest(req, appLanguages, defaultLanguage) {
|
|
if (!req || !req.headers || !req.headers['accept-language']) {
|
|
return defaultLanguage;
|
|
}
|
|
let languages = req.headers['accept-language'].split(',');
|
|
for (let i = 0; i < languages.length; i++) {
|
|
let languageWeighted = languages[i].split(';');
|
|
languageWeighted[0] = getLangAlias(languageWeighted[0].trim());
|
|
languages[i] = languageWeighted.join(';');
|
|
}
|
|
const reqLanguage = languages.join(',');
|
|
if (!reqLanguage) {
|
|
return defaultLanguage;
|
|
}
|
|
// Copy the array so that it won't be mutated
|
|
appLanguages = [defaultLanguage, ...appLanguages];
|
|
acceptLanguage.languages(appLanguages);
|
|
const bestLanguage = acceptLanguage.get(reqLanguage);
|
|
return bestLanguage || defaultLanguage;
|
|
}
|
|
exports.getLanguageFromRequest = getLanguageFromRequest;
|
|
function myIntlDir() {
|
|
return INTL_DIR;
|
|
}
|
|
exports.myIntlDir = myIntlDir;
|
|
/**
|
|
* Load messages for the language from a given root directory
|
|
* @param lang Language for messages
|
|
* @param rootDir Root directory
|
|
* @param enumerateNodeModules A flag to control if node_modules will be checked
|
|
*/
|
|
function loadMsgFromFile(lang, rootDir, enumerateNodeModules) {
|
|
assert(lang);
|
|
rootDir = rootDir || getRootDir();
|
|
if (!isLoadMessages(rootDir))
|
|
return;
|
|
enumerateNodeModules = enumerateNodeModules || false;
|
|
const tagType = exports.MSG_TAG;
|
|
enumerateMsgSync(rootDir, lang, enumerateNodeModules, (jsonObj, filePath) => {
|
|
// writeAllToMsg(lang, jsonObj);
|
|
let fileName = path.basename(filePath);
|
|
const re = /^([0-9a-f]{32})_(.*)\.txt/;
|
|
const results = re.exec(fileName);
|
|
let fileIdHash;
|
|
if (results && results.length === 3) {
|
|
// deep-extracted txt file ?
|
|
fileIdHash = results[1];
|
|
fileName = results[2] + '.txt';
|
|
}
|
|
else {
|
|
fileIdHash = msgFileIdHash(fileName, rootDir);
|
|
fileName = fileName;
|
|
}
|
|
if (resTagExists(fileIdHash, fileName, lang, tagType)) {
|
|
debug('*** loadMsgFromFile(res tag exists): skipping:', lang, fileName);
|
|
return;
|
|
}
|
|
debug('*** loadMsgFromFile(new res tag): loading:', lang, fileName);
|
|
removeDoubleCurlyBraces(jsonObj);
|
|
const messages = {};
|
|
messages[lang] = jsonObj;
|
|
config_1.STRONGLOOP_GLB.loadMessages(messages);
|
|
registerResTag(fileIdHash, fileName, lang, tagType);
|
|
if (config_1.STRONGLOOP_GLB.formatters.has(lang)) {
|
|
const formatters = config_1.STRONGLOOP_GLB.formatters.get(lang);
|
|
for (const key in jsonObj) {
|
|
formatters.delete(key);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
exports.loadMsgFromFile = loadMsgFromFile;
|
|
/**
|
|
* Remove `{{` and `}}`
|
|
*/
|
|
function removeDoubleCurlyBraces(json) {
|
|
let count = 0;
|
|
Object.keys(json).forEach((key) => {
|
|
count++;
|
|
if (typeof json[key] !== 'string') {
|
|
// The value for `zz` pseudo code is an array, let's skip
|
|
return;
|
|
}
|
|
json[key] = json[key].replace(/}}/g, '').replace(/{{/g, '');
|
|
debug(count, key + ' : ' + json[key]);
|
|
});
|
|
}
|
|
exports.removeDoubleCurlyBraces = removeDoubleCurlyBraces;
|
|
/**
|
|
* If an language has alias name that SG supports, return the alias name.
|
|
*
|
|
* The known aliases are hard-coded to solve issue
|
|
* https://github.com/strongloop/strong-globalize/issues/150
|
|
* @param lang
|
|
*/
|
|
function getLangAlias(lang) {
|
|
// The {lang: alias} pairs
|
|
let language = _.toLower(lang) || lang;
|
|
const ALIAS_MAP = {
|
|
'zh-cn': 'zh-Hans',
|
|
'zh-tw': 'zh-Hant',
|
|
};
|
|
if (lang && ALIAS_MAP.hasOwnProperty(language))
|
|
return ALIAS_MAP[language];
|
|
return lang;
|
|
}
|
|
exports.getLangAlias = getLangAlias;
|
|
//# sourceMappingURL=helper.js.map
|