From 31867a9f41075ca4562d6b5d6eaa13b84fccedd7 Mon Sep 17 00:00:00 2001 From: Davey Alvarez Date: Fri, 10 Jan 2025 14:32:33 -0800 Subject: [PATCH] feat(next): l10n: Update payments-next logic to use payments-next.ftl Because: * payments-next no longer needs to rely on payments translations, as payments-next translations are completed. This commit: * Removes the dependency on payments.ftl translations within payments-next project.json and gruntfile.js Closes #FXA-7841 --- apps/payments/next/Gruntfile.js | 8 +- .../payments/next/app/_lib/scripts/convert.ts | 110 ----- .../app/_lib/scripts/create-payments-next.ts | 385 ------------------ apps/payments/next/project.json | 11 +- 4 files changed, 3 insertions(+), 511 deletions(-) delete mode 100644 apps/payments/next/app/_lib/scripts/convert.ts delete mode 100644 apps/payments/next/app/_lib/scripts/create-payments-next.ts diff --git a/apps/payments/next/Gruntfile.js b/apps/payments/next/Gruntfile.js index 9557a364b37..0c7885a470b 100644 --- a/apps/payments/next/Gruntfile.js +++ b/apps/payments/next/Gruntfile.js @@ -2,7 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -module.exports = function(grunt) { +module.exports = function (grunt) { const srcPaths = [ '.license.header', 'app/**/*.ftl', @@ -26,10 +26,6 @@ module.exports = function(grunt) { src: srcPaths, dest: 'public/locales/en/payments-next.ftl', }, - ftlLegacy: { - src: srcPaths, - dest: 'public/locales/en/payments.ftl', - }, }, http: { // Call the Payments Next route to restart and reinitialize the Nest App. @@ -76,6 +72,6 @@ module.exports = function(grunt) { grunt.loadNpmTasks('grunt-http'); grunt.loadNpmTasks('grunt-concurrent'); - grunt.registerTask('merge-ftl', ['copy:branding-ftl', 'concat:ftl', 'concat:ftlLegacy']); + grunt.registerTask('merge-ftl', ['copy:branding-ftl', 'concat:ftl']); grunt.registerTask('watchers', ['concurrent:dev']); }; diff --git a/apps/payments/next/app/_lib/scripts/convert.ts b/apps/payments/next/app/_lib/scripts/convert.ts deleted file mode 100644 index 908410be9cd..00000000000 --- a/apps/payments/next/app/_lib/scripts/convert.ts +++ /dev/null @@ -1,110 +0,0 @@ -#!/usr/bin/env node -r esbuild-register -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -/** - * This is a temporary script to convert l10n strings to match the format that - * will be used in Payments Next - * - * For unchanged strings copied from SP2.5 to SP3.0, the l10n team will attempt - * to run a script to copy the existing translations from the SP2.5 id’s to the - * new SP3.0 ids. To help with this effort, when copying a string ID, the - * SubPlat team should pre-fix the string ID with ‘next-’. Example below - * - * SP2.5 string: coupon-promo-code-applied - * SP3.0 string: next-coupon-promo-code-applied - */ -import path from 'path'; -import { stat, readFile, writeFile, readdir } from 'fs/promises'; - -// These directories will be skipped since they do not have a payments.ftl file -// The "en" directory should also be skipped, since l10n-merge populates en/payments.ftl -// with latest ftl files sourced from the FxA repo. -const skipDir = [ - 'en', - 'templates', - 'ar', - 'bn', - 'bs', - 'gd', - 'hy-AM', - 'km', - 'ms', - 'my', - 'ne-NP', -]; - -function getFullPath(directory: string) { - const filename = './payments.ftl'; - return path.resolve(directory, filename); -} - -async function getFile(directory: string) { - const fullPath = getFullPath(directory); - return readFile(fullPath, { encoding: 'utf8' }); -} - -async function setFile(directory: string, contents: string) { - const fullPath = getFullPath(directory); - return writeFile(fullPath, contents, { encoding: 'utf8' }); -} - -function convertContents(content: string) { - const brokenUp = content.split('\n'); - const modifiedContent: string[] = []; - for (const line of brokenUp) { - let newLine = line; - if (/^[a-zA-Z]+$/.test(line.charAt(0))) { - if (line.split('-')[0] !== 'next') { - newLine = `next-${line}`; - } - } - modifiedContent.push(newLine); - } - - return modifiedContent.join('\n'); -} - -async function convertToNext(directory: string) { - try { - const contents = await getFile(directory); - const modifiedContents = convertContents(contents); - await setFile(directory, modifiedContents); - } catch (error) { - console.error(error); - } -} - -async function* directoryGen(baseDir: string) { - const files = await readdir(baseDir); - - for (const file of files) { - if (skipDir.includes(file)) { - continue; - } - const fullPath = path.resolve(baseDir, file); - const fileStat = await stat(fullPath); - if (fileStat.isDirectory()) { - yield fullPath; - } - } -} - -async function init() { - const baseDir = path.resolve(__dirname, '../../../public/locales'); - - for await (const dir of directoryGen(baseDir)) { - await convertToNext(dir); - } - - return 0; -} - -if (require.main === module) { - init() - .catch((err) => { - console.error(err); - process.exit(1); - }) - .then((result) => process.exit(result)); -} diff --git a/apps/payments/next/app/_lib/scripts/create-payments-next.ts b/apps/payments/next/app/_lib/scripts/create-payments-next.ts deleted file mode 100644 index 26ea75f2dad..00000000000 --- a/apps/payments/next/app/_lib/scripts/create-payments-next.ts +++ /dev/null @@ -1,385 +0,0 @@ -#!/usr/bin/env node -r esbuild-register -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -/** - * This is a temporary script to create the payments-next.ftl file for each locale - * by reusing existing translations from the payments.ftl files for each locale. - */ -import path from 'path'; -import { stat, readFile, writeFile, readdir } from 'fs/promises'; - -// These directories will be skipped since they do not have a payments.ftl file -// The "en" directory should also be skipped, since l10n-merge populates en/payments.ftl -// with latest ftl files sourced from the FxA repo. -const skipDir = [ - 'en', - 'templates', - 'ar', - 'bn', - 'bs', - 'gd', - 'hy-AM', - 'km', - 'ms', - 'my', - 'ne-NP', -]; - -/** - * Global variables - */ -let nextFtlIdMap = new Map(); -let templatePaymentsNextFtl = ''; -const convertReport: string[] = []; - -/** - * Helper functions - */ -const isNextFtlId = (id: string) => id.startsWith('next-'); - -const isFtlIdSuffix = (id: string) => id.trim().startsWith('.'); - -const getFtlId = (line: string) => line.split('=')[0].trim(); - -const getFtlValue = (line: string) => line.split('=')[1].trim(); - -const createFtlIdSuffixKey = (id: string, suffix: string) => `${id}${suffix}`; - -function getFullPath(directory: string, isNext = false) { - const filename = isNext ? './payments-next.ftl' : './payments.ftl'; - return path.resolve(directory, filename); -} - -async function getPaymentsFile(directory: string) { - const fullPath = getFullPath(directory, false); - return readFile(fullPath, { encoding: 'utf8' }); -} - -async function setPaymentsNextFile(directory: string, contents: string) { - const fullPath = getFullPath(directory, true); - return writeFile(fullPath, contents, { encoding: 'utf8' }); -} - -function identifyLineType(line: string) { - if (line.startsWith('##')) { - return 'sectionHeader'; - } else if (line.startsWith('next-')) { - if (nextFtlIdMap.has(getFtlId(line))) { - return 'validIdField'; - } else { - return 'invalidIdField'; - } - } else if (line.startsWith('# ')) { - return 'idFieldPrefix'; - } else if (isFtlIdSuffix(line)) { - return 'idFieldSuffix'; - } else if (line === '') { - return 'space'; - } else { - return 'other'; - } -} - -function getMapKey(currentLine: string, currentFtlId?: string) { - if (isNextFtlId(currentLine)) { - return getFtlId(currentLine); - } else if (isFtlIdSuffix(currentLine) && currentFtlId) { - return createFtlIdSuffixKey(currentFtlId, getFtlId(currentLine)); - } else { - return null; - } -} - -/** - * Functions to modify the value while writing to the new payments-next.ftl file - */ -function setEmptyLine(validIdLine: string): string | null { - const id = validIdLine.split('=')[0]; - - return `${id}=`; -} - -function setLocaleValue( - validIdLine: string, - valueMap?: Map, - currentId?: string -): string | null { - const id = getMapKey(validIdLine, currentId); - - if (!valueMap || !id) { - return null; - } - - const value = valueMap.get(id); - const rawFtlId = validIdLine.split('=')[0]; - - if (value === undefined || value === null) { - return null; - } else { - return `${rawFtlId}= ${value}`; - } -} - -type PreviousLineType = 'section' | 'validIdField'; - -function createPaymentsNextFtl( - paymentsFtlContent: string, - modifyValidIdLine: ( - line: string, - valueMap?: Map, - currentValidId?: string - ) => string | null, - localeMap?: Map -) { - const paymentsNextContent: string[] = []; - let sectionHeaderBuffer: string[] = []; - let currentWriteBuffer: string[] = []; - let prefixWriteBuffer: string[] = []; - let previousLineType: PreviousLineType | null = null; - let currentValidId: string | undefined = undefined; - - // This for-loop walks through the payments ftl content line by line and only writes the lines - // that are valid and should be included in the new ftl file. - // Valid lines include - // 1. Section headers (if that section has at least 1 valid id field) - // 2. Valid id fields that start with `next-` - // 3. Prefix, or comment lines, for Valid id fields - // 4. Suffix lines for valid id fields - // - // All other lines should be excluded from the new ftl file - // - // This for loop makes use of various buffers to store lines in a section - // and only writes the buffers to the new ftl file string when a new section - // is encountered, and the values in the buffer are valid. - for (const line of paymentsFtlContent.split('\n')) { - const lineType = identifyLineType(line); - - // Write buffers to output - if (lineType === 'sectionHeader' && previousLineType !== 'section') { - if (currentWriteBuffer.length) { - paymentsNextContent.push(...sectionHeaderBuffer); - paymentsNextContent.push(...currentWriteBuffer); - } - sectionHeaderBuffer = []; - currentWriteBuffer = []; - } - - // Write to buffers - switch (lineType) { - case 'sectionHeader': - if ( - previousLineType !== 'section' && - paymentsNextContent[paymentsNextContent.length - 1] !== '' - ) { - sectionHeaderBuffer.push(''); - } - sectionHeaderBuffer.push(line); - previousLineType = 'section'; - break; - case 'validIdField': - const modifiedLine = modifyValidIdLine(line, localeMap); - if (modifiedLine !== null) { - if (prefixWriteBuffer.length) { - currentWriteBuffer.push(...prefixWriteBuffer); - } - currentWriteBuffer.push(modifiedLine); - } else { - convertReport.push(`${getFtlId(line)} - Missing value`); - } - break; - case 'idFieldPrefix': - prefixWriteBuffer.push(line); - break; - case 'idFieldSuffix': - if (previousLineType === 'validIdField') { - const modifiedLine = modifyValidIdLine( - line, - localeMap, - currentValidId - ); - if (modifiedLine !== null) { - currentWriteBuffer.push(modifiedLine); - } - } - break; - case 'space': - if (previousLineType === 'section') { - sectionHeaderBuffer.push(line); - } - if (previousLineType === 'validIdField') { - currentWriteBuffer.push(line); - } - break; - } - - // Reset values - switch (lineType) { - case 'sectionHeader': - previousLineType = 'section'; - break; - case 'validIdField': - const validIdHasValue = !!modifyValidIdLine(line, localeMap); - if (validIdHasValue) { - currentValidId = getFtlId(line); - previousLineType = 'validIdField'; - } else { - currentValidId = ''; - previousLineType = null; - } - prefixWriteBuffer = []; - break; - case 'other': - case 'invalidIdField': - previousLineType = null; - currentValidId = undefined; - prefixWriteBuffer = []; - break; - } - } - - // Write the last section - if (currentWriteBuffer.length) { - paymentsNextContent.push(...sectionHeaderBuffer); - paymentsNextContent.push(...currentWriteBuffer); - } - - return paymentsNextContent.join('\n'); -} - -function addLocaleReport(directory: string) { - convertReport.push(''); - convertReport.push('------------------------------------------------'); - convertReport.push(directory); - convertReport.push('------------------------------------------------'); -} - -async function convertLocaleToNext(directory: string) { - try { - // create map for locale - const localeIdMap = await populateValidFtlIdMap(directory); - addLocaleReport(directory); - const paymentsNextFtlContent = createPaymentsNextFtl( - templatePaymentsNextFtl, - setLocaleValue, - localeIdMap - ); - await setPaymentsNextFile(directory, paymentsNextFtlContent); - } catch (error) { - console.error(error); - } -} - -async function* localeDirectoryGen(baseDir: string) { - const files = await readdir(baseDir); - - for (const file of files) { - // Temporary to only test on en-CA - //if (file !== 'de') continue; - - if (skipDir.includes(file)) { - continue; - } - const fullPath = path.resolve(baseDir, file); - const fileStat = await stat(fullPath); - if (fileStat.isDirectory()) { - yield fullPath; - } - } -} - -async function populateValidFtlIdMap(dir: string) { - const idMap = new Map(); - let currentId = ''; - - // Create a new map copying the keys from nextFtlIdMap - // and setting the value to null. - // Note: If a locales payments.ftl file does not have a value for that - // id, the value will remain null. - for (const [key] of nextFtlIdMap) { - idMap.set(key, null); - } - - // Fetch the current locale's payments.ftl file - const paymentsFtlContentEn = await getPaymentsFile(dir); - - // Populate idMap with the values from the locale's payments.ftl file - for (const line of paymentsFtlContentEn.split('\n')) { - const id = getMapKey(line, currentId); - if (id && idMap.has(id)) { - idMap.set(id, getFtlValue(line)); - } - - if (isNextFtlId(line)) { - currentId = getFtlId(line); - } else { - currentId = ''; - } - } - - return idMap; -} - -async function populateNextFtlIdMap(baseDir: string) { - const idMap = new Map(); - const paymentsFtlContentEn = await getPaymentsFile(`${baseDir}/en`); - - let currentId = ''; - for (const line of paymentsFtlContentEn.split('\n')) { - const id = getMapKey(line, currentId); - if (id) { - // Note, the value of this Map are only used to identify duplicate strings - // and are not used anywhere else in this program. - const currentValue = idMap.get(id); - if (currentValue) { - idMap.set(id, currentValue + 1); - } else { - idMap.set(id, 1); - } - } - if (isNextFtlId(line)) { - currentId = getFtlId(line); - } else { - currentId = ''; - } - } - - return idMap; -} - -async function createTemplatePaymentsNextFtl(baseDir: string) { - const fullPath = baseDir + '/en'; - const paymentsFtlContentEn = await getPaymentsFile(fullPath); - return createPaymentsNextFtl(paymentsFtlContentEn, setEmptyLine); -} - -async function init() { - const baseDir = path.resolve(__dirname, '../../../public/locales'); - - // Create template map and template file from en/payments.ftl - // Note: en/payments.ftl is a concatenation of all ftl files used in SP3.0 - nextFtlIdMap = await populateNextFtlIdMap(baseDir); - templatePaymentsNextFtl = await createTemplatePaymentsNextFtl(baseDir); - - // Create payments-next.ftl for each locale in `public/locales` - for await (const dir of localeDirectoryGen(baseDir)) { - await convertLocaleToNext(dir); - } - - await writeFile( - path.resolve(__dirname, 'report.txt'), - convertReport.join('\n'), - { encoding: 'utf8' } - ); - - return 0; -} - -if (require.main === module) { - init() - .catch((err) => { - console.error(err); - process.exit(1); - }) - .then((result) => process.exit(result)); -} diff --git a/apps/payments/next/project.json b/apps/payments/next/project.json index 7e7b246b07f..170c9625876 100644 --- a/apps/payments/next/project.json +++ b/apps/payments/next/project.json @@ -48,7 +48,6 @@ "command": "pm2 delete apps/payments/next/pm2.config.js" }, "l10n-merge": { - "dependsOn": ["l10n-convert"], "command": "yarn grunt --gruntfile='apps/payments/next/Gruntfile.js' merge-ftl" }, "l10n-prime": { @@ -56,19 +55,11 @@ }, "l10n-bundle": { "dependsOn": ["l10n-merge"], - "command": "./_scripts/l10n/bundle.sh apps/payments/next branding,react,payments" + "command": "./_scripts/l10n/bundle.sh apps/payments/next branding,react,payments-next" }, "watchers": { "command": "yarn grunt --gruntfile='apps/payments/next/Gruntfile.js' watchers" }, - "l10n-create": { - "dependsOn": ["l10n-merge"], - "command": "node -r esbuild-register apps/payments/next/app/_lib/scripts/create-payments-next.ts" - }, - "l10n-convert": { - "dependsOn": ["l10n-prime"], - "command": "node -r esbuild-register apps/payments/next/app/_lib/scripts/convert.ts" - }, "glean-generate": { "dependsOn": ["glean-lint"], "command": "yarn glean translate libs/shared/metrics/glean/src/registry/subplat-backend-metrics.yaml -f typescript_server -o libs/payments/metrics/src/lib/glean/__generated__"