UNPKG

@lingui/cli

Version:

Lingui CLI to extract messages, compile catalogs, and manage translation workflows

118 lines (117 loc) 5.01 kB
import { globSync } from "node:fs"; import path from "path"; import { Catalog } from "../catalog.js"; import { normalizeRelativePath, PATHSEP, replacePlaceholders, } from "../utils.js"; import micromatch from "micromatch"; import { getFormat } from "../formats/index.js"; import { getExperimentalCatalogs } from "../../extract-experimental/getExperimentalCatalogs.js"; const NAME_PH = "{name}"; const LOCALE_PH = "{locale}"; /** * Parse `config.catalogs` and return a list of configured Catalog instances. */ export async function getCatalogs(config) { const catalogsConfig = config.catalogs; const catalogs = []; const format = await getFormat(config.format, config.sourceLocale); catalogsConfig.forEach((catalog) => { validateCatalogPath(catalog.path, format.getCatalogExtension()); const include = ensureArray(catalog.include).map(normalizeRelativePath); const exclude = ensureArray(catalog.exclude).map(normalizeRelativePath); // catalog.path without {name} pattern -> always refers to a single catalog if (!catalog.path.includes(NAME_PH)) { // Validate that sourcePaths doesn't use {name} pattern either const invalidSource = include.find((path) => path.includes(NAME_PH)); if (invalidSource !== undefined) { throw new Error(`Catalog with path "${catalog.path}" doesn't have a {name} pattern` + ` in it, but one of source directories uses it: "${invalidSource}".` + ` Either add {name} pattern to "${catalog.path}" or remove it` + ` from all source directories.`); } catalogs.push(new Catalog({ name: getCatalogName(catalog.path), path: normalizeRelativePath(catalog.path), include, exclude, format, }, config)); return; } const patterns = include.map((path) => replacePlaceholders(path, { name: "*" })); const candidates = globSync(patterns, { exclude, }); candidates.forEach((catalogDir) => { const name = path.basename(catalogDir); catalogs.push(new Catalog({ name, path: normalizeRelativePath(replacePlaceholders(catalog.path, { name })), include: include.map((path) => replacePlaceholders(path, { name })), exclude: exclude.map((path) => replacePlaceholders(path, { name })), format, }, config)); }); }); if (config.experimental?.extractor && config.experimental.extractor.entries.length) { catalogs.push(...(await getExperimentalCatalogs(config, format, config.experimental.extractor))); } return catalogs; } /** * Ensure that value is always array. If not, turn it into an array of one element. */ const ensureArray = (value) => { if (value == null) return []; return Array.isArray(value) ? value : [value]; }; /** * Create catalog for merged messages. */ export async function getMergedCatalogPath(config) { const format = await getFormat(config.format, config.sourceLocale); validateCatalogPath(config.catalogsMergePath, format.getCatalogExtension()); return normalizeRelativePath(config.catalogsMergePath); } export function getCatalogForFile(file, catalogs) { for (const catalog of catalogs) { const catalogFile = `${catalog.path}${catalog.format.getCatalogExtension()}`; const catalogGlob = replacePlaceholders(catalogFile, { locale: "*" }); const matchPattern = normalizeRelativePath(path.relative(catalog.config.rootDir, catalogGlob)).replace(/(\(|\)|\[|\]|\{|\})/g, "\\$1"); const match = micromatch.capture(matchPattern, normalizeRelativePath(file)); if (match) { return { locale: match[0], catalog, }; } } return null; } /** * Validate that `catalogPath` doesn't end with trailing slash */ function validateCatalogPath(path, extension) { if (!path.includes(LOCALE_PH)) { throw new Error(`Invalid catalog path: ${LOCALE_PH} variable is missing`); } if (!path.endsWith(PATHSEP)) { return; } const correctPath = path.slice(0, -1); const examplePath = replacePlaceholders(correctPath, { locale: "en", }) + extension; throw new Error( // prettier-ignore `Remove trailing slash from "${path}". Catalog path isn't a directory,` + ` but translation file without extension. For example, catalog path "${correctPath}"` + ` results in translation file "${examplePath}".`); } function getCatalogName(filePath) { // catalog name is the last directory of catalogPath. // If the last part is {locale}, then catalog doesn't have an explicit name const name = path.basename(normalizeRelativePath(filePath)); return name !== LOCALE_PH ? name : undefined; }