ilib-tools-common 1.21.4 → 1.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/utils.js CHANGED
@@ -99,34 +99,58 @@ output+="["+keyword+"]";break}}}return output}/**
99
99
  * @param {string} template the path template string
100
100
  * @param {Object} parameters an object containing:
101
101
  * @param {string} parameters.sourcepath the path to the source file, relative to the
102
- * root of the project
102
+ * root of the project (used when path parts are not provided)
103
103
  * @param {string} parameters.locale the locale for the output file path
104
104
  * @param {string} parameters.resourceDir optional resource directory to substitute
105
105
  * for [resourceDir] in the template
106
+ * @param {string} parameters.dir optional pre-parsed directory (e.g. from parsePath)
107
+ * @param {string} parameters.basename optional pre-parsed basename without extension
108
+ * @param {string} parameters.extension optional pre-parsed file extension
109
+ * @param {string} parameters.filename optional pre-parsed filename (basename + extension)
110
+ * When any of dir, basename, extension, or filename are provided, they are used
111
+ * instead of deriving from sourcepath. This allows using parsePath output directly.
106
112
  * @returns {string} the formatted file path
107
- */function formatPath(template,parameters){var pathname=parameters.sourcepath||"";var locale=parameters.locale||"en";var resourceDir=parameters.resourceDir||".";// First, handle locale-related substitutions without path normalization
108
- var output=formatLocaleParams(template,locale);// Now handle path-specific keywords
109
- var base;var lastDot;output=output.replace(/\[dir\]/g,_path["default"].dirname(pathname));output=output.replace(/\[filename\]/g,_path["default"].basename(pathname));output=output.replace(/\[resourceDir\]/g,resourceDir);if(output.includes("[extension]")){base=_path["default"].basename(pathname);output=output.replace(/\[extension\]/g,base.indexOf(".")>-1?base.substring(base.lastIndexOf(".")+1):"")}if(output.includes("[basename]")){base=_path["default"].basename(pathname);lastDot=base.lastIndexOf(".");output=output.replace(/\[basename\]/g,lastDot>-1?base.substring(0,lastDot):base)}return _path["default"].normalize(output)};var matchExprs={"dir":{regex:"(.*)",brackets:1,groups:{dir:1}},"basename":{regex:"(.*?)",brackets:1,groups:{basename:1}},"resourceDir":{regex:"(.*?)",brackets:1,groups:{resourceDir:1}},"extension":{regex:"(.*)",brackets:1,groups:{extension:1}},"locale":{regex:"(([a-z][a-z][a-z]?)(-([A-Z][a-z][a-z][a-z]))?(-([A-Z][A-Z]|[0-9][0-9][0-9]))?)",brackets:6,groups:{locale:1,language:2,script:4,region:6}},"language":{regex:"([a-z][a-z][a-z]?)",brackets:1,groups:{language:1}},"script":{regex:"([A-Z][a-z][a-z][a-z])",brackets:1,groups:{script:1}},"region":{regex:"([A-Z][A-Z]|[0-9][0-9][0-9])",brackets:1,groups:{region:1}},"localeDir":{regex:"(([a-z][a-z][a-z]?)(/([A-Z][a-z][a-z][a-z]))?(/([A-Z][A-Z]|[0-9][0-9][0-9]))?)",brackets:6,groups:{localeDir:1,language:2,script:4,region:6}},"localeUnder":{regex:"(([a-z][a-z][a-z]?)(_([A-Z][a-z][a-z][a-z]))?(_([A-Z][A-Z]|[0-9][0-9][0-9]))?)",brackets:6,groups:{localeUnder:1,language:2,script:4,region:6}},"localeLower":{regex:"(([a-z][a-z][a-z]?)(-([a-z][a-z][a-z][a-z]))?(-([a-z][a-z]|[0-9][0-9][0-9]))?)",brackets:6,groups:{localeUnder:1,language:2,script:4,region:6}}};/**
113
+ */function formatPath(template,parameters){var _parameters$dir,_parameters$basename,_parameters$extension,_parameters$filename;var pathname=parameters.sourcepath||"";var locale=parameters.locale||"en";var resourceDir=parameters.resourceDir||".";// Use pre-parsed path parts when provided (e.g. from parsePath); otherwise derive from sourcepath
114
+ var pathParts=fillPartialPathParts(pathname);var dir=(_parameters$dir=parameters.dir)!==null&&_parameters$dir!==void 0?_parameters$dir:pathParts.dir;var basename=(_parameters$basename=parameters.basename)!==null&&_parameters$basename!==void 0?_parameters$basename:pathParts.basename;var extension=(_parameters$extension=parameters.extension)!==null&&_parameters$extension!==void 0?_parameters$extension:pathParts.extension;var filename=(_parameters$filename=parameters.filename)!==null&&_parameters$filename!==void 0?_parameters$filename:extension?basename+"."+extension:basename;// First, handle locale-related substitutions without path normalization
115
+ var output=formatLocaleParams(template,locale);// Handle [basename].[extension] as a unit to avoid trailing dot when extension is empty
116
+ output=output.replace(/\[basename\]\.\[extension\]/g,extension?basename+"."+extension:basename);// Now handle path-specific keywords
117
+ output=output.replace(/\[dir\]/g,dir);output=output.replace(/\[filename\]/g,filename);output=output.replace(/\[resourceDir\]/g,resourceDir);output=output.replace(/\[extension\]/g,extension);output=output.replace(/\[basename\]/g,basename);return _path["default"].normalize(output)};var matchExprs={"dir":{regex:"(.*)",brackets:1,groups:{dir:1}},"basename":{regex:"(.*?)",brackets:1,groups:{basename:1}},"resourceDir":{regex:"(.*?)",brackets:1,groups:{resourceDir:1}},"extension":{regex:"(.*)",brackets:1,groups:{extension:1}},"locale":{regex:"(([a-z][a-z][a-z]?)(-([A-Z][a-z][a-z][a-z]))?(-([A-Z][A-Z]|[0-9][0-9][0-9]))?)",brackets:6,groups:{locale:1,language:2,script:4,region:6}},"language":{regex:"([a-z][a-z][a-z]?)",brackets:1,groups:{language:1}},"script":{regex:"([A-Z][a-z][a-z][a-z])",brackets:1,groups:{script:1}},"region":{regex:"([A-Z][A-Z]|[0-9][0-9][0-9])",brackets:1,groups:{region:1}},"localeDir":{regex:"(([a-z][a-z][a-z]?)(/([A-Z][a-z][a-z][a-z]))?(/([A-Z][A-Z]|[0-9][0-9][0-9]))?)",brackets:6,groups:{localeDir:1,language:2,script:4,region:6}},"localeUnder":{regex:"(([a-z][a-z][a-z]?)(_([A-Z][a-z][a-z][a-z]))?(_([A-Z][A-Z]|[0-9][0-9][0-9]))?)",brackets:6,groups:{localeUnder:1,language:2,script:4,region:6}},"localeLower":{regex:"(([a-z][a-z][a-z]?)(-([a-z][a-z][a-z][a-z]))?(-([a-z][a-z]|[0-9][0-9][0-9]))?)",brackets:6,groups:{localeUnder:1,language:2,script:4,region:6}}};/**
118
+ * Derive dir, basename, extension, and whole filename from a pathname using path operations.
119
+ * Used when the template does not match so we can still fill path parts.
120
+ *
121
+ * @param {String} pathname the path name
122
+ * @returns {Object} { dir, basename, extension, filename }
123
+ */function fillPartialPathParts(pathname){if(!pathname){return{dir:".",basename:"",extension:""}}var dir=_path["default"].dirname(pathname)||".";var filename=_path["default"].basename(pathname);var lastDot=filename.lastIndexOf(".");var basename=lastDot>-1?filename.substring(0,lastDot):filename;var extension=lastDot>-1?filename.substring(lastDot+1):"";return{dir:dir,basename:basename,extension:extension,filename:filename}}/**
124
+ * Fill locale, language, script, region from a source locale string using Locale class.
125
+ *
126
+ * @param {String} sourceLocale BCP-47 locale string
127
+ * @returns {Object} { locale, language, script?, region? }
128
+ */function fillLocalePartsFromSource(sourceLocale){if(!sourceLocale){return{}}var l=new _ilibLocale["default"](sourceLocale);var result={locale:l.getSpec(),language:l.language||""};if(l.script){result.script=l.script}if(l.region){result.region=l.region}return result}/**
110
129
  * Parse a path according to the given template, and return the parts.
111
130
  * The parts can be any of the fields mentioned in the {@link formatPath}
112
- * documentation. If any field is not parsed, the result is an empty object
131
+ * documentation. If the template does not match, fills dir, basename, extension
132
+ * from the path. When sourceLocale is provided and no locale is in the path,
133
+ * fills locale, language, script, region from the source locale.
113
134
  *
114
135
  * @param {String} template the ilib template for matching against the path
115
136
  * @param {String} pathname the path name to match against the template
116
- * @returns {Object} an object mapping the fields to their values in the
117
- * the pathname
118
- */function parsePath(template,pathname){var regex="";var matchGroups={};var totalBrackets=0;var base;if(!template){template=defaultMappings["**/*.json"].template}for(var i=0;i<template.length;i++){if(template[i]!=="["){regex+=template[i]}else{var start=++i;while(i<template.length&&template[i]!=="]"){i++}var keyword=template.substring(start,i);switch(keyword){case"filename":// escape special characters in the filename so they don't have their special meaning in the regex
119
- regex+=_path["default"].basename(pathname||"").replace(/[.*+?^${}()|[\]\\]/g,"\\$&");break;default:if(!matchExprs[keyword]){logger.warning("Warning: template contains unknown substitution parameter "+keyword);return""}// [dir]/ is optional when followed by / and another token. Always capture dir so callers
137
+ * @param {String} sourceLocale optional; when no locale in path, use this to fill locale parts
138
+ * @returns {Object} an object mapping the fields to their values in the pathname
139
+ */function parsePath(template,pathname,sourceLocale){var regex="";var matchGroups={};var totalBrackets=0;var base;if(!template){template=defaultMappings["**/*.json"].template}for(var i=0;i<template.length;i++){if(template[i]!=="["){// Escape regex metacharacters so literal "." etc. match correctly (e.g. [basename].[locale].[extension])
140
+ var c=template[i];regex+=c==="."||c==="*"||c==="+"||c==="?"||c==="^"||c==="$"||c==="{"||c==="}"||c==="("||c===")"||c==="|"||c==="\\"?"\\"+c:c}else{var start=++i;while(i<template.length&&template[i]!=="]"){i++}var keyword=template.substring(start,i);switch(keyword){case"filename":// escape special characters in the filename so they don't have their special meaning in the regex
141
+ regex+=_path["default"].basename(pathname||"").replace(/[.*+?^${}()|[\]\\]/g,"\\$&");break;default:if(!matchExprs[keyword]){logger.warning("Warning: template contains unknown substitution parameter "+keyword);return""}// [basename].[extension]: use last dot to split so "foo.en-US.mdx" -> basename="foo.en-US", extension="mdx"
142
+ if(keyword==="basename"&&template[i+1]==="."&&template[i+2]==="["){var nextEnd=template.indexOf("]",i+2);var nextKeyword=nextEnd>-1?template.substring(i+3,nextEnd):"";if(nextKeyword==="extension"){regex+="(.*)\\.([^.]+)";matchGroups.basename=totalBrackets+1;matchGroups.extension=totalBrackets+2;totalBrackets+=2;i=nextEnd;break}}// [dir]/ is optional when followed by / and another token. Always capture dir so callers
120
143
  // (e.g. PropertiesParser) can build paths. Use (?:()|(.*?)/) so we require the slash when
121
144
  // dir is non-empty - this prevents "test.properties" from matching as locale "est" with dir "test/testfiles/t".
122
145
  // For "./de.po" the empty alternative matches; for "test/testfiles/de-DE.properties" we capture dir.
123
146
  // Do NOT make optional for [basename], [extension], [resourceDir] - that would break path parsing.
124
- var optionalDirSlash=false;if(keyword==="dir"&&i+1<template.length&&template[i+1]==="/"&&i+2<template.length&&template[i+2]==="["){var endBracket=template.indexOf("]",i+2);var nextKeyword=endBracket>-1?template.substring(i+3,endBracket):"";var optionalDirTokens=["filename","locale","language","script","region","localeDir","localeUnder","localeLower"];optionalDirSlash=optionalDirTokens.includes(nextKeyword)}if(keyword==="dir"&&optionalDirSlash){// (?:()|(.*?)/) - empty dir or (.*?)/ (require slash when dir is non-empty)
147
+ var optionalDirSlash=false;if(keyword==="dir"&&i+1<template.length&&template[i+1]==="/"&&i+2<template.length&&template[i+2]==="["){var endBracket=template.indexOf("]",i+2);var _nextKeyword=endBracket>-1?template.substring(i+3,endBracket):"";var optionalDirTokens=["filename","locale","language","script","region","localeDir","localeUnder","localeLower"];optionalDirSlash=optionalDirTokens.includes(_nextKeyword)}if(keyword==="dir"&&optionalDirSlash){// (?:()|(.*?)/) - empty dir or (.*?)/ (require slash when dir is non-empty)
125
148
  regex+="(?:()|(.*?)/)";matchGroups.dir=totalBrackets+2;// second group has dir when present
126
149
  totalBrackets+=2;i++}else{regex+=matchExprs[keyword].regex;for(var prop in matchExprs[keyword].groups){matchGroups[prop]=totalBrackets+matchExprs[keyword].groups[prop]}totalBrackets+=matchExprs[keyword].brackets}break}}}// Anchor at start. If path starts with "./", allow it so template matches the rest (e.g. "./ja/foo.mdx" matches "[language]/[dir]/[filename]").
127
150
  var prefix=pathname&&pathname.startsWith("./")?"^\\.\\/":"^";var re=new RegExp(prefix+regex,"u");var match=re.exec(pathname||"");if(match!==null){var groups={};var found=false;for(var groupName in matchGroups){var idx=matchGroups[groupName];var value=idx<match.length?match[idx]:undefined;// Capture empty only for dir (so "[dir]/[filename]" with "foo.mdx" gives dir "."); optional locale parts stay omitted.
128
151
  if(value!==undefined&&(value||groupName==="dir"&&value==="")){groups[groupName]=value===undefined?"":value;found=true}}if(groups.dir===""||matchGroups.dir&&groups.dir===undefined){groups.dir="."}// When path started with "./", prefix consumed it; put it back into dir so dir is the full path.
129
- if(pathname&&pathname.startsWith("./")&&groups.dir!==undefined&&groups.dir!=="."){groups.dir="./"+groups.dir}return groups}return{}}/**
152
+ if(pathname&&pathname.startsWith("./")&&groups.dir!==undefined&&groups.dir!=="."){groups.dir="./"+groups.dir}return groups}// Template did not match; fill dir, basename, extension from path
153
+ var partial=fillPartialPathParts(pathname||"");if(sourceLocale){Object.assign(partial,fillLocalePartsFromSource(sourceLocale))}return partial}/**
130
154
  * Return a locale encoded in the path using template to parse that path.
131
155
  * See {#formatPath} for the full description of the syntax of the template.
132
156
  * @param {String} template template for the output file
package/lib/utils.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","names":["_fs","_interopRequireDefault","require","_path","_ilibLocale","_log4jsApi","_ilibCtype","_pluralCategories","e","__esModule","_typeof","o","Symbol","iterator","constructor","prototype","logger","log4js","getLogger","cleanString","str","undefined","toLowerCase","replace","trim","isEmpty","obj","prop","formatLocaleParams","template","locale","l","Locale","localeSpec","getSpec","output","i","length","start","keyword","substring","getLanguage","getScript","getRegion","formatPath","parameters","pathname","sourcepath","resourceDir","base","lastDot","path","dirname","basename","includes","indexOf","lastIndexOf","normalize","matchExprs","regex","brackets","groups","dir","extension","language","script","region","localeDir","localeUnder","parsePath","matchGroups","totalBrackets","defaultMappings","warning","optionalDirSlash","endBracket","nextKeyword","optionalDirTokens","prefix","startsWith","re","RegExp","match","exec","found","groupName","idx","value","getLocaleFromPath","charAt","toUpperCase","slice","makeDirs","parts","split","p","join","fs","existsSync","mkdirSync","containsActualText","cleaned","c","isAlnum","isIdeo","isPrimitive","type","objectMap","object","visitor","Array","isArray","map","item","ret","hasOwnProperty","hashKey","source","hash","modulus","multiple","charCodeAt","nonBreakingTags","exports","selfClosingTags","ignoreTags","localizableAttributes","defaultPluralCategories","getLanguagePluralCategories","_locale$getLanguage","_pluralCategories$lan","lang","pluralCategories"],"sources":["../src/utils.js"],"sourcesContent":["/*\n * utils.js - utility functions to support the other code\n *\n * Copyright © 2022-2023, 2025-2026 JEDLSoft\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport fs from 'fs';\nimport path from 'path';\nimport Locale from 'ilib-locale';\nimport log4js from '@log4js-node/log4js-api';\n\nimport { isAlnum, isIdeo } from 'ilib-ctype';\n\nimport pluralCategories from './pluralCategories.js';\n\nconst logger = log4js.getLogger('tools-common.utils');\n\n/**\n * Clean a string for matching against other strings by removing\n * differences that are inconsequential for translation.\n *\n * @param {String} str string to clean\n * @returns {String} the cleaned string\n */\nexport function cleanString(str) {\n if (typeof(str) !== 'string') {\n return undefined;\n }\n return str.toLowerCase().\n replace(/\\\\n/g, \" \").\n replace(/\\\\t/g, \" \").\n replace(/\\\\/g, \"\").\n replace(/\\s+/g, \" \").\n trim().\n replace(/&apos;/g, \"'\").\n replace(/&quot;/g, '\"').\n replace(/&lt;/g, \"<\").\n replace(/&gt;/g, \">\").\n replace(/&amp;/g, \"&\").\n replace(/’/g, \"'\");\n};\n\n/**\n * Is an empty object or not\n * @param {Object} obj object to test\n * @returns {Boolean} true if there are no properties, false otherwise\n */\nexport function isEmpty(obj) {\n let prop = undefined;\n\n if (!obj) {\n return true;\n }\n\n for (prop in obj) {\n if (prop && obj[prop]) {\n return false;\n }\n }\n return true;\n};\n\n/**\n * Format a string template with locale-related parameters.\n *\n * This function substitutes locale-related placeholders in a template string\n * without treating the string as a file path (no path normalization).\n * This is useful for formatting headers, footers, or other strings that\n * contain locale placeholders but should not be treated as file paths.\n *\n * This function recognizes and replaces the following strings in\n * templates:\n * - [locale] the full BCP-47 locale specification for the target locale\n * eg. \"zh-Hans-CN\" -> \"zh-Hans-CN\"\n * - [language] the language portion of the full locale\n * eg. \"zh-Hans-CN\" -> \"zh\"\n * - [script] the script portion of the full locale\n * eg. \"zh-Hans-CN\" -> \"Hans\"\n * - [region] the region portion of the full locale\n * eg. \"zh-Hans-CN\" -> \"CN\"\n * - [localeDir] the full locale where each portion of the locale\n * is a directory in this order: [langage], [script], [region].\n * eg, \"zh-Hans-CN\" -> \"zh/Hans/CN\", but \"en\" -> \"en\".\n * - [localeUnder] the full BCP-47 locale specification, but using\n * underscores to separate the locale parts instead of dashes.\n * eg. \"zh-Hans-CN\" -> \"zh_Hans_CN\"\n * - [localeLower] the full BCP-47 locale specification, but makes\n * all locale parts lowercased.\n * eg. \"zh-Hans-CN\" -> \"zh-hans-cn\"\n *\n * Unknown keywords are preserved in the output unchanged.\n *\n * @param {string} template the template string to format\n * @param {string|Object} locale the locale specifier, either as a string\n * or as a Locale object\n * @returns {string} the formatted string with locale placeholders replaced\n */\nexport function formatLocaleParams(template, locale) {\n if (!template) return \"\";\n const l = typeof locale === 'string' ? new Locale(locale || \"en\") : locale;\n const localeSpec = l.getSpec();\n let output = \"\";\n\n for (let i = 0; i < template.length; i++) {\n if (template[i] !== '[') {\n output += template[i];\n } else {\n let start = ++i;\n while (i < template.length && template[i] !== ']') {\n i++;\n }\n const keyword = template.substring(start, i);\n switch (keyword) {\n case 'locale':\n output += localeSpec;\n break;\n case 'language':\n output += l.getLanguage() || \"\";\n break;\n case 'script':\n output += l.getScript() || \"\";\n break;\n case 'region':\n output += l.getRegion() || \"\";\n break;\n case 'localeDir':\n output += localeSpec.replace(/-/g, '/');\n break;\n case 'localeUnder':\n output += localeSpec.replace(/-/g, '_');\n break;\n case 'localeLower':\n output += localeSpec.toLowerCase();\n break;\n default:\n // unknown keyword, preserve it unchanged\n output += '[' + keyword + ']';\n break;\n }\n }\n }\n return output;\n}\n\n/**\n * Format a file path using a path template and parameters.\n *\n * This function is used to generate an output file path for a given\n * source file path and a locale specifier.\n * The template replaces strings in square brackets with special values,\n * and keeps any characters intact that are not in square brackets.\n * This function recognizes and replaces the following strings in\n * templates:\n * - [dir] the original directory where the source file\n * came from. This is given as a directory that is relative\n * to the root of the project. eg. \"foo/bar/strings.json\" -> \"foo/bar\"\n * - [filename] the file name of the source file.\n * eg. \"foo/bar/strings.json\" -> \"strings.json\"\n * - [basename] the basename of the source file without any extension\n * eg. \"foo/bar/strings.json\" -> \"strings\"\n * - [extension] the extension part of the file name of the source file.\n * etc. \"foo/bar/strings.json\" -> \"json\"\n * - [locale] the full BCP-47 locale specification for the target locale\n * eg. \"zh-Hans-CN\" -> \"zh-Hans-CN\"\n * - [language] the language portion of the full locale\n * eg. \"zh-Hans-CN\" -> \"zh\"\n * - [script] the script portion of the full locale\n * eg. \"zh-Hans-CN\" -> \"Hans\"\n * - [region] the region portion of the full locale\n * eg. \"zh-Hans-CN\" -> \"CN\"\n * - [localeDir] the full locale where each portion of the locale\n * is a directory in this order: [langage], [script], [region].\n * eg, \"zh-Hans-CN\" -> \"zh/Hans/CN\", but \"en\" -> \"en\".\n * - [localeUnder] the full BCP-47 locale specification, but using\n * underscores to separate the locale parts instead of dashes.\n * eg. \"zh-Hans-CN\" -> \"zh_Hans_CN\"\n * - [localeLower] the full BCP-47 locale specification, but makes\n * all locale parts lowercased.\n * eg. \"zh-Hans-CN\" -> \"zh-hans-cn\"\n *\n * @param {string} template the path template string\n * @param {Object} parameters an object containing:\n * @param {string} parameters.sourcepath the path to the source file, relative to the\n * root of the project\n * @param {string} parameters.locale the locale for the output file path\n * @param {string} parameters.resourceDir optional resource directory to substitute\n * for [resourceDir] in the template\n * @returns {string} the formatted file path\n */\nexport function formatPath(template, parameters) {\n const pathname = parameters.sourcepath || \"\";\n const locale = parameters.locale || \"en\";\n const resourceDir = parameters.resourceDir || \".\";\n\n // First, handle locale-related substitutions without path normalization\n let output = formatLocaleParams(template, locale);\n\n // Now handle path-specific keywords\n let base;\n let lastDot;\n\n output = output.replace(/\\[dir\\]/g, path.dirname(pathname));\n output = output.replace(/\\[filename\\]/g, path.basename(pathname));\n output = output.replace(/\\[resourceDir\\]/g, resourceDir);\n\n if (output.includes('[extension]')) {\n base = path.basename(pathname);\n output = output.replace(/\\[extension\\]/g, base.indexOf('.') > -1 ? base.substring(base.lastIndexOf('.')+1) : \"\");\n }\n\n if (output.includes('[basename]')) {\n base = path.basename(pathname);\n lastDot = base.lastIndexOf('.');\n output = output.replace(/\\[basename\\]/g, lastDot > -1 ? base.substring(0, lastDot) : base);\n }\n\n return path.normalize(output);\n};\n\nconst matchExprs = {\n \"dir\": {\n regex: \"(.*)\",\n brackets: 1,\n groups: {\n dir: 1\n }\n },\n \"basename\": {\n regex: \"(.*?)\",\n brackets: 1,\n groups: {\n basename: 1\n }\n },\n \"resourceDir\": {\n regex: \"(.*?)\",\n brackets: 1,\n groups: {\n resourceDir: 1\n }\n },\n \"extension\": {\n regex: \"(.*)\",\n brackets: 1,\n groups: {\n extension: 1\n }\n },\n \"locale\": {\n regex: \"(([a-z][a-z][a-z]?)(-([A-Z][a-z][a-z][a-z]))?(-([A-Z][A-Z]|[0-9][0-9][0-9]))?)\",\n brackets: 6,\n groups: {\n locale: 1,\n language: 2,\n script: 4,\n region: 6\n }\n },\n \"language\": {\n regex: \"([a-z][a-z][a-z]?)\",\n brackets: 1,\n groups: {\n language: 1\n }\n },\n \"script\": {\n regex: \"([A-Z][a-z][a-z][a-z])\",\n brackets: 1,\n groups: {\n script: 1\n }\n },\n \"region\": {\n regex: \"([A-Z][A-Z]|[0-9][0-9][0-9])\",\n brackets: 1,\n groups: {\n region: 1\n }\n },\n \"localeDir\": {\n regex: \"(([a-z][a-z][a-z]?)(/([A-Z][a-z][a-z][a-z]))?(/([A-Z][A-Z]|[0-9][0-9][0-9]))?)\",\n brackets: 6,\n groups: {\n localeDir: 1,\n language: 2,\n script: 4,\n region: 6\n }\n },\n \"localeUnder\": {\n regex: \"(([a-z][a-z][a-z]?)(_([A-Z][a-z][a-z][a-z]))?(_([A-Z][A-Z]|[0-9][0-9][0-9]))?)\",\n brackets: 6,\n groups: {\n localeUnder: 1,\n language: 2,\n script: 4,\n region: 6\n }\n },\n \"localeLower\": {\n regex: \"(([a-z][a-z][a-z]?)(-([a-z][a-z][a-z][a-z]))?(-([a-z][a-z]|[0-9][0-9][0-9]))?)\",\n brackets: 6,\n groups: {\n localeUnder: 1,\n language: 2,\n script: 4,\n region: 6\n }\n }\n};\n\n/**\n * Parse a path according to the given template, and return the parts.\n * The parts can be any of the fields mentioned in the {@link formatPath}\n * documentation. If any field is not parsed, the result is an empty object\n *\n * @param {String} template the ilib template for matching against the path\n * @param {String} pathname the path name to match against the template\n * @returns {Object} an object mapping the fields to their values in the\n * the pathname\n */\nexport function parsePath(template, pathname) {\n let regex = \"\";\n let matchGroups = {};\n let totalBrackets = 0;\n let base;\n\n if (!template) {\n template = defaultMappings[\"**/*.json\"].template;\n }\n\n for (let i = 0; i < template.length; i++) {\n if ( template[i] !== '[' ) {\n regex += template[i];\n } else {\n let start = ++i;\n while (i < template.length && template[i] !== ']') {\n i++;\n }\n const keyword = template.substring(start, i);\n switch (keyword) {\n case 'filename':\n // escape special characters in the filename so they don't have their special meaning in the regex\n regex += path.basename(pathname || \"\").replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n break;\n default:\n if (!matchExprs[keyword]) {\n logger.warning(\"Warning: template contains unknown substitution parameter \" + keyword);\n return \"\";\n }\n // [dir]/ is optional when followed by / and another token. Always capture dir so callers\n // (e.g. PropertiesParser) can build paths. Use (?:()|(.*?)/) so we require the slash when\n // dir is non-empty - this prevents \"test.properties\" from matching as locale \"est\" with dir \"test/testfiles/t\".\n // For \"./de.po\" the empty alternative matches; for \"test/testfiles/de-DE.properties\" we capture dir.\n // Do NOT make optional for [basename], [extension], [resourceDir] - that would break path parsing.\n let optionalDirSlash = false;\n if (keyword === \"dir\" && i + 1 < template.length && template[i + 1] === \"/\" && i + 2 < template.length && template[i + 2] === \"[\") {\n const endBracket = template.indexOf(\"]\", i + 2);\n const nextKeyword = endBracket > -1 ? template.substring(i + 3, endBracket) : \"\";\n const optionalDirTokens = [\"filename\", \"locale\", \"language\", \"script\", \"region\", \"localeDir\", \"localeUnder\", \"localeLower\"];\n optionalDirSlash = optionalDirTokens.includes(nextKeyword);\n }\n if (keyword === \"dir\" && optionalDirSlash) {\n // (?:()|(.*?)/) - empty dir or (.*?)/ (require slash when dir is non-empty)\n regex += \"(?:()|(.*?)/)\";\n matchGroups.dir = totalBrackets + 2; // second group has dir when present\n totalBrackets += 2;\n i++;\n } else {\n regex += matchExprs[keyword].regex;\n for (let prop in matchExprs[keyword].groups) {\n matchGroups[prop] = totalBrackets + matchExprs[keyword].groups[prop];\n }\n totalBrackets += matchExprs[keyword].brackets;\n }\n break;\n }\n }\n }\n\n // Anchor at start. If path starts with \"./\", allow it so template matches the rest (e.g. \"./ja/foo.mdx\" matches \"[language]/[dir]/[filename]\").\n const prefix = (pathname && pathname.startsWith(\"./\")) ? \"^\\\\.\\\\/\" : \"^\";\n const re = new RegExp(prefix + regex, \"u\");\n const match = re.exec(pathname || \"\");\n if (match !== null) {\n let groups = {};\n let found = false;\n for (let groupName in matchGroups) {\n const idx = matchGroups[groupName];\n const value = idx < match.length ? match[idx] : undefined;\n // Capture empty only for dir (so \"[dir]/[filename]\" with \"foo.mdx\" gives dir \".\"); optional locale parts stay omitted.\n if (value !== undefined && (value || (groupName === \"dir\" && value === \"\"))) {\n groups[groupName] = value === undefined ? \"\" : value;\n found = true;\n }\n }\n if (groups.dir === \"\" || (matchGroups.dir && groups.dir === undefined)) {\n groups.dir = \".\";\n }\n // When path started with \"./\", prefix consumed it; put it back into dir so dir is the full path.\n if (pathname && pathname.startsWith(\"./\") && groups.dir !== undefined && groups.dir !== \".\") {\n groups.dir = \"./\" + groups.dir;\n }\n return groups;\n }\n\n return {};\n}\n\n/**\n * Return a locale encoded in the path using template to parse that path.\n * See {#formatPath} for the full description of the syntax of the template.\n * @param {String} template template for the output file\n * @param {String} pathname path to the source file\n * @returns {String} the locale within the path, or undefined if no locale found\n */\nexport function getLocaleFromPath(template, pathname) {\n const groups = parsePath(template, pathname);\n\n if (groups.locale || groups.language || groups.script || groups.region ) {\n // TODO: Remove script transformation once similar change is implemented in iLib/Locale class.\n if (groups.script && groups.script.length) {\n groups.script = groups.script.charAt(0).toUpperCase() + groups.script.slice(1).toLowerCase();\n }\n\n const l = groups.locale ?\n new Locale(groups.locale) :\n new Locale(groups.language, groups.region, undefined, groups.script);\n\n return l.getSpec();\n }\n\n return \"\";\n};\n\nexport function makeDirs(path) {\n const parts = path.split(/[\\\\\\/]/);\n\n for (let i = 1; i <= parts.length; i++) {\n const p = parts.slice(0, i).join(\"/\");\n if (p && p.length > 0 && !fs.existsSync(p)) {\n fs.mkdirSync(p);\n }\n }\n};\n\n/**\n * Return true if the string still contains some text after removing all HTML tags and entities.\n * @param {string} str the string to check\n * @returns {boolean} true if there is text left over, and false otherwise\n */\nexport function containsActualText(str) {\n // remove the html and entities first\n const cleaned = str.replace(/<(\"(\\\\\"|[^\"])*\"|'(\\\\'|[^'])*'|[^>])*>/g, \"\").replace(/&[a-zA-Z]+;/g, \"\");\n\n for (let i = 0; i < cleaned.length; i++) {\n const c = cleaned.charAt(i);\n if (isAlnum(c) || isIdeo(c)) return true;\n }\n return false;\n};\n\nfunction isPrimitive(type) {\n return [\"boolean\", \"number\", \"integer\", \"string\"].indexOf(type) > -1;\n}\n\n/**\n * Recursively visit every node in an object and call the visitor on any\n * primitive values.\n * @param {*} object any object, arrary, or primitive\n * @param {Function(*)} visitor function to call on any primitive\n * @returns {*} the same type as the original object, but with every\n * primitive processed by the visitor function\n */\nexport function objectMap(object, visitor) {\n if (!object) return object;\n if (isPrimitive(typeof(object))) {\n return visitor(object);\n } else if (Array.isArray(object)) {\n return object.map(item => {\n return objectMap(item, visitor);\n });\n } else {\n const ret = {};\n for (let prop in object) {\n if (object.hasOwnProperty(prop)) {\n ret[prop] = objectMap(object[prop], visitor);\n }\n }\n return ret;\n }\n};\n\n/**\n * Return a standard hash of the given source string.\n *\n * @param {String} source the source string as extracted from the\n * source code, unmodified\n * @returns {String} the hash key\n */\nexport function hashKey(source) {\n if (!source) return undefined;\n let hash = 0;\n // these two numbers together = 46 bits so it won't blow out the precision of an integer in javascript\n const modulus = 1073741789; // largest prime number that fits in 30 bits\n const multiple = 65521; // largest prime that fits in 16 bits, co-prime with the modulus\n\n // logger.trace(\"hash starts off at \" + hash);\n\n for (let i = 0; i < source.length; i++) {\n // logger.trace(\"hash \" + hash + \" char \" + source.charCodeAt(i) + \"=\" + source.charAt(i));\n hash += source.charCodeAt(i);\n hash *= multiple;\n hash %= modulus;\n }\n const value = \"r\" + hash;\n\n // System.out.println(\"String '\" + source + \"' hashes to \" + value);\n\n return value;\n};\n\n/**\n * A hash containing a list of HTML tags that do not\n * cause a break in a resource string. These tags should\n * be included in the middle of the string.\n */\nexport const nonBreakingTags = {\n \"a\": true,\n \"abbr\": true,\n \"b\": true,\n \"bdi\": true,\n \"bdo\": true,\n \"br\": true,\n \"dfn\": true,\n \"del\": true,\n \"em\": true,\n \"i\": true,\n \"ins\": true,\n \"mark\": true,\n \"ruby\": true,\n \"rt\": true,\n \"span\": true,\n \"strong\": true,\n \"sub\": true,\n \"sup\": true,\n \"time\": true,\n \"u\": true,\n \"var\": true,\n \"wbr\": true\n};\n\n/**\n * A hash containing a list of HTML tags that are\n * typically self-closing. That is, in HTML4 and earlier,\n * the close tag was not needed for these.\n */\nexport const selfClosingTags = {\n \"area\": true,\n \"base\": true,\n \"bdi\": true,\n \"bdo\": true,\n \"br\": true,\n \"embed\": true,\n \"hr\": true,\n \"img\": true,\n \"input\": true,\n \"link\": true,\n \"option\": true,\n \"param\": true,\n \"source\": true,\n \"track\": true\n};\n\n/**\n * A hash containing a list of HTML tags where\n * the text content inside of those tags should be\n * ignored for localization purposes. Instead,\n * those contents should just be copied to the\n * localized file unmodified.\n */\nexport const ignoreTags = {\n \"code\": true,\n \"output\": true,\n \"samp\": true,\n \"script\": true,\n \"style\": true\n};\n\n/**\n * List of html5 tags and their attributes that contain localizable strings.\n * The \"*\" indicates it applies to the given attributes on every tag.\n * Also added ARIA attributes to localize for accessibility. For more details,\n * see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/\n */\nexport const localizableAttributes = {\n \"area\": {\"alt\":true},\n \"img\": {\"alt\":true},\n \"input\": {\n \"alt\": true,\n \"placeholder\": true\n },\n \"optgroup\": {\"label\":true},\n \"option\": {\"label\":true},\n \"textarea\": {\"placeholder\":true},\n \"track\": {\"label\":true},\n \"*\": {\n \"title\": true,\n \"aria-braillelabel\": true,\n \"aria-brailleroledescription\": true,\n \"aria-description\": true,\n \"aria-label\": true,\n \"aria-placeholder\": true,\n \"aria-roledescription\": true,\n \"aria-rowindextext\": true,\n \"aria-valuetext\": true\n }\n};\n\n// this is for English and many other languages\nconst defaultPluralCategories = [\n \"one\",\n \"other\"\n];\n\n/**\n * Return the plural categories for the given language.\n * If there is no entry for the given\n * language, then the default English plural categories are returned.\n *\n * @param {string|undefined} language the ISO 639 language code to get the plural\n * categories for\n * @returns {Array} an array of strings containing the names of the plural categories\n */\nexport function getLanguagePluralCategories(language) {\n // make sure to get the language only if someone accidentally sends in a full locale\n const locale = new Locale(language);\n const lang = locale.getLanguage() ?? \"en\";\n return pluralCategories[lang] ?? defaultPluralCategories;\n}\n"],"mappings":"2kBAmBA,IAAAA,GAAA,CAAAC,sBAAA,CAAAC,OAAA,QACA,IAAAC,KAAA,CAAAF,sBAAA,CAAAC,OAAA,UACA,IAAAE,WAAA,CAAAH,sBAAA,CAAAC,OAAA,iBACA,IAAAG,UAAA,CAAAJ,sBAAA,CAAAC,OAAA,6BAEA,IAAAI,UAAA,CAAAJ,OAAA,eAEA,IAAAK,iBAAA,CAAAN,sBAAA,CAAAC,OAAA,2BAAqD,SAAAD,uBAAAO,CAAA,SAAAA,CAAA,EAAAA,CAAA,CAAAC,UAAA,CAAAD,CAAA,YAAAA,CAAA,WAAAE,QAAAC,CAAA,mCAAAD,OAAA,oBAAAE,MAAA,mBAAAA,MAAA,CAAAC,QAAA,UAAAF,CAAA,gBAAAA,CAAA,WAAAA,CAAA,SAAAA,CAAA,qBAAAC,MAAA,EAAAD,CAAA,CAAAG,WAAA,GAAAF,MAAA,EAAAD,CAAA,GAAAC,MAAA,CAAAG,SAAA,iBAAAJ,CAAA,EAAAD,OAAA,CAAAC,CAAA,EA1BrD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,GAWA,GAAM,CAAAK,MAAM,CAAGC,qBAAM,CAACC,SAAS,CAAC,oBAAoB,CAAC,CAErD;AACA;AACA;AACA;AACA;AACA;AACA,GACO,QAAS,CAAAC,WAAWA,CAACC,GAAG,CAAE,CAC7B,GAAI,MAAO,CAAAA,GAAI,GAAK,QAAQ,CAAE,CAC1B,MAAO,CAAAC,SACX,CACA,MAAO,CAAAD,GAAG,CAACE,WAAW,CAAC,CAAC,CACpBC,OAAO,CAAC,MAAM,CAAE,GAAG,CAAC,CACpBA,OAAO,CAAC,MAAM,CAAE,GAAG,CAAC,CACpBA,OAAO,CAAC,KAAK,CAAE,EAAE,CAAC,CAClBA,OAAO,CAAC,MAAM,CAAE,GAAG,CAAC,CACpBC,IAAI,CAAC,CAAC,CACND,OAAO,CAAC,SAAS,CAAE,GAAG,CAAC,CACvBA,OAAO,CAAC,SAAS,CAAE,IAAG,CAAC,CACvBA,OAAO,CAAC,OAAO,CAAE,GAAG,CAAC,CACrBA,OAAO,CAAC,OAAO,CAAE,GAAG,CAAC,CACrBA,OAAO,CAAC,QAAQ,CAAE,GAAG,CAAC,CACtBA,OAAO,CAAC,IAAI,CAAE,GAAG,CACzB,CAAC,CAED;AACA;AACA;AACA;AACA,GACO,QAAS,CAAAE,OAAOA,CAACC,GAAG,CAAE,CACzB,GAAI,CAAAC,IAAI,CAAGN,SAAS,CAEpB,GAAI,CAACK,GAAG,CAAE,CACN,MAAO,KACX,CAEA,IAAKC,IAAI,GAAI,CAAAD,GAAG,CAAE,CACd,GAAIC,IAAI,EAAID,GAAG,CAACC,IAAI,CAAC,CAAE,CACnB,MAAO,MACX,CACJ,CACA,MAAO,KACX,CAAC,CAED;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,GACO,QAAS,CAAAC,kBAAkBA,CAACC,QAAQ,CAAEC,MAAM,CAAE,CACjD,GAAI,CAACD,QAAQ,CAAE,MAAO,EAAE,CACxB,GAAM,CAAAE,CAAC,CAAG,MAAO,CAAAD,MAAM,GAAK,QAAQ,CAAG,GAAI,CAAAE,sBAAM,CAACF,MAAM,EAAI,IAAI,CAAC,CAAGA,MAAM,CAC1E,GAAM,CAAAG,UAAU,CAAGF,CAAC,CAACG,OAAO,CAAC,CAAC,CAC9B,GAAI,CAAAC,MAAM,CAAG,EAAE,CAEf,IAAK,GAAI,CAAAC,CAAC,CAAG,CAAC,CAAEA,CAAC,CAAGP,QAAQ,CAACQ,MAAM,CAAED,CAAC,EAAE,CAAE,CACtC,GAAIP,QAAQ,CAACO,CAAC,CAAC,GAAK,GAAG,CAAE,CACrBD,MAAM,EAAIN,QAAQ,CAACO,CAAC,CACxB,CAAC,IAAM,CACH,GAAI,CAAAE,KAAK,CAAG,EAAEF,CAAC,CACf,MAAOA,CAAC,CAAGP,QAAQ,CAACQ,MAAM,EAAIR,QAAQ,CAACO,CAAC,CAAC,GAAK,GAAG,CAAE,CAC/CA,CAAC,EACL,CACA,GAAM,CAAAG,OAAO,CAAGV,QAAQ,CAACW,SAAS,CAACF,KAAK,CAAEF,CAAC,CAAC,CAC5C,OAAQG,OAAO,EACX,IAAK,QAAQ,CACTJ,MAAM,EAAIF,UAAU,CACpB,MACJ,IAAK,UAAU,CACXE,MAAM,EAAIJ,CAAC,CAACU,WAAW,CAAC,CAAC,EAAI,EAAE,CAC/B,MACJ,IAAK,QAAQ,CACTN,MAAM,EAAIJ,CAAC,CAACW,SAAS,CAAC,CAAC,EAAI,EAAE,CAC7B,MACJ,IAAK,QAAQ,CACTP,MAAM,EAAIJ,CAAC,CAACY,SAAS,CAAC,CAAC,EAAI,EAAE,CAC7B,MACJ,IAAK,WAAW,CACZR,MAAM,EAAIF,UAAU,CAACV,OAAO,CAAC,IAAI,CAAE,GAAG,CAAC,CACvC,MACJ,IAAK,aAAa,CACdY,MAAM,EAAIF,UAAU,CAACV,OAAO,CAAC,IAAI,CAAE,GAAG,CAAC,CACvC,MACJ,IAAK,aAAa,CACdY,MAAM,EAAIF,UAAU,CAACX,WAAW,CAAC,CAAC,CAClC,MACJ,QACI;AACAa,MAAM,EAAI,GAAG,CAAGI,OAAO,CAAG,GAAG,CAC7B,KACR,CACJ,CACJ,CACA,MAAO,CAAAJ,MACX,CAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,GACO,QAAS,CAAAS,UAAUA,CAACf,QAAQ,CAAEgB,UAAU,CAAE,CAC7C,GAAM,CAAAC,QAAQ,CAAGD,UAAU,CAACE,UAAU,EAAI,EAAE,CAC5C,GAAM,CAAAjB,MAAM,CAAGe,UAAU,CAACf,MAAM,EAAI,IAAI,CACxC,GAAM,CAAAkB,WAAW,CAAGH,UAAU,CAACG,WAAW,EAAI,GAAG,CAEjD;AACA,GAAI,CAAAb,MAAM,CAAGP,kBAAkB,CAACC,QAAQ,CAAEC,MAAM,CAAC,CAEjD;AACA,GAAI,CAAAmB,IAAI,CACR,GAAI,CAAAC,OAAO,CAEXf,MAAM,CAAGA,MAAM,CAACZ,OAAO,CAAC,UAAU,CAAE4B,gBAAI,CAACC,OAAO,CAACN,QAAQ,CAAC,CAAC,CAC3DX,MAAM,CAAGA,MAAM,CAACZ,OAAO,CAAC,eAAe,CAAE4B,gBAAI,CAACE,QAAQ,CAACP,QAAQ,CAAC,CAAC,CACjEX,MAAM,CAAGA,MAAM,CAACZ,OAAO,CAAC,kBAAkB,CAAEyB,WAAW,CAAC,CAExD,GAAIb,MAAM,CAACmB,QAAQ,CAAC,aAAa,CAAC,CAAE,CAChCL,IAAI,CAAGE,gBAAI,CAACE,QAAQ,CAACP,QAAQ,CAAC,CAC9BX,MAAM,CAAGA,MAAM,CAACZ,OAAO,CAAC,gBAAgB,CAAE0B,IAAI,CAACM,OAAO,CAAC,GAAG,CAAC,CAAG,CAAC,CAAC,CAAGN,IAAI,CAACT,SAAS,CAACS,IAAI,CAACO,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAG,EAAE,CACnH,CAEA,GAAIrB,MAAM,CAACmB,QAAQ,CAAC,YAAY,CAAC,CAAE,CAC/BL,IAAI,CAAGE,gBAAI,CAACE,QAAQ,CAACP,QAAQ,CAAC,CAC9BI,OAAO,CAAGD,IAAI,CAACO,WAAW,CAAC,GAAG,CAAC,CAC/BrB,MAAM,CAAGA,MAAM,CAACZ,OAAO,CAAC,eAAe,CAAE2B,OAAO,CAAG,CAAC,CAAC,CAAGD,IAAI,CAACT,SAAS,CAAC,CAAC,CAAEU,OAAO,CAAC,CAAGD,IAAI,CAC7F,CAEA,MAAO,CAAAE,gBAAI,CAACM,SAAS,CAACtB,MAAM,CAChC,CAAC,CAED,GAAM,CAAAuB,UAAU,CAAG,CACf,KAAK,CAAE,CACHC,KAAK,CAAE,MAAM,CACbC,QAAQ,CAAE,CAAC,CACXC,MAAM,CAAE,CACJC,GAAG,CAAE,CACT,CACJ,CAAC,CACD,UAAU,CAAE,CACRH,KAAK,CAAE,OAAO,CACdC,QAAQ,CAAE,CAAC,CACXC,MAAM,CAAE,CACJR,QAAQ,CAAE,CACd,CACJ,CAAC,CACD,aAAa,CAAE,CACXM,KAAK,CAAE,OAAO,CACdC,QAAQ,CAAE,CAAC,CACXC,MAAM,CAAE,CACJb,WAAW,CAAE,CACjB,CACJ,CAAC,CACD,WAAW,CAAE,CACTW,KAAK,CAAE,MAAM,CACbC,QAAQ,CAAE,CAAC,CACXC,MAAM,CAAE,CACJE,SAAS,CAAE,CACf,CACJ,CAAC,CACD,QAAQ,CAAE,CACNJ,KAAK,CAAE,gFAAgF,CACvFC,QAAQ,CAAE,CAAC,CACXC,MAAM,CAAE,CACJ/B,MAAM,CAAE,CAAC,CACTkC,QAAQ,CAAE,CAAC,CACXC,MAAM,CAAE,CAAC,CACTC,MAAM,CAAE,CACZ,CACJ,CAAC,CACD,UAAU,CAAE,CACRP,KAAK,CAAE,oBAAoB,CAC3BC,QAAQ,CAAE,CAAC,CACXC,MAAM,CAAE,CACJG,QAAQ,CAAE,CACd,CACJ,CAAC,CACD,QAAQ,CAAE,CACNL,KAAK,CAAE,wBAAwB,CAC/BC,QAAQ,CAAE,CAAC,CACXC,MAAM,CAAE,CACJI,MAAM,CAAE,CACZ,CACJ,CAAC,CACD,QAAQ,CAAE,CACNN,KAAK,CAAE,8BAA8B,CACrCC,QAAQ,CAAE,CAAC,CACXC,MAAM,CAAE,CACJK,MAAM,CAAE,CACZ,CACJ,CAAC,CACD,WAAW,CAAE,CACTP,KAAK,CAAE,gFAAgF,CACvFC,QAAQ,CAAE,CAAC,CACXC,MAAM,CAAE,CACJM,SAAS,CAAE,CAAC,CACZH,QAAQ,CAAE,CAAC,CACXC,MAAM,CAAE,CAAC,CACTC,MAAM,CAAE,CACZ,CACJ,CAAC,CACD,aAAa,CAAE,CACXP,KAAK,CAAE,gFAAgF,CACvFC,QAAQ,CAAE,CAAC,CACXC,MAAM,CAAE,CACJO,WAAW,CAAE,CAAC,CACdJ,QAAQ,CAAE,CAAC,CACXC,MAAM,CAAE,CAAC,CACTC,MAAM,CAAE,CACZ,CACJ,CAAC,CACD,aAAa,CAAE,CACXP,KAAK,CAAE,gFAAgF,CACvFC,QAAQ,CAAE,CAAC,CACXC,MAAM,CAAE,CACJO,WAAW,CAAE,CAAC,CACdJ,QAAQ,CAAE,CAAC,CACXC,MAAM,CAAE,CAAC,CACTC,MAAM,CAAE,CACZ,CACJ,CACJ,CAAC,CAED;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,GACO,QAAS,CAAAG,SAASA,CAACxC,QAAQ,CAAEiB,QAAQ,CAAE,CAC1C,GAAI,CAAAa,KAAK,CAAG,EAAE,CACd,GAAI,CAAAW,WAAW,CAAG,CAAC,CAAC,CACpB,GAAI,CAAAC,aAAa,CAAG,CAAC,CACrB,GAAI,CAAAtB,IAAI,CAER,GAAI,CAACpB,QAAQ,CAAE,CACXA,QAAQ,CAAG2C,eAAe,CAAC,WAAW,CAAC,CAAC3C,QAC5C,CAEA,IAAK,GAAI,CAAAO,CAAC,CAAG,CAAC,CAAEA,CAAC,CAAGP,QAAQ,CAACQ,MAAM,CAAED,CAAC,EAAE,CAAE,CACtC,GAAKP,QAAQ,CAACO,CAAC,CAAC,GAAK,GAAG,CAAG,CACvBuB,KAAK,EAAI9B,QAAQ,CAACO,CAAC,CACvB,CAAC,IAAM,CACH,GAAI,CAAAE,KAAK,CAAG,EAAEF,CAAC,CACf,MAAOA,CAAC,CAAGP,QAAQ,CAACQ,MAAM,EAAIR,QAAQ,CAACO,CAAC,CAAC,GAAK,GAAG,CAAE,CAC/CA,CAAC,EACL,CACA,GAAM,CAAAG,OAAO,CAAGV,QAAQ,CAACW,SAAS,CAACF,KAAK,CAAEF,CAAC,CAAC,CAC5C,OAAQG,OAAO,EACX,IAAK,UAAU,CACX;AACAoB,KAAK,EAAIR,gBAAI,CAACE,QAAQ,CAACP,QAAQ,EAAI,EAAE,CAAC,CAACvB,OAAO,CAAC,qBAAqB,CAAE,MAAM,CAAC,CAC7E,MACJ,QACI,GAAI,CAACmC,UAAU,CAACnB,OAAO,CAAC,CAAE,CACtBvB,MAAM,CAACyD,OAAO,CAAC,4DAA4D,CAAGlC,OAAO,CAAC,CACtF,MAAO,EACX,CACA;AACA;AACA;AACA;AACA;AACA,GAAI,CAAAmC,gBAAgB,CAAG,KAAK,CAC5B,GAAInC,OAAO,GAAK,KAAK,EAAIH,CAAC,CAAG,CAAC,CAAGP,QAAQ,CAACQ,MAAM,EAAIR,QAAQ,CAACO,CAAC,CAAG,CAAC,CAAC,GAAK,GAAG,EAAIA,CAAC,CAAG,CAAC,CAAGP,QAAQ,CAACQ,MAAM,EAAIR,QAAQ,CAACO,CAAC,CAAG,CAAC,CAAC,GAAK,GAAG,CAAE,CAC/H,GAAM,CAAAuC,UAAU,CAAG9C,QAAQ,CAAC0B,OAAO,CAAC,GAAG,CAAEnB,CAAC,CAAG,CAAC,CAAC,CAC/C,GAAM,CAAAwC,WAAW,CAAGD,UAAU,CAAG,CAAC,CAAC,CAAG9C,QAAQ,CAACW,SAAS,CAACJ,CAAC,CAAG,CAAC,CAAEuC,UAAU,CAAC,CAAG,EAAE,CAChF,GAAM,CAAAE,iBAAiB,CAAG,CAAC,UAAU,CAAE,QAAQ,CAAE,UAAU,CAAE,QAAQ,CAAE,QAAQ,CAAE,WAAW,CAAE,aAAa,CAAE,aAAa,CAAC,CAC3HH,gBAAgB,CAAGG,iBAAiB,CAACvB,QAAQ,CAACsB,WAAW,CAC7D,CACA,GAAIrC,OAAO,GAAK,KAAK,EAAImC,gBAAgB,CAAE,CACvC;AACAf,KAAK,EAAI,eAAe,CACxBW,WAAW,CAACR,GAAG,CAAGS,aAAa,CAAG,CAAC,CAAE;AACrCA,aAAa,EAAI,CAAC,CAClBnC,CAAC,EACL,CAAC,IAAM,CACHuB,KAAK,EAAID,UAAU,CAACnB,OAAO,CAAC,CAACoB,KAAK,CAClC,IAAK,GAAI,CAAAhC,IAAI,GAAI,CAAA+B,UAAU,CAACnB,OAAO,CAAC,CAACsB,MAAM,CAAE,CACzCS,WAAW,CAAC3C,IAAI,CAAC,CAAG4C,aAAa,CAAGb,UAAU,CAACnB,OAAO,CAAC,CAACsB,MAAM,CAAClC,IAAI,CACvE,CACA4C,aAAa,EAAIb,UAAU,CAACnB,OAAO,CAAC,CAACqB,QACzC,CACA,KACR,CACJ,CACJ,CAEA;AACA,GAAM,CAAAkB,MAAM,CAAIhC,QAAQ,EAAIA,QAAQ,CAACiC,UAAU,CAAC,IAAI,CAAC,CAAI,SAAS,CAAG,GAAG,CACxE,GAAM,CAAAC,EAAE,CAAG,GAAI,CAAAC,MAAM,CAACH,MAAM,CAAGnB,KAAK,CAAE,GAAG,CAAC,CAC1C,GAAM,CAAAuB,KAAK,CAAGF,EAAE,CAACG,IAAI,CAACrC,QAAQ,EAAI,EAAE,CAAC,CACrC,GAAIoC,KAAK,GAAK,IAAI,CAAE,CAChB,GAAI,CAAArB,MAAM,CAAG,CAAC,CAAC,CACf,GAAI,CAAAuB,KAAK,CAAG,KAAK,CACjB,IAAK,GAAI,CAAAC,SAAS,GAAI,CAAAf,WAAW,CAAE,CAC/B,GAAM,CAAAgB,GAAG,CAAGhB,WAAW,CAACe,SAAS,CAAC,CAClC,GAAM,CAAAE,KAAK,CAAGD,GAAG,CAAGJ,KAAK,CAAC7C,MAAM,CAAG6C,KAAK,CAACI,GAAG,CAAC,CAAGjE,SAAS,CACzD;AACA,GAAIkE,KAAK,GAAKlE,SAAS,GAAKkE,KAAK,EAAKF,SAAS,GAAK,KAAK,EAAIE,KAAK,GAAK,EAAG,CAAC,CAAE,CACzE1B,MAAM,CAACwB,SAAS,CAAC,CAAGE,KAAK,GAAKlE,SAAS,CAAG,EAAE,CAAGkE,KAAK,CACpDH,KAAK,CAAG,IACZ,CACJ,CACA,GAAIvB,MAAM,CAACC,GAAG,GAAK,EAAE,EAAKQ,WAAW,CAACR,GAAG,EAAID,MAAM,CAACC,GAAG,GAAKzC,SAAU,CAAE,CACpEwC,MAAM,CAACC,GAAG,CAAG,GACjB,CACA;AACA,GAAIhB,QAAQ,EAAIA,QAAQ,CAACiC,UAAU,CAAC,IAAI,CAAC,EAAIlB,MAAM,CAACC,GAAG,GAAKzC,SAAS,EAAIwC,MAAM,CAACC,GAAG,GAAK,GAAG,CAAE,CACzFD,MAAM,CAACC,GAAG,CAAG,IAAI,CAAGD,MAAM,CAACC,GAC/B,CACA,MAAO,CAAAD,MACX,CAEA,MAAO,CAAC,CACZ,CAEA;AACA;AACA;AACA;AACA;AACA;AACA,GACO,QAAS,CAAA2B,iBAAiBA,CAAC3D,QAAQ,CAAEiB,QAAQ,CAAE,CAClD,GAAM,CAAAe,MAAM,CAAGQ,SAAS,CAACxC,QAAQ,CAAEiB,QAAQ,CAAC,CAE5C,GAAIe,MAAM,CAAC/B,MAAM,EAAI+B,MAAM,CAACG,QAAQ,EAAIH,MAAM,CAACI,MAAM,EAAIJ,MAAM,CAACK,MAAM,CAAG,CACrE;AACA,GAAIL,MAAM,CAACI,MAAM,EAAIJ,MAAM,CAACI,MAAM,CAAC5B,MAAM,CAAE,CACvCwB,MAAM,CAACI,MAAM,CAAGJ,MAAM,CAACI,MAAM,CAACwB,MAAM,CAAC,CAAC,CAAC,CAACC,WAAW,CAAC,CAAC,CAAG7B,MAAM,CAACI,MAAM,CAAC0B,KAAK,CAAC,CAAC,CAAC,CAACrE,WAAW,CAAC,CAC/F,CAEA,GAAM,CAAAS,CAAC,CAAG8B,MAAM,CAAC/B,MAAM,CACnB,GAAI,CAAAE,sBAAM,CAAC6B,MAAM,CAAC/B,MAAM,CAAC,CACzB,GAAI,CAAAE,sBAAM,CAAC6B,MAAM,CAACG,QAAQ,CAAEH,MAAM,CAACK,MAAM,CAAE7C,SAAS,CAAEwC,MAAM,CAACI,MAAM,CAAC,CAExE,MAAO,CAAAlC,CAAC,CAACG,OAAO,CAAC,CACrB,CAEA,MAAO,EACX,CAAC,CAEM,QAAS,CAAA0D,QAAQA,CAACzC,IAAI,CAAE,CAC3B,GAAM,CAAA0C,KAAK,CAAG1C,IAAI,CAAC2C,KAAK,CAAC,QAAQ,CAAC,CAElC,IAAK,GAAI,CAAA1D,CAAC,CAAG,CAAC,CAAEA,CAAC,EAAIyD,KAAK,CAACxD,MAAM,CAAED,CAAC,EAAE,CAAE,CACpC,GAAM,CAAA2D,CAAC,CAAGF,KAAK,CAACF,KAAK,CAAC,CAAC,CAAEvD,CAAC,CAAC,CAAC4D,IAAI,CAAC,GAAG,CAAC,CACrC,GAAID,CAAC,EAAIA,CAAC,CAAC1D,MAAM,CAAG,CAAC,EAAI,CAAC4D,cAAE,CAACC,UAAU,CAACH,CAAC,CAAC,CAAE,CACxCE,cAAE,CAACE,SAAS,CAACJ,CAAC,CAClB,CACJ,CACJ,CAAC,CAED;AACA;AACA;AACA;AACA,GACO,QAAS,CAAAK,kBAAkBA,CAAChF,GAAG,CAAE,CACpC;AACA,GAAM,CAAAiF,OAAO,CAAGjF,GAAG,CAACG,OAAO,CAAC,wCAAwC,CAAE,EAAE,CAAC,CAACA,OAAO,CAAC,cAAc,CAAE,EAAE,CAAC,CAErG,IAAK,GAAI,CAAAa,CAAC,CAAG,CAAC,CAAEA,CAAC,CAAGiE,OAAO,CAAChE,MAAM,CAAED,CAAC,EAAE,CAAE,CACrC,GAAM,CAAAkE,CAAC,CAAGD,OAAO,CAACZ,MAAM,CAACrD,CAAC,CAAC,CAC3B,GAAI,GAAAmE,kBAAO,EAACD,CAAC,CAAC,EAAI,GAAAE,iBAAM,EAACF,CAAC,CAAC,CAAE,MAAO,KACxC,CACA,MAAO,MACX,CAAC,CAED,QAAS,CAAAG,WAAWA,CAACC,IAAI,CAAE,CACvB,MAAO,CAAC,SAAS,CAAE,QAAQ,CAAE,SAAS,CAAE,QAAQ,CAAC,CAACnD,OAAO,CAACmD,IAAI,CAAC,CAAG,CAAC,CACvE,CAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,GACO,QAAS,CAAAC,SAASA,CAACC,MAAM,CAAEC,OAAO,CAAE,CACvC,GAAI,CAACD,MAAM,CAAE,MAAO,CAAAA,MAAM,CAC1B,GAAIH,WAAW,CAAA/F,OAAA,CAAQkG,MAAM,CAAC,CAAC,CAAE,CAC7B,MAAO,CAAAC,OAAO,CAACD,MAAM,CACzB,CAAC,IAAM,IAAIE,KAAK,CAACC,OAAO,CAACH,MAAM,CAAC,CAAE,CAC9B,MAAO,CAAAA,MAAM,CAACI,GAAG,CAAC,SAAAC,IAAI,CAAI,CACtB,MAAO,CAAAN,SAAS,CAACM,IAAI,CAAEJ,OAAO,CAClC,CAAC,CACL,CAAC,IAAM,CACH,GAAM,CAAAK,GAAG,CAAG,CAAC,CAAC,CACd,IAAK,GAAI,CAAAvF,IAAI,GAAI,CAAAiF,MAAM,CAAE,CACrB,GAAIA,MAAM,CAACO,cAAc,CAACxF,IAAI,CAAC,CAAE,CAC7BuF,GAAG,CAACvF,IAAI,CAAC,CAAGgF,SAAS,CAACC,MAAM,CAACjF,IAAI,CAAC,CAAEkF,OAAO,CAC/C,CACJ,CACA,MAAO,CAAAK,GACX,CACJ,CAAC,CAED;AACA;AACA;AACA;AACA;AACA;AACA,GACO,QAAS,CAAAE,OAAOA,CAACC,MAAM,CAAE,CAC5B,GAAI,CAACA,MAAM,CAAE,MAAO,CAAAhG,SAAS,CAC7B,GAAI,CAAAiG,IAAI,CAAG,CAAC,CACZ;AACA,GAAM,CAAAC,OAAO,CAAG,UAAU,CAAG;AAC7B,GAAM,CAAAC,QAAQ,CAAG,KAAK,CAAO;AAE7B;AAEA,IAAK,GAAI,CAAApF,CAAC,CAAG,CAAC,CAAEA,CAAC,CAAGiF,MAAM,CAAChF,MAAM,CAAED,CAAC,EAAE,CAAE,CACpC;AACAkF,IAAI,EAAID,MAAM,CAACI,UAAU,CAACrF,CAAC,CAAC,CAC5BkF,IAAI,EAAIE,QAAQ,CAChBF,IAAI,EAAIC,OACZ,CACA,GAAM,CAAAhC,KAAK,CAAG,GAAG,CAAG+B,IAAI,CAExB;AAEA,MAAO,CAAA/B,KACX,CAAC,CAED;AACA;AACA;AACA;AACA,GACO,GAAM,CAAAmC,eAAe,CAAAC,OAAA,CAAAD,eAAA,CAAG,CAC3B,GAAG,CAAE,IAAI,CACT,MAAM,CAAE,IAAI,CACZ,GAAG,CAAE,IAAI,CACT,KAAK,CAAE,IAAI,CACX,KAAK,CAAE,IAAI,CACX,IAAI,CAAE,IAAI,CACV,KAAK,CAAE,IAAI,CACX,KAAK,CAAE,IAAI,CACX,IAAI,CAAE,IAAI,CACV,GAAG,CAAE,IAAI,CACT,KAAK,CAAE,IAAI,CACX,MAAM,CAAE,IAAI,CACZ,MAAM,CAAE,IAAI,CACZ,IAAI,CAAE,IAAI,CACV,MAAM,CAAE,IAAI,CACZ,QAAQ,CAAE,IAAI,CACd,KAAK,CAAE,IAAI,CACX,KAAK,CAAE,IAAI,CACX,MAAM,CAAE,IAAI,CACZ,GAAG,CAAE,IAAI,CACT,KAAK,CAAE,IAAI,CACX,KAAK,CAAE,IACX,CAAC,CAED;AACA;AACA;AACA;AACA,GACO,GAAM,CAAAE,eAAe,CAAAD,OAAA,CAAAC,eAAA,CAAG,CAC3B,MAAM,CAAE,IAAI,CACZ,MAAM,CAAE,IAAI,CACZ,KAAK,CAAE,IAAI,CACX,KAAK,CAAE,IAAI,CACX,IAAI,CAAE,IAAI,CACV,OAAO,CAAE,IAAI,CACb,IAAI,CAAE,IAAI,CACV,KAAK,CAAE,IAAI,CACX,OAAO,CAAE,IAAI,CACb,MAAM,CAAE,IAAI,CACZ,QAAQ,CAAE,IAAI,CACd,OAAO,CAAE,IAAI,CACb,QAAQ,CAAE,IAAI,CACd,OAAO,CAAE,IACb,CAAC,CAED;AACA;AACA;AACA;AACA;AACA;AACA,GACO,GAAM,CAAAC,UAAU,CAAAF,OAAA,CAAAE,UAAA,CAAG,CACtB,MAAM,CAAE,IAAI,CACZ,QAAQ,CAAE,IAAI,CACd,MAAM,CAAE,IAAI,CACZ,QAAQ,CAAE,IAAI,CACd,OAAO,CAAE,IACb,CAAC,CAED;AACA;AACA;AACA;AACA;AACA,GACO,GAAM,CAAAC,qBAAqB,CAAAH,OAAA,CAAAG,qBAAA,CAAG,CACjC,MAAM,CAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CACpB,KAAK,CAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CACnB,OAAO,CAAE,CACL,KAAK,CAAE,IAAI,CACX,aAAa,CAAE,IACnB,CAAC,CACD,UAAU,CAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAC1B,QAAQ,CAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CACxB,UAAU,CAAE,CAAC,aAAa,CAAC,IAAI,CAAC,CAChC,OAAO,CAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CACvB,GAAG,CAAE,CACD,OAAO,CAAE,IAAI,CACb,mBAAmB,CAAE,IAAI,CACzB,6BAA6B,CAAE,IAAI,CACnC,kBAAkB,CAAE,IAAI,CACxB,YAAY,CAAE,IAAI,CAClB,kBAAkB,CAAE,IAAI,CACxB,sBAAsB,CAAE,IAAI,CAC5B,mBAAmB,CAAE,IAAI,CACzB,gBAAgB,CAAE,IACtB,CACJ,CAAC,CAED;AACA,GAAM,CAAAC,uBAAuB,CAAG,CAC5B,KAAK,CACL,OAAO,CACV,CAED;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,GACO,QAAS,CAAAC,2BAA2BA,CAAChE,QAAQ,CAAE,KAAAiE,mBAAA,CAAAC,qBAAA,CAClD;AACA,GAAM,CAAApG,MAAM,CAAG,GAAI,CAAAE,sBAAM,CAACgC,QAAQ,CAAC,CACnC,GAAM,CAAAmE,IAAI,EAAAF,mBAAA,CAAGnG,MAAM,CAACW,WAAW,CAAC,CAAC,UAAAwF,mBAAA,UAAAA,mBAAA,CAAI,IAAI,CACzC,OAAAC,qBAAA,CAAOE,4BAAgB,CAACD,IAAI,CAAC,UAAAD,qBAAA,UAAAA,qBAAA,CAAIH,uBACrC","ignoreList":[]}
1
+ {"version":3,"file":"utils.js","names":["_fs","_interopRequireDefault","require","_path","_ilibLocale","_log4jsApi","_ilibCtype","_pluralCategories","e","__esModule","_typeof","o","Symbol","iterator","constructor","prototype","logger","log4js","getLogger","cleanString","str","undefined","toLowerCase","replace","trim","isEmpty","obj","prop","formatLocaleParams","template","locale","l","Locale","localeSpec","getSpec","output","i","length","start","keyword","substring","getLanguage","getScript","getRegion","formatPath","parameters","_parameters$dir","_parameters$basename","_parameters$extension","_parameters$filename","pathname","sourcepath","resourceDir","pathParts","fillPartialPathParts","dir","basename","extension","filename","path","normalize","matchExprs","regex","brackets","groups","language","script","region","localeDir","localeUnder","dirname","lastDot","lastIndexOf","fillLocalePartsFromSource","sourceLocale","result","parsePath","matchGroups","totalBrackets","base","defaultMappings","c","warning","nextEnd","indexOf","nextKeyword","optionalDirSlash","endBracket","optionalDirTokens","includes","prefix","startsWith","re","RegExp","match","exec","found","groupName","idx","value","partial","Object","assign","getLocaleFromPath","charAt","toUpperCase","slice","makeDirs","parts","split","p","join","fs","existsSync","mkdirSync","containsActualText","cleaned","isAlnum","isIdeo","isPrimitive","type","objectMap","object","visitor","Array","isArray","map","item","ret","hasOwnProperty","hashKey","source","hash","modulus","multiple","charCodeAt","nonBreakingTags","exports","selfClosingTags","ignoreTags","localizableAttributes","defaultPluralCategories","getLanguagePluralCategories","_locale$getLanguage","_pluralCategories$lan","lang","pluralCategories"],"sources":["../src/utils.js"],"sourcesContent":["/*\n * utils.js - utility functions to support the other code\n *\n * Copyright © 2022-2023, 2025-2026 JEDLSoft\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport fs from 'fs';\nimport path from 'path';\nimport Locale from 'ilib-locale';\nimport log4js from '@log4js-node/log4js-api';\n\nimport { isAlnum, isIdeo } from 'ilib-ctype';\n\nimport pluralCategories from './pluralCategories.js';\n\nconst logger = log4js.getLogger('tools-common.utils');\n\n/**\n * Clean a string for matching against other strings by removing\n * differences that are inconsequential for translation.\n *\n * @param {String} str string to clean\n * @returns {String} the cleaned string\n */\nexport function cleanString(str) {\n if (typeof(str) !== 'string') {\n return undefined;\n }\n return str.toLowerCase().\n replace(/\\\\n/g, \" \").\n replace(/\\\\t/g, \" \").\n replace(/\\\\/g, \"\").\n replace(/\\s+/g, \" \").\n trim().\n replace(/&apos;/g, \"'\").\n replace(/&quot;/g, '\"').\n replace(/&lt;/g, \"<\").\n replace(/&gt;/g, \">\").\n replace(/&amp;/g, \"&\").\n replace(/’/g, \"'\");\n};\n\n/**\n * Is an empty object or not\n * @param {Object} obj object to test\n * @returns {Boolean} true if there are no properties, false otherwise\n */\nexport function isEmpty(obj) {\n let prop = undefined;\n\n if (!obj) {\n return true;\n }\n\n for (prop in obj) {\n if (prop && obj[prop]) {\n return false;\n }\n }\n return true;\n};\n\n/**\n * Format a string template with locale-related parameters.\n *\n * This function substitutes locale-related placeholders in a template string\n * without treating the string as a file path (no path normalization).\n * This is useful for formatting headers, footers, or other strings that\n * contain locale placeholders but should not be treated as file paths.\n *\n * This function recognizes and replaces the following strings in\n * templates:\n * - [locale] the full BCP-47 locale specification for the target locale\n * eg. \"zh-Hans-CN\" -> \"zh-Hans-CN\"\n * - [language] the language portion of the full locale\n * eg. \"zh-Hans-CN\" -> \"zh\"\n * - [script] the script portion of the full locale\n * eg. \"zh-Hans-CN\" -> \"Hans\"\n * - [region] the region portion of the full locale\n * eg. \"zh-Hans-CN\" -> \"CN\"\n * - [localeDir] the full locale where each portion of the locale\n * is a directory in this order: [langage], [script], [region].\n * eg, \"zh-Hans-CN\" -> \"zh/Hans/CN\", but \"en\" -> \"en\".\n * - [localeUnder] the full BCP-47 locale specification, but using\n * underscores to separate the locale parts instead of dashes.\n * eg. \"zh-Hans-CN\" -> \"zh_Hans_CN\"\n * - [localeLower] the full BCP-47 locale specification, but makes\n * all locale parts lowercased.\n * eg. \"zh-Hans-CN\" -> \"zh-hans-cn\"\n *\n * Unknown keywords are preserved in the output unchanged.\n *\n * @param {string} template the template string to format\n * @param {string|Object} locale the locale specifier, either as a string\n * or as a Locale object\n * @returns {string} the formatted string with locale placeholders replaced\n */\nexport function formatLocaleParams(template, locale) {\n if (!template) return \"\";\n const l = typeof locale === 'string' ? new Locale(locale || \"en\") : locale;\n const localeSpec = l.getSpec();\n let output = \"\";\n\n for (let i = 0; i < template.length; i++) {\n if (template[i] !== '[') {\n output += template[i];\n } else {\n let start = ++i;\n while (i < template.length && template[i] !== ']') {\n i++;\n }\n const keyword = template.substring(start, i);\n switch (keyword) {\n case 'locale':\n output += localeSpec;\n break;\n case 'language':\n output += l.getLanguage() || \"\";\n break;\n case 'script':\n output += l.getScript() || \"\";\n break;\n case 'region':\n output += l.getRegion() || \"\";\n break;\n case 'localeDir':\n output += localeSpec.replace(/-/g, '/');\n break;\n case 'localeUnder':\n output += localeSpec.replace(/-/g, '_');\n break;\n case 'localeLower':\n output += localeSpec.toLowerCase();\n break;\n default:\n // unknown keyword, preserve it unchanged\n output += '[' + keyword + ']';\n break;\n }\n }\n }\n return output;\n}\n\n/**\n * Format a file path using a path template and parameters.\n *\n * This function is used to generate an output file path for a given\n * source file path and a locale specifier.\n * The template replaces strings in square brackets with special values,\n * and keeps any characters intact that are not in square brackets.\n * This function recognizes and replaces the following strings in\n * templates:\n * - [dir] the original directory where the source file\n * came from. This is given as a directory that is relative\n * to the root of the project. eg. \"foo/bar/strings.json\" -> \"foo/bar\"\n * - [filename] the file name of the source file.\n * eg. \"foo/bar/strings.json\" -> \"strings.json\"\n * - [basename] the basename of the source file without any extension\n * eg. \"foo/bar/strings.json\" -> \"strings\"\n * - [extension] the extension part of the file name of the source file.\n * etc. \"foo/bar/strings.json\" -> \"json\"\n * - [locale] the full BCP-47 locale specification for the target locale\n * eg. \"zh-Hans-CN\" -> \"zh-Hans-CN\"\n * - [language] the language portion of the full locale\n * eg. \"zh-Hans-CN\" -> \"zh\"\n * - [script] the script portion of the full locale\n * eg. \"zh-Hans-CN\" -> \"Hans\"\n * - [region] the region portion of the full locale\n * eg. \"zh-Hans-CN\" -> \"CN\"\n * - [localeDir] the full locale where each portion of the locale\n * is a directory in this order: [langage], [script], [region].\n * eg, \"zh-Hans-CN\" -> \"zh/Hans/CN\", but \"en\" -> \"en\".\n * - [localeUnder] the full BCP-47 locale specification, but using\n * underscores to separate the locale parts instead of dashes.\n * eg. \"zh-Hans-CN\" -> \"zh_Hans_CN\"\n * - [localeLower] the full BCP-47 locale specification, but makes\n * all locale parts lowercased.\n * eg. \"zh-Hans-CN\" -> \"zh-hans-cn\"\n *\n * @param {string} template the path template string\n * @param {Object} parameters an object containing:\n * @param {string} parameters.sourcepath the path to the source file, relative to the\n * root of the project (used when path parts are not provided)\n * @param {string} parameters.locale the locale for the output file path\n * @param {string} parameters.resourceDir optional resource directory to substitute\n * for [resourceDir] in the template\n * @param {string} parameters.dir optional pre-parsed directory (e.g. from parsePath)\n * @param {string} parameters.basename optional pre-parsed basename without extension\n * @param {string} parameters.extension optional pre-parsed file extension\n * @param {string} parameters.filename optional pre-parsed filename (basename + extension)\n * When any of dir, basename, extension, or filename are provided, they are used\n * instead of deriving from sourcepath. This allows using parsePath output directly.\n * @returns {string} the formatted file path\n */\nexport function formatPath(template, parameters) {\n const pathname = parameters.sourcepath || \"\";\n const locale = parameters.locale || \"en\";\n const resourceDir = parameters.resourceDir || \".\";\n\n // Use pre-parsed path parts when provided (e.g. from parsePath); otherwise derive from sourcepath\n const pathParts = fillPartialPathParts(pathname);\n const dir = parameters.dir ?? pathParts.dir;\n const basename = parameters.basename ?? pathParts.basename;\n const extension = parameters.extension ?? pathParts.extension;\n const filename = parameters.filename ?? (extension ? basename + \".\" + extension : basename);\n\n // First, handle locale-related substitutions without path normalization\n let output = formatLocaleParams(template, locale);\n\n // Handle [basename].[extension] as a unit to avoid trailing dot when extension is empty\n output = output.replace(/\\[basename\\]\\.\\[extension\\]/g, extension ? basename + \".\" + extension : basename);\n\n // Now handle path-specific keywords\n output = output.replace(/\\[dir\\]/g, dir);\n output = output.replace(/\\[filename\\]/g, filename);\n output = output.replace(/\\[resourceDir\\]/g, resourceDir);\n output = output.replace(/\\[extension\\]/g, extension);\n output = output.replace(/\\[basename\\]/g, basename);\n\n return path.normalize(output);\n};\n\nconst matchExprs = {\n \"dir\": {\n regex: \"(.*)\",\n brackets: 1,\n groups: {\n dir: 1\n }\n },\n \"basename\": {\n regex: \"(.*?)\",\n brackets: 1,\n groups: {\n basename: 1\n }\n },\n \"resourceDir\": {\n regex: \"(.*?)\",\n brackets: 1,\n groups: {\n resourceDir: 1\n }\n },\n \"extension\": {\n regex: \"(.*)\",\n brackets: 1,\n groups: {\n extension: 1\n }\n },\n \"locale\": {\n regex: \"(([a-z][a-z][a-z]?)(-([A-Z][a-z][a-z][a-z]))?(-([A-Z][A-Z]|[0-9][0-9][0-9]))?)\",\n brackets: 6,\n groups: {\n locale: 1,\n language: 2,\n script: 4,\n region: 6\n }\n },\n \"language\": {\n regex: \"([a-z][a-z][a-z]?)\",\n brackets: 1,\n groups: {\n language: 1\n }\n },\n \"script\": {\n regex: \"([A-Z][a-z][a-z][a-z])\",\n brackets: 1,\n groups: {\n script: 1\n }\n },\n \"region\": {\n regex: \"([A-Z][A-Z]|[0-9][0-9][0-9])\",\n brackets: 1,\n groups: {\n region: 1\n }\n },\n \"localeDir\": {\n regex: \"(([a-z][a-z][a-z]?)(/([A-Z][a-z][a-z][a-z]))?(/([A-Z][A-Z]|[0-9][0-9][0-9]))?)\",\n brackets: 6,\n groups: {\n localeDir: 1,\n language: 2,\n script: 4,\n region: 6\n }\n },\n \"localeUnder\": {\n regex: \"(([a-z][a-z][a-z]?)(_([A-Z][a-z][a-z][a-z]))?(_([A-Z][A-Z]|[0-9][0-9][0-9]))?)\",\n brackets: 6,\n groups: {\n localeUnder: 1,\n language: 2,\n script: 4,\n region: 6\n }\n },\n \"localeLower\": {\n regex: \"(([a-z][a-z][a-z]?)(-([a-z][a-z][a-z][a-z]))?(-([a-z][a-z]|[0-9][0-9][0-9]))?)\",\n brackets: 6,\n groups: {\n localeUnder: 1,\n language: 2,\n script: 4,\n region: 6\n }\n }\n};\n\n/**\n * Derive dir, basename, extension, and whole filename from a pathname using path operations.\n * Used when the template does not match so we can still fill path parts.\n *\n * @param {String} pathname the path name\n * @returns {Object} { dir, basename, extension, filename }\n */\nfunction fillPartialPathParts(pathname) {\n if (!pathname) {\n return { dir: \".\", basename: \"\", extension: \"\" };\n }\n const dir = path.dirname(pathname) || \".\";\n const filename = path.basename(pathname);\n const lastDot = filename.lastIndexOf(\".\");\n const basename = lastDot > -1 ? filename.substring(0, lastDot) : filename;\n const extension = lastDot > -1 ? filename.substring(lastDot + 1) : \"\";\n return { dir, basename, extension, filename };\n}\n\n/**\n * Fill locale, language, script, region from a source locale string using Locale class.\n *\n * @param {String} sourceLocale BCP-47 locale string\n * @returns {Object} { locale, language, script?, region? }\n */\nfunction fillLocalePartsFromSource(sourceLocale) {\n if (!sourceLocale) {\n return {};\n }\n const l = new Locale(sourceLocale);\n const result = {\n locale: l.getSpec(),\n language: l.language || \"\"\n };\n if (l.script) {\n result.script = l.script;\n }\n if (l.region) {\n result.region = l.region;\n }\n return result;\n}\n\n/**\n * Parse a path according to the given template, and return the parts.\n * The parts can be any of the fields mentioned in the {@link formatPath}\n * documentation. If the template does not match, fills dir, basename, extension\n * from the path. When sourceLocale is provided and no locale is in the path,\n * fills locale, language, script, region from the source locale.\n *\n * @param {String} template the ilib template for matching against the path\n * @param {String} pathname the path name to match against the template\n * @param {String} sourceLocale optional; when no locale in path, use this to fill locale parts\n * @returns {Object} an object mapping the fields to their values in the pathname\n */\nexport function parsePath(template, pathname, sourceLocale) {\n let regex = \"\";\n let matchGroups = {};\n let totalBrackets = 0;\n let base;\n\n if (!template) {\n template = defaultMappings[\"**/*.json\"].template;\n }\n\n for (let i = 0; i < template.length; i++) {\n if ( template[i] !== '[' ) {\n // Escape regex metacharacters so literal \".\" etc. match correctly (e.g. [basename].[locale].[extension])\n const c = template[i];\n regex += (c === \".\" || c === \"*\" || c === \"+\" || c === \"?\" || c === \"^\" || c === \"$\" || c === \"{\" || c === \"}\" || c === \"(\" || c === \")\" || c === \"|\" || c === \"\\\\\") ? \"\\\\\" + c : c;\n } else {\n let start = ++i;\n while (i < template.length && template[i] !== ']') {\n i++;\n }\n const keyword = template.substring(start, i);\n switch (keyword) {\n case 'filename':\n // escape special characters in the filename so they don't have their special meaning in the regex\n regex += path.basename(pathname || \"\").replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n break;\n default:\n if (!matchExprs[keyword]) {\n logger.warning(\"Warning: template contains unknown substitution parameter \" + keyword);\n return \"\";\n }\n // [basename].[extension]: use last dot to split so \"foo.en-US.mdx\" -> basename=\"foo.en-US\", extension=\"mdx\"\n if (keyword === \"basename\" && template[i + 1] === \".\" && template[i + 2] === \"[\") {\n const nextEnd = template.indexOf(\"]\", i + 2);\n const nextKeyword = nextEnd > -1 ? template.substring(i + 3, nextEnd) : \"\";\n if (nextKeyword === \"extension\") {\n regex += \"(.*)\\\\.([^.]+)\";\n matchGroups.basename = totalBrackets + 1;\n matchGroups.extension = totalBrackets + 2;\n totalBrackets += 2;\n i = nextEnd;\n break;\n }\n }\n // [dir]/ is optional when followed by / and another token. Always capture dir so callers\n // (e.g. PropertiesParser) can build paths. Use (?:()|(.*?)/) so we require the slash when\n // dir is non-empty - this prevents \"test.properties\" from matching as locale \"est\" with dir \"test/testfiles/t\".\n // For \"./de.po\" the empty alternative matches; for \"test/testfiles/de-DE.properties\" we capture dir.\n // Do NOT make optional for [basename], [extension], [resourceDir] - that would break path parsing.\n let optionalDirSlash = false;\n if (keyword === \"dir\" && i + 1 < template.length && template[i + 1] === \"/\" && i + 2 < template.length && template[i + 2] === \"[\") {\n const endBracket = template.indexOf(\"]\", i + 2);\n const nextKeyword = endBracket > -1 ? template.substring(i + 3, endBracket) : \"\";\n const optionalDirTokens = [\"filename\", \"locale\", \"language\", \"script\", \"region\", \"localeDir\", \"localeUnder\", \"localeLower\"];\n optionalDirSlash = optionalDirTokens.includes(nextKeyword);\n }\n if (keyword === \"dir\" && optionalDirSlash) {\n // (?:()|(.*?)/) - empty dir or (.*?)/ (require slash when dir is non-empty)\n regex += \"(?:()|(.*?)/)\";\n matchGroups.dir = totalBrackets + 2; // second group has dir when present\n totalBrackets += 2;\n i++;\n } else {\n regex += matchExprs[keyword].regex;\n for (let prop in matchExprs[keyword].groups) {\n matchGroups[prop] = totalBrackets + matchExprs[keyword].groups[prop];\n }\n totalBrackets += matchExprs[keyword].brackets;\n }\n break;\n }\n }\n }\n\n // Anchor at start. If path starts with \"./\", allow it so template matches the rest (e.g. \"./ja/foo.mdx\" matches \"[language]/[dir]/[filename]\").\n const prefix = (pathname && pathname.startsWith(\"./\")) ? \"^\\\\.\\\\/\" : \"^\";\n const re = new RegExp(prefix + regex, \"u\");\n const match = re.exec(pathname || \"\");\n if (match !== null) {\n let groups = {};\n let found = false;\n for (let groupName in matchGroups) {\n const idx = matchGroups[groupName];\n const value = idx < match.length ? match[idx] : undefined;\n // Capture empty only for dir (so \"[dir]/[filename]\" with \"foo.mdx\" gives dir \".\"); optional locale parts stay omitted.\n if (value !== undefined && (value || (groupName === \"dir\" && value === \"\"))) {\n groups[groupName] = value === undefined ? \"\" : value;\n found = true;\n }\n }\n if (groups.dir === \"\" || (matchGroups.dir && groups.dir === undefined)) {\n groups.dir = \".\";\n }\n // When path started with \"./\", prefix consumed it; put it back into dir so dir is the full path.\n if (pathname && pathname.startsWith(\"./\") && groups.dir !== undefined && groups.dir !== \".\") {\n groups.dir = \"./\" + groups.dir;\n }\n return groups;\n }\n\n // Template did not match; fill dir, basename, extension from path\n const partial = fillPartialPathParts(pathname || \"\");\n if (sourceLocale) {\n Object.assign(partial, fillLocalePartsFromSource(sourceLocale));\n }\n return partial;\n}\n\n/**\n * Return a locale encoded in the path using template to parse that path.\n * See {#formatPath} for the full description of the syntax of the template.\n * @param {String} template template for the output file\n * @param {String} pathname path to the source file\n * @returns {String} the locale within the path, or undefined if no locale found\n */\nexport function getLocaleFromPath(template, pathname) {\n const groups = parsePath(template, pathname);\n\n if (groups.locale || groups.language || groups.script || groups.region ) {\n // TODO: Remove script transformation once similar change is implemented in iLib/Locale class.\n if (groups.script && groups.script.length) {\n groups.script = groups.script.charAt(0).toUpperCase() + groups.script.slice(1).toLowerCase();\n }\n\n const l = groups.locale ?\n new Locale(groups.locale) :\n new Locale(groups.language, groups.region, undefined, groups.script);\n\n return l.getSpec();\n }\n\n return \"\";\n};\n\nexport function makeDirs(path) {\n const parts = path.split(/[\\\\\\/]/);\n\n for (let i = 1; i <= parts.length; i++) {\n const p = parts.slice(0, i).join(\"/\");\n if (p && p.length > 0 && !fs.existsSync(p)) {\n fs.mkdirSync(p);\n }\n }\n};\n\n/**\n * Return true if the string still contains some text after removing all HTML tags and entities.\n * @param {string} str the string to check\n * @returns {boolean} true if there is text left over, and false otherwise\n */\nexport function containsActualText(str) {\n // remove the html and entities first\n const cleaned = str.replace(/<(\"(\\\\\"|[^\"])*\"|'(\\\\'|[^'])*'|[^>])*>/g, \"\").replace(/&[a-zA-Z]+;/g, \"\");\n\n for (let i = 0; i < cleaned.length; i++) {\n const c = cleaned.charAt(i);\n if (isAlnum(c) || isIdeo(c)) return true;\n }\n return false;\n};\n\nfunction isPrimitive(type) {\n return [\"boolean\", \"number\", \"integer\", \"string\"].indexOf(type) > -1;\n}\n\n/**\n * Recursively visit every node in an object and call the visitor on any\n * primitive values.\n * @param {*} object any object, arrary, or primitive\n * @param {Function(*)} visitor function to call on any primitive\n * @returns {*} the same type as the original object, but with every\n * primitive processed by the visitor function\n */\nexport function objectMap(object, visitor) {\n if (!object) return object;\n if (isPrimitive(typeof(object))) {\n return visitor(object);\n } else if (Array.isArray(object)) {\n return object.map(item => {\n return objectMap(item, visitor);\n });\n } else {\n const ret = {};\n for (let prop in object) {\n if (object.hasOwnProperty(prop)) {\n ret[prop] = objectMap(object[prop], visitor);\n }\n }\n return ret;\n }\n};\n\n/**\n * Return a standard hash of the given source string.\n *\n * @param {String} source the source string as extracted from the\n * source code, unmodified\n * @returns {String} the hash key\n */\nexport function hashKey(source) {\n if (!source) return undefined;\n let hash = 0;\n // these two numbers together = 46 bits so it won't blow out the precision of an integer in javascript\n const modulus = 1073741789; // largest prime number that fits in 30 bits\n const multiple = 65521; // largest prime that fits in 16 bits, co-prime with the modulus\n\n // logger.trace(\"hash starts off at \" + hash);\n\n for (let i = 0; i < source.length; i++) {\n // logger.trace(\"hash \" + hash + \" char \" + source.charCodeAt(i) + \"=\" + source.charAt(i));\n hash += source.charCodeAt(i);\n hash *= multiple;\n hash %= modulus;\n }\n const value = \"r\" + hash;\n\n // System.out.println(\"String '\" + source + \"' hashes to \" + value);\n\n return value;\n};\n\n/**\n * A hash containing a list of HTML tags that do not\n * cause a break in a resource string. These tags should\n * be included in the middle of the string.\n */\nexport const nonBreakingTags = {\n \"a\": true,\n \"abbr\": true,\n \"b\": true,\n \"bdi\": true,\n \"bdo\": true,\n \"br\": true,\n \"dfn\": true,\n \"del\": true,\n \"em\": true,\n \"i\": true,\n \"ins\": true,\n \"mark\": true,\n \"ruby\": true,\n \"rt\": true,\n \"span\": true,\n \"strong\": true,\n \"sub\": true,\n \"sup\": true,\n \"time\": true,\n \"u\": true,\n \"var\": true,\n \"wbr\": true\n};\n\n/**\n * A hash containing a list of HTML tags that are\n * typically self-closing. That is, in HTML4 and earlier,\n * the close tag was not needed for these.\n */\nexport const selfClosingTags = {\n \"area\": true,\n \"base\": true,\n \"bdi\": true,\n \"bdo\": true,\n \"br\": true,\n \"embed\": true,\n \"hr\": true,\n \"img\": true,\n \"input\": true,\n \"link\": true,\n \"option\": true,\n \"param\": true,\n \"source\": true,\n \"track\": true\n};\n\n/**\n * A hash containing a list of HTML tags where\n * the text content inside of those tags should be\n * ignored for localization purposes. Instead,\n * those contents should just be copied to the\n * localized file unmodified.\n */\nexport const ignoreTags = {\n \"code\": true,\n \"output\": true,\n \"samp\": true,\n \"script\": true,\n \"style\": true\n};\n\n/**\n * List of html5 tags and their attributes that contain localizable strings.\n * The \"*\" indicates it applies to the given attributes on every tag.\n * Also added ARIA attributes to localize for accessibility. For more details,\n * see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/\n */\nexport const localizableAttributes = {\n \"area\": {\"alt\":true},\n \"img\": {\"alt\":true},\n \"input\": {\n \"alt\": true,\n \"placeholder\": true\n },\n \"optgroup\": {\"label\":true},\n \"option\": {\"label\":true},\n \"textarea\": {\"placeholder\":true},\n \"track\": {\"label\":true},\n \"*\": {\n \"title\": true,\n \"aria-braillelabel\": true,\n \"aria-brailleroledescription\": true,\n \"aria-description\": true,\n \"aria-label\": true,\n \"aria-placeholder\": true,\n \"aria-roledescription\": true,\n \"aria-rowindextext\": true,\n \"aria-valuetext\": true\n }\n};\n\n// this is for English and many other languages\nconst defaultPluralCategories = [\n \"one\",\n \"other\"\n];\n\n/**\n * Return the plural categories for the given language.\n * If there is no entry for the given\n * language, then the default English plural categories are returned.\n *\n * @param {string|undefined} language the ISO 639 language code to get the plural\n * categories for\n * @returns {Array} an array of strings containing the names of the plural categories\n */\nexport function getLanguagePluralCategories(language) {\n // make sure to get the language only if someone accidentally sends in a full locale\n const locale = new Locale(language);\n const lang = locale.getLanguage() ?? \"en\";\n return pluralCategories[lang] ?? defaultPluralCategories;\n}\n"],"mappings":"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","ignoreList":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ilib-tools-common",
3
- "version": "1.21.4",
3
+ "version": "1.22.0",
4
4
  "main": "./lib/index.js",
5
5
  "module": "./src/index.js",
6
6
  "exports": {
package/src/utils.js CHANGED
@@ -194,10 +194,16 @@ export function formatLocaleParams(template, locale) {
194
194
  * @param {string} template the path template string
195
195
  * @param {Object} parameters an object containing:
196
196
  * @param {string} parameters.sourcepath the path to the source file, relative to the
197
- * root of the project
197
+ * root of the project (used when path parts are not provided)
198
198
  * @param {string} parameters.locale the locale for the output file path
199
199
  * @param {string} parameters.resourceDir optional resource directory to substitute
200
200
  * for [resourceDir] in the template
201
+ * @param {string} parameters.dir optional pre-parsed directory (e.g. from parsePath)
202
+ * @param {string} parameters.basename optional pre-parsed basename without extension
203
+ * @param {string} parameters.extension optional pre-parsed file extension
204
+ * @param {string} parameters.filename optional pre-parsed filename (basename + extension)
205
+ * When any of dir, basename, extension, or filename are provided, they are used
206
+ * instead of deriving from sourcepath. This allows using parsePath output directly.
201
207
  * @returns {string} the formatted file path
202
208
  */
203
209
  export function formatPath(template, parameters) {
@@ -205,27 +211,25 @@ export function formatPath(template, parameters) {
205
211
  const locale = parameters.locale || "en";
206
212
  const resourceDir = parameters.resourceDir || ".";
207
213
 
214
+ // Use pre-parsed path parts when provided (e.g. from parsePath); otherwise derive from sourcepath
215
+ const pathParts = fillPartialPathParts(pathname);
216
+ const dir = parameters.dir ?? pathParts.dir;
217
+ const basename = parameters.basename ?? pathParts.basename;
218
+ const extension = parameters.extension ?? pathParts.extension;
219
+ const filename = parameters.filename ?? (extension ? basename + "." + extension : basename);
220
+
208
221
  // First, handle locale-related substitutions without path normalization
209
222
  let output = formatLocaleParams(template, locale);
210
223
 
211
- // Now handle path-specific keywords
212
- let base;
213
- let lastDot;
224
+ // Handle [basename].[extension] as a unit to avoid trailing dot when extension is empty
225
+ output = output.replace(/\[basename\]\.\[extension\]/g, extension ? basename + "." + extension : basename);
214
226
 
215
- output = output.replace(/\[dir\]/g, path.dirname(pathname));
216
- output = output.replace(/\[filename\]/g, path.basename(pathname));
227
+ // Now handle path-specific keywords
228
+ output = output.replace(/\[dir\]/g, dir);
229
+ output = output.replace(/\[filename\]/g, filename);
217
230
  output = output.replace(/\[resourceDir\]/g, resourceDir);
218
-
219
- if (output.includes('[extension]')) {
220
- base = path.basename(pathname);
221
- output = output.replace(/\[extension\]/g, base.indexOf('.') > -1 ? base.substring(base.lastIndexOf('.')+1) : "");
222
- }
223
-
224
- if (output.includes('[basename]')) {
225
- base = path.basename(pathname);
226
- lastDot = base.lastIndexOf('.');
227
- output = output.replace(/\[basename\]/g, lastDot > -1 ? base.substring(0, lastDot) : base);
228
- }
231
+ output = output.replace(/\[extension\]/g, extension);
232
+ output = output.replace(/\[basename\]/g, basename);
229
233
 
230
234
  return path.normalize(output);
231
235
  };
@@ -322,17 +326,62 @@ const matchExprs = {
322
326
  }
323
327
  };
324
328
 
329
+ /**
330
+ * Derive dir, basename, extension, and whole filename from a pathname using path operations.
331
+ * Used when the template does not match so we can still fill path parts.
332
+ *
333
+ * @param {String} pathname the path name
334
+ * @returns {Object} { dir, basename, extension, filename }
335
+ */
336
+ function fillPartialPathParts(pathname) {
337
+ if (!pathname) {
338
+ return { dir: ".", basename: "", extension: "" };
339
+ }
340
+ const dir = path.dirname(pathname) || ".";
341
+ const filename = path.basename(pathname);
342
+ const lastDot = filename.lastIndexOf(".");
343
+ const basename = lastDot > -1 ? filename.substring(0, lastDot) : filename;
344
+ const extension = lastDot > -1 ? filename.substring(lastDot + 1) : "";
345
+ return { dir, basename, extension, filename };
346
+ }
347
+
348
+ /**
349
+ * Fill locale, language, script, region from a source locale string using Locale class.
350
+ *
351
+ * @param {String} sourceLocale BCP-47 locale string
352
+ * @returns {Object} { locale, language, script?, region? }
353
+ */
354
+ function fillLocalePartsFromSource(sourceLocale) {
355
+ if (!sourceLocale) {
356
+ return {};
357
+ }
358
+ const l = new Locale(sourceLocale);
359
+ const result = {
360
+ locale: l.getSpec(),
361
+ language: l.language || ""
362
+ };
363
+ if (l.script) {
364
+ result.script = l.script;
365
+ }
366
+ if (l.region) {
367
+ result.region = l.region;
368
+ }
369
+ return result;
370
+ }
371
+
325
372
  /**
326
373
  * Parse a path according to the given template, and return the parts.
327
374
  * The parts can be any of the fields mentioned in the {@link formatPath}
328
- * documentation. If any field is not parsed, the result is an empty object
375
+ * documentation. If the template does not match, fills dir, basename, extension
376
+ * from the path. When sourceLocale is provided and no locale is in the path,
377
+ * fills locale, language, script, region from the source locale.
329
378
  *
330
379
  * @param {String} template the ilib template for matching against the path
331
380
  * @param {String} pathname the path name to match against the template
332
- * @returns {Object} an object mapping the fields to their values in the
333
- * the pathname
381
+ * @param {String} sourceLocale optional; when no locale in path, use this to fill locale parts
382
+ * @returns {Object} an object mapping the fields to their values in the pathname
334
383
  */
335
- export function parsePath(template, pathname) {
384
+ export function parsePath(template, pathname, sourceLocale) {
336
385
  let regex = "";
337
386
  let matchGroups = {};
338
387
  let totalBrackets = 0;
@@ -344,7 +393,9 @@ export function parsePath(template, pathname) {
344
393
 
345
394
  for (let i = 0; i < template.length; i++) {
346
395
  if ( template[i] !== '[' ) {
347
- regex += template[i];
396
+ // Escape regex metacharacters so literal "." etc. match correctly (e.g. [basename].[locale].[extension])
397
+ const c = template[i];
398
+ regex += (c === "." || c === "*" || c === "+" || c === "?" || c === "^" || c === "$" || c === "{" || c === "}" || c === "(" || c === ")" || c === "|" || c === "\\") ? "\\" + c : c;
348
399
  } else {
349
400
  let start = ++i;
350
401
  while (i < template.length && template[i] !== ']') {
@@ -361,6 +412,19 @@ export function parsePath(template, pathname) {
361
412
  logger.warning("Warning: template contains unknown substitution parameter " + keyword);
362
413
  return "";
363
414
  }
415
+ // [basename].[extension]: use last dot to split so "foo.en-US.mdx" -> basename="foo.en-US", extension="mdx"
416
+ if (keyword === "basename" && template[i + 1] === "." && template[i + 2] === "[") {
417
+ const nextEnd = template.indexOf("]", i + 2);
418
+ const nextKeyword = nextEnd > -1 ? template.substring(i + 3, nextEnd) : "";
419
+ if (nextKeyword === "extension") {
420
+ regex += "(.*)\\.([^.]+)";
421
+ matchGroups.basename = totalBrackets + 1;
422
+ matchGroups.extension = totalBrackets + 2;
423
+ totalBrackets += 2;
424
+ i = nextEnd;
425
+ break;
426
+ }
427
+ }
364
428
  // [dir]/ is optional when followed by / and another token. Always capture dir so callers
365
429
  // (e.g. PropertiesParser) can build paths. Use (?:()|(.*?)/) so we require the slash when
366
430
  // dir is non-empty - this prevents "test.properties" from matching as locale "est" with dir "test/testfiles/t".
@@ -417,7 +481,12 @@ export function parsePath(template, pathname) {
417
481
  return groups;
418
482
  }
419
483
 
420
- return {};
484
+ // Template did not match; fill dir, basename, extension from path
485
+ const partial = fillPartialPathParts(pathname || "");
486
+ if (sourceLocale) {
487
+ Object.assign(partial, fillLocalePartsFromSource(sourceLocale));
488
+ }
489
+ return partial;
421
490
  }
422
491
 
423
492
  /**