diff --git a/chrome/locale/en-US/zotero/zotero.ftl b/chrome/locale/en-US/zotero/zotero.ftl index c430cc740e..8425b66583 100644 --- a/chrome/locale/en-US/zotero/zotero.ftl +++ b/chrome/locale/en-US/zotero/zotero.ftl @@ -55,8 +55,7 @@ file-interface-items-were-relinked = { $numRelinked -> *[other] { $numRelinked } items were relinked } -import-mendeley-encrypted = The selected Mendeley database cannot be read, likely because it is encrypted. - See How do I import a Mendeley library into Zotero? for more information. +import-mendeley-encrypted = The selected Mendeley database cannot be read, likely because it is encrypted. See How do I import a Mendeley library into Zotero? for more information. file-interface-import-error-translator = An error occurred importing the selected file with “{ $translator }”. Please ensure that the file is valid and try again. diff --git a/js-build/ftl-to-json.mjs b/js-build/ftl-to-json.mjs new file mode 100644 index 0000000000..db812d8603 --- /dev/null +++ b/js-build/ftl-to-json.mjs @@ -0,0 +1,38 @@ +import { ftlToJSON } from "ftl-tx"; +import fs from 'fs-extra'; +import { dirname, join } from 'path'; +import { fileURLToPath } from 'url'; +import { onError, onSuccess } from './utils.js'; + +const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..'); +const JSONDir = join(ROOT, 'tmp', 'tx'); + +async function getJSON() { + const t1 = performance.now(); + await fs.mkdirp(JSONDir); + const sourcefile = join(ROOT, 'chrome', 'locale', 'en-US', 'zotero', 'zotero.ftl'); + const destFile = join(JSONDir, 'zotero_en_US.json'); + const ftl = await fs.readFile(sourcefile, 'utf8'); + const json = ftlToJSON(ftl, { transformTerms: false, storeTermsInJSON: false }); + await fs.outputJSON(destFile, json); + const t2 = performance.now(); + return ({ + action: 'ftl->json', + count: 1, + totalCount: 1, + processingTime: t2 - t1 + }); +} + +if (process.argv[1] === fileURLToPath(import.meta.url)) { + (async () => { + try { + onSuccess(await getJSON()); + } + catch (err) { + process.exitCode = 1; + global.isError = true; + onError(err); + } + })(); +} diff --git a/js-build/localize-ftl.mjs b/js-build/localize-ftl.mjs new file mode 100644 index 0000000000..3c06bfd430 --- /dev/null +++ b/js-build/localize-ftl.mjs @@ -0,0 +1,98 @@ +import { extractTerms, ftlToJSON, JSONToFtl } from 'ftl-tx'; +import fs from 'fs-extra'; +import { dirname, join } from 'path'; +import { fileURLToPath } from 'url'; +import { onError, onProgress, onSuccess } from './utils.js'; +import { exit } from 'process'; + +const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..'); +const JSONDir = join(ROOT, 'tmp', 'tx'); +const localesDir = join(ROOT, 'chrome', 'locale'); +const sourceDir = join(localesDir, 'en-US', 'zotero'); + +const termsSourceFTLPath = join(ROOT, 'app', 'assets', 'branding', 'locale', 'brand.ftl'); +const fallbackJSONPath = join(sourceDir, 'zotero.json'); + +// don't override source zotero.ftl +const localeToSkip = ['en-US']; + +async function getFTL() { + const t1 = performance.now(); + if (!(await fs.pathExists(fallbackJSONPath))) { + console.error(`File ${fallbackJSONPath} does not exist, please run 'ftl-to-json' first`); + exit(1); + } + + if (!(await fs.pathExists(termsSourceFTLPath))) { + console.error(`Required file ${termsSourceFTLPath} does not exist`); + exit(1); + } + + const fallbackJSON = await fs.readJSON(fallbackJSONPath); + const terms = extractTerms(await fs.readFile(termsSourceFTLPath, 'utf-8')); + + const expectedLocale = (await fs.readdir(localeDir, { withFileTypes: true })) + .filter(dirent => dirent.isDirectory()) + .map(dirent => dirent.name); + + const totalCount = expectedLocale.length; + let locale; + let count = 0; + + while ((locale = expectedLocale.pop())) { + if (localeToSkip.includes(locale)) { + count++; + continue; + } + + const ftlFilePath = join(ROOT, 'chrome', 'locale', locale, 'zotero', 'zotero.ftl'); + let JSONFromLocalFTL = {}; + try { + const ftl = await fs.readFile(ftlFilePath, 'utf8'); + JSONFromLocalFTL = ftlToJSON(ftl, { transformTerms: false, storeTermsInJSON: false }); + } + catch (e) { + // no local .ftl file + } + + const JSONFilePath = join(JSONDir, `zotero_${locale.replace('-', '_')}.json`); + let JSONFromTransifex = {}; + try { + const json = await fs.readJSON(JSONFilePath); + JSONFromTransifex = json; + } + catch (e) { + // no .json file from transifex + } + + const mergedJSON = { ...fallbackJSON, ...JSONFromLocalFTL, ...JSONFromTransifex }; + const ftl = JSONToFtl(mergedJSON, { addTermsToFTL: false, storeTermsInJSON: false, transformTerms: false, terms }); + + const outFtlPath = join(ROOT, 'chrome', 'locale', locale, 'zotero', 'zotero.ftl'); + await fs.outputFile(outFtlPath, ftl); + onProgress(outFtlPath, outFtlPath, 'ftl'); + count++; + } + + const t2 = performance.now(); + return ({ + action: 'ftl', + count, + totalCount, + processingTime: t2 - t1 + }); +} + + +if (process.argv[1] === fileURLToPath(import.meta.url)) { + (async () => { + try { + onSuccess(await getFTL()); + } + catch (err) { + process.exitCode = 1; + global.isError = true; + onError(err); + } + })(); +} diff --git a/js-build/utils.js b/js-build/utils.js index 3cb1480d6b..2474fcf161 100644 --- a/js-build/utils.js +++ b/js-build/utils.js @@ -21,7 +21,7 @@ function onSuccess(result) { msg += ` | ${result.totalCount} checked`; } - msg += ` [${yellow(`${result.processingTime}ms`)}]`; + msg += ` [${yellow(`${result.processingTime.toFixed(2)}ms`)}]`; console.log(msg); } diff --git a/package-lock.json b/package-lock.json index 9bfa05ac6e..eae6924893 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "zotero", - "version": "5.0.0", + "version": "7.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "zotero", - "version": "5.0.0", + "version": "7.0.0", "dependencies": { "@citeproc-rs/wasm": "^0.2.0", "ace-builds": "^1.4.12", @@ -40,6 +40,7 @@ "eslint-plugin-react": "^7.28.0", "eslint-plugin-react-hooks": "^4.0.4", "fs-extra": "^3.0.1", + "ftl-tx": "^0.6.0", "globby": "^6.1.0", "jspath": "^0.4.0", "mocha": "^3.5.3", @@ -919,6 +920,16 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, + "node_modules/@fluent/syntax": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@fluent/syntax/-/syntax-0.19.0.tgz", + "integrity": "sha512-5D2qVpZrgpjtqU4eNOcWGp1gnUCgjfM+vKGE2y03kKN6z5EBhtx0qdRFbg8QuNNj8wXNoX93KJoYb+NqoxswmQ==", + "dev": true, + "engines": { + "node": ">=14.0.0", + "npm": ">=7.0.0" + } + }, "node_modules/@formatjs/intl-displaynames": { "version": "1.2.10", "resolved": "https://registry.npmjs.org/@formatjs/intl-displaynames/-/intl-displaynames-1.2.10.tgz", @@ -3561,6 +3572,15 @@ "node": ">= 4.0" } }, + "node_modules/ftl-tx": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/ftl-tx/-/ftl-tx-0.6.0.tgz", + "integrity": "sha512-w7s0p6RNsaUKpiuQZ5Q0KIyiX2JIDItqI4qRKt49u0aMqdGp0gDc5G5Qnubgu6l2ww5PDt4wsyL7A1iZo1VGMQ==", + "dev": true, + "dependencies": { + "@fluent/syntax": "^0.19.0" + } + }, "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -7533,6 +7553,12 @@ } } }, + "@fluent/syntax": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@fluent/syntax/-/syntax-0.19.0.tgz", + "integrity": "sha512-5D2qVpZrgpjtqU4eNOcWGp1gnUCgjfM+vKGE2y03kKN6z5EBhtx0qdRFbg8QuNNj8wXNoX93KJoYb+NqoxswmQ==", + "dev": true + }, "@formatjs/intl-displaynames": { "version": "1.2.10", "resolved": "https://registry.npmjs.org/@formatjs/intl-displaynames/-/intl-displaynames-1.2.10.tgz", @@ -9769,6 +9795,15 @@ "nan": "^2.12.1" } }, + "ftl-tx": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/ftl-tx/-/ftl-tx-0.6.0.tgz", + "integrity": "sha512-w7s0p6RNsaUKpiuQZ5Q0KIyiX2JIDItqI4qRKt49u0aMqdGp0gDc5G5Qnubgu6l2ww5PDt4wsyL7A1iZo1VGMQ==", + "dev": true, + "requires": { + "@fluent/syntax": "^0.19.0" + } + }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", diff --git a/package.json b/package.json index 7442586c06..672ed97c1c 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,8 @@ "sass": "node ./js-build/sass.js", "js": "node ./js-build/js.js", "clean": "node ./js-build/clean.js", + "ftl-to-json": "node ./js-build/ftl-to-json.mjs", + "localize-ftl": "node ./js-build/localize-ftl.mjs", "clean-build": "node ./js-build/clean.js && node ./js-build/build.js", "clean-start": "node ./js-build/clean.js && node ./js-build/build.js && node ./js-build/watch.js" }, @@ -47,6 +49,7 @@ "eslint-plugin-react": "^7.28.0", "eslint-plugin-react-hooks": "^4.0.4", "fs-extra": "^3.0.1", + "ftl-tx": "^0.6.0", "globby": "^6.1.0", "jspath": "^0.4.0", "mocha": "^3.5.3",