diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-05-17 19:05:49 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-05-17 19:05:49 +0300 |
commit | 43a25d93ebdabea52f99b05e15b06250cd8f07d7 (patch) | |
tree | dceebdc68925362117480a5d672bcff122fb625b /scripts/frontend | |
parent | 20c84b99005abd1c82101dfeff264ac50d2df211 (diff) |
Add latest changes from gitlab-org/gitlab@16-0-stable-eev16.0.0-rc42
Diffstat (limited to 'scripts/frontend')
-rwxr-xr-x | scripts/frontend/download_fixtures.sh | 56 | ||||
-rw-r--r-- | scripts/frontend/extract_gettext_all.js | 10 | ||||
-rwxr-xr-x | scripts/frontend/po_to_json.js | 208 | ||||
-rw-r--r-- | scripts/frontend/startup_css/constants.js | 4 |
4 files changed, 271 insertions, 7 deletions
diff --git a/scripts/frontend/download_fixtures.sh b/scripts/frontend/download_fixtures.sh new file mode 100755 index 00000000000..47a57401bb9 --- /dev/null +++ b/scripts/frontend/download_fixtures.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash + +# +# Downloads the most recent frontend fixtures for the current commit, going up the commit parent +# chain up to max-commits commits (defaults to 50 commits). +# + +source scripts/packages/helpers.sh + +print_help() { + echo "Usage: scripts/frontend/download_fixtures.sh [--branch <branch-name>] [--max-commits <number>]" + echo + echo "Looks for a frontend fixture package in the package registry for commits on a local branch." + echo + echo "If --branch isn't specified, the script will use the current branch as a commit reference." + echo "If --max-commits isn't specified, the default is 50 commits." + + return +} + +branch="HEAD" +max_commits_count=50 + +while [ $# -gt 0 ]; do + case "$1" in + --branch) + shift + branch="$1" + ;; + --max-commits) + shift + max_commits_count="$1" + ;; + *) + print_help + exit + ;; + esac + shift +done + +for commit_sha in $(git rev-list ${branch} --max-count="${max_commits_count}"); do + API_PACKAGES_BASE_URL=https://gitlab.com/api/v4/projects/278964/packages/generic + FIXTURES_PACKAGE="fixtures-${commit_sha}.tar.gz" + FIXTURES_PACKAGE_URL="${API_PACKAGES_BASE_URL}/fixtures/${commit_sha}/${FIXTURES_PACKAGE}" + + echo "Looking for frontend fixtures for commit ${commit_sha}..." + + if ! archive_doesnt_exist "${FIXTURES_PACKAGE_URL}" > /dev/null 2>&1; then + echo "We have found frontend fixtures at ${FIXTURES_PACKAGE_URL}!" + + read_curl_package "${FIXTURES_PACKAGE_URL}" | extract_package + + break + fi +done diff --git a/scripts/frontend/extract_gettext_all.js b/scripts/frontend/extract_gettext_all.js index 922aa85241f..0cd6ab99a3a 100644 --- a/scripts/frontend/extract_gettext_all.js +++ b/scripts/frontend/extract_gettext_all.js @@ -19,7 +19,7 @@ extractor.addMessageTransformFunction(ensureSingleLine); const jsParser = extractor.createJsParser([ // Place all the possible expressions to extract here: - JsExtractors.callExpression('__', { + JsExtractors.callExpression(['__', 's__'], { arguments: { text: 0, }, @@ -30,15 +30,13 @@ const jsParser = extractor.createJsParser([ textPlural: 1, }, }), - JsExtractors.callExpression('s__', { - arguments: { - text: 0, - }, - }), ]); const vueParser = decorateJSParserWithVueSupport(jsParser, { vue2TemplateCompiler, + // All of our expressions contain `__`. + // So we can safely ignore parsing files _not_ containing it. + guard: '__', }); function printJson() { diff --git a/scripts/frontend/po_to_json.js b/scripts/frontend/po_to_json.js new file mode 100755 index 00000000000..fba68a61814 --- /dev/null +++ b/scripts/frontend/po_to_json.js @@ -0,0 +1,208 @@ +#!/usr/bin/env node + +const fs = require('fs/promises'); +const path = require('path'); + +async function isDir(dirPath) { + if (!dirPath) { + return false; + } + try { + const stat = await fs.stat(dirPath); + return stat.isDirectory(); + } catch (e) { + return false; + } +} + +/** + * This is the main function which starts multiple workers + * in order to speed up the po file => app.js + * locale conversions + */ +async function main({ localeRoot, outputDir } = {}) { + if (!(await isDir(localeRoot))) { + throw new Error(`Provided localeRoot: '${localeRoot}' doesn't seem to be a folder`); + } + + if (!(await isDir(outputDir))) { + throw new Error(`Provided outputDir '${outputDir}' doesn't seem to be a folder`); + } + + // eslint-disable-next-line global-require + const glob = require('glob'); + // eslint-disable-next-line global-require + const { Worker } = require('jest-worker'); + + const locales = glob.sync('*/*.po', { cwd: localeRoot }); + + const worker = new Worker(__filename, { + exposedMethods: ['convertPoFileForLocale'], + silent: false, + enableWorkerThreads: true, + }); + worker.getStdout().pipe(process.stdout); + worker.getStderr().pipe(process.stderr); + + await Promise.all( + locales.map((localeFile) => { + const locale = path.dirname(localeFile); + return worker.convertPoFileForLocale({ + locale, + localeFile: path.join(localeRoot, localeFile), + resultDir: path.join(outputDir, locale), + }); + }), + ); + + await worker.end(); + + console.log('Done converting all the po files'); +} + +/** + * This is the conversion logic for: po => JS object for jed + */ +function convertPoToJed(data, locale) { + // eslint-disable-next-line global-require + const { parse } = require('gettext-parser/lib/poparser'); + const DEFAULT_CONTEXT = ''; + + /** + * TODO: This replacer might be unnecessary _or_ even cause bugs. + * due to potential unnecessary double escaping. + * But for now it is here to ensure that the old and new output + * are equivalent. + * @param str + * @returns {string} + */ + function escapeMsgid(str) { + return `${str}`.replace(/([\\"])/g, '\\$1'); + } + + /** + * TODO: This replacer might be unnecessary _or_ even cause bugs. + * due to potential unnecessary double escaping. + * But for now it is here to ensure that the old and new output + * are equivalent. + * + * NOTE: The replacements of `\n` and `\t` need to be iterated on, + * because: In the cases where we see those chars, they: + * - likely need or could be trimmed because they do nothing + * - they seem to escaped in a way that is broken anyhow + * @param str + * @returns {string} + */ + function escapeMsgstr(str) { + return `${str}`.replace(/[\t\n"\\]/g, (match) => { + if (match === '\n') { + return '\\n'; + } + if (match === '\t') { + return '\\t'; + } + return `\\${match}`; + }); + } + + const { headers = {}, translations: parsed } = parse(data); + + const translations = Object.values(parsed[DEFAULT_CONTEXT] ?? {}).reduce((acc, entry) => { + const { msgid, msgstr } = entry; + + /* TODO: If a msgid has no translation, we can just drop the whole key, + as jed will just fallback to the keys + We are not doing that yet, because we do want to ensure that + the results of the new and old way of generating the files matches. + if (msgstr.every((x) => x === '')) { + return acc; + } + */ + + acc[escapeMsgid(msgid)] = msgstr.map(escapeMsgstr); + + return acc; + }, {}); + + // Do not bother if the file has no actual translations + if (!Object.keys(translations).length) { + return { jed: null }; + } + + if (headers['Plural-Forms']) { + headers.plural_forms = headers['Plural-Forms']; + } + + // Format required for jed: http://messageformat.github.io/Jed/ + const jed = { + domain: 'app', + locale_data: { + app: { + ...translations, + // Ensure that the header data which is attached to a message with id "" + // is not accidentally overwritten by an empty externalized string + '': { + ...headers, + domain: 'app', + lang: locale, + }, + }, + }, + }; + + return { jed }; +} + +/** + * This is the function which the workers actually execute + * 1. It reads the po + * 2. converts it with convertPoToJed + * 3. writes the file to + */ +async function convertPoFileForLocale({ locale, localeFile, resultDir }) { + const poContent = await fs.readFile(localeFile); + + const { jed } = await convertPoToJed(poContent, locale); + + if (jed === null) { + console.log(`${locale}: No translations. Skipping creation of app.js`); + return; + } + + await fs.mkdir(resultDir, { recursive: true }); + + await fs.writeFile( + path.join(resultDir, 'app.js'), + `window.translations = ${JSON.stringify(jed)}`, + 'utf8', + ); + console.log(`Created app.js in ${resultDir}`); +} + +/* + Start the main thread only if we are not part of a worker + */ +if (!process.env.JEST_WORKER_ID) { + // eslint-disable-next-line global-require + const argumentsParser = require('commander'); + + const args = argumentsParser + .option('-l, --locale-root <locale_root>', 'Extract messages from subfolders in this directory') + .option('-o, --output-dir <output_dir>', 'Write app.js files into subfolders in this directory') + .parse(process.argv); + + main(args).catch((e) => { + console.warn(`Something went wrong: ${e.message}`); + console.warn(args.printHelp()); + process.exitCode = 1; + }); +} + +/* + Expose the function for workers + */ +module.exports = { + main, + convertPoToJed, + convertPoFileForLocale, +}; diff --git a/scripts/frontend/startup_css/constants.js b/scripts/frontend/startup_css/constants.js index 5143c04dc37..bf9774daea5 100644 --- a/scripts/frontend/startup_css/constants.js +++ b/scripts/frontend/startup_css/constants.js @@ -51,12 +51,14 @@ const createMainOutput = ({ outFile, cssKeys, type }) => ({ htmlPaths: [ path.join(FIXTURES_ROOT, `startup_css/project-${type}.html`), path.join(FIXTURES_ROOT, `startup_css/project-${type}-signed-out.html`), - path.join(FIXTURES_ROOT, `startup_css/project-${type}-search-ff-off.html`), + path.join(FIXTURES_ROOT, `startup_css/project-${type}-super-sidebar.html`), ], cssKeys, purgeOptions: { safelist: { standard: [ + 'page-with-super-sidebar', + 'page-with-super-sidebar-collapsed', 'page-with-icon-sidebar', 'sidebar-collapsed-desktop', // We want to include the root dropdown-menu style since it should be hidden by default |