Add CLI args --ftl-path and --ftl-prefix
This commit is contained in:
Родитель
a03ec9d81f
Коммит
2c84f03db1
12
cli.js
12
cli.js
|
@ -25,6 +25,18 @@ yargs(process.argv.slice(2))
|
|||
default: 'python -m black',
|
||||
type: 'string'
|
||||
},
|
||||
ftlPath: {
|
||||
alias: 'p',
|
||||
desc: 'Path to target FTL file',
|
||||
requiresArg: true,
|
||||
type: 'string'
|
||||
},
|
||||
ftlPrefix: {
|
||||
alias: 'x',
|
||||
desc: 'Prefix for Fluent message keys',
|
||||
requiresArg: true,
|
||||
type: 'string'
|
||||
},
|
||||
root: {
|
||||
alias: 'r',
|
||||
desc: 'Root of mozilla-central (usually autodetected)',
|
||||
|
|
|
@ -1,16 +1,24 @@
|
|||
import { Comment } from '@fluent/syntax'
|
||||
import { Comment as FluentComment } from '@fluent/syntax'
|
||||
import {
|
||||
Comment as PropComment,
|
||||
EmptyLine as PropEmptyLine
|
||||
} from 'dot-properties'
|
||||
import { addFluentPattern } from './add-fluent-pattern.js'
|
||||
|
||||
/**
|
||||
* @param {import('./parse-message-files.js').PropData} propData
|
||||
* @param {import('./transform-js').TransformOptions} options
|
||||
*/
|
||||
export function applyMigration({ ast, ftl, ftlTransform, migrate }) {
|
||||
export function applyMigration(
|
||||
{ ast, ftl, ftlTransform, migrate },
|
||||
{ ftlPath, ftlPrefix }
|
||||
) {
|
||||
let commentLines = []
|
||||
const commentNotes = {}
|
||||
|
||||
/**
|
||||
* @param {string | null} key
|
||||
* @returns {Comment | null}
|
||||
* @returns {FluentComment | null}
|
||||
*/
|
||||
const getComment = (key) => {
|
||||
let content = commentLines.join('\n').trim()
|
||||
|
@ -25,7 +33,7 @@ export function applyMigration({ ast, ftl, ftlTransform, migrate }) {
|
|||
const cn = commentNotes[key]
|
||||
if (cn) content = content ? cn + '\n' + content : cn
|
||||
}
|
||||
return key && content ? new Comment(content) : null
|
||||
return key && content ? new FluentComment(content) : null
|
||||
}
|
||||
|
||||
let nextCutAfter = -1
|
||||
|
@ -66,4 +74,17 @@ export function applyMigration({ ast, ftl, ftlTransform, migrate }) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ftlPath || ftlPrefix) {
|
||||
const insert = []
|
||||
if (ftlPath) insert.push(new PropComment(`FTL path: ${ftlPath}`))
|
||||
if (ftlPrefix) insert.push(new PropComment(`FTL prefix: ${ftlPrefix}`))
|
||||
|
||||
let pos = 0
|
||||
while (ast[pos].type === 'COMMENT') pos += 1
|
||||
if (pos > 0) insert.unshift(new PropEmptyLine())
|
||||
if (ast[pos].type !== 'EMPTY_LINE') insert.push(new PropEmptyLine())
|
||||
|
||||
ast.splice(pos, 0, ...insert)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
import { Comment, parse as parseFluent, Resource } from '@fluent/syntax'
|
||||
import { existsSync } from 'fs'
|
||||
import { readFile } from 'fs/promises'
|
||||
import { resolve } from 'path'
|
||||
import { types, visit } from 'recast'
|
||||
import { visit } from 'recast'
|
||||
import { resolveChromeUri } from 'resolve-chrome-uri'
|
||||
|
||||
import { parseMessageFiles } from './parse-message-files.js'
|
||||
import { parseStringBundleTags } from './parse-xhtml.js'
|
||||
|
||||
const n = types.namedTypes
|
||||
|
||||
export async function findExternalRefs(root, ast) {
|
||||
/**
|
||||
* @param {unknown} ast
|
||||
* @param {import('./transform-js.js').TransformOptions} options
|
||||
*/
|
||||
export async function findExternalRefs(ast, options) {
|
||||
/** @type {import('ast-types').NodePath[]} */
|
||||
const propertiesUriPaths = []
|
||||
/** @type {import('ast-types').NodePath[]} */
|
||||
|
@ -39,7 +38,7 @@ export async function findExternalRefs(root, ast) {
|
|||
*/
|
||||
const xhtml = []
|
||||
for (const uri of new Set(xhtmlUriPaths.map((path) => path.node.value))) {
|
||||
const filePaths = await resolveChromeUri(root, uri)
|
||||
const filePaths = await resolveChromeUri(options.root, uri)
|
||||
if (filePaths.size === 0) console.warn(`Unresolved URI: ${uri}`)
|
||||
else {
|
||||
for (const fp of filePaths) {
|
||||
|
@ -56,11 +55,11 @@ export async function findExternalRefs(root, ast) {
|
|||
/** @type {import('./parse-message-files.js').PropData[]} */
|
||||
const properties = []
|
||||
for (const uri of propUris) {
|
||||
const filePaths = await resolveChromeUri(root, uri)
|
||||
const filePaths = await resolveChromeUri(options.root, uri)
|
||||
if (filePaths.size === 0) console.warn(`Unresolved URI: ${uri}`)
|
||||
else {
|
||||
for (const fp of filePaths) {
|
||||
const data = await parseMessageFiles(fp)
|
||||
const data = await parseMessageFiles(fp, options)
|
||||
data.uri = uri
|
||||
properties.push(data)
|
||||
}
|
||||
|
|
|
@ -22,15 +22,16 @@ import { resolve } from 'path'
|
|||
|
||||
/**
|
||||
* @param {string} path
|
||||
* @param {import('./transform-js.js').TransformOptions} options
|
||||
* @returns {Promise<PropData>}
|
||||
*/
|
||||
export async function parseMessageFiles(path) {
|
||||
export async function parseMessageFiles(path, options) {
|
||||
const src = await readFile(path, 'utf8')
|
||||
const ast = parseLines(src, true)
|
||||
const msgKeys = ast
|
||||
.filter((node) => node.type === 'PAIR')
|
||||
.map((pair) => pair.key)
|
||||
const { ftlRoot, ftlPath, ftlPrefix } = getFtlMetadata(path, ast)
|
||||
const { ftlRoot, ftlPath, ftlPrefix } = getFtlMetadata(path, ast, options)
|
||||
const ftl = await getFluentResource(ftlRoot, ftlPath)
|
||||
return {
|
||||
uri: '', // filled in by caller
|
||||
|
@ -55,42 +56,26 @@ export async function parseMessageFiles(path) {
|
|||
*
|
||||
* @param {string} propPath - The location of the .properties file
|
||||
* @param {import('dot-properties').Node[]} ast
|
||||
* @param {import('./transform-js.js').TransformOptions} options
|
||||
*/
|
||||
function getFtlMetadata(propPath, ast) {
|
||||
let ftlPath = null
|
||||
let ftlRoot = null
|
||||
let ftlPrefix = ''
|
||||
function getFtlMetadata(propPath, ast, options) {
|
||||
let rawFtlPath = options.ftlPath || null
|
||||
let ftlPrefix = options.ftlPrefix || ''
|
||||
|
||||
for (const node of ast) {
|
||||
if (node.type === 'COMMENT') {
|
||||
const match = node.comment.match(/[!#]\s*FTL\s+(path|prefix):(.*)/)
|
||||
if (match)
|
||||
switch (match[1]) {
|
||||
case 'path': {
|
||||
if (ftlPath)
|
||||
throw new Error(`FTL path set more than once in ${propPath}`)
|
||||
const parts = match[2].trim().split('/')
|
||||
const fi = parts.indexOf('en-US')
|
||||
if (fi !== -1) {
|
||||
ftlRoot = parts.splice(0, fi + 1).join('/')
|
||||
} else {
|
||||
const propPathParts = propPath.split('/')
|
||||
const i = propPathParts.indexOf('en-US')
|
||||
if (i === -1)
|
||||
throw new Error(
|
||||
`A full FTL file path is required in ${propPath}`
|
||||
)
|
||||
ftlRoot = propPathParts.slice(0, i + 1).join('/')
|
||||
}
|
||||
ftlPath = parts.join('/')
|
||||
if (!ftlPath.endsWith('.ftl'))
|
||||
throw new Error(
|
||||
`FTL file path should be fully qualified with an .ftl extension in ${propPath}`
|
||||
)
|
||||
if (rawFtlPath)
|
||||
throw new Error(`FTL path set more than once for ${propPath}`)
|
||||
rawFtlPath = match[2].trim()
|
||||
break
|
||||
}
|
||||
case 'prefix':
|
||||
if (ftlPrefix)
|
||||
throw new Error(`FTL prefix set more than once in ${propPath}`)
|
||||
throw new Error(`FTL prefix set more than once for ${propPath}`)
|
||||
ftlPrefix = match[2].trim()
|
||||
if (/[^a-z-]/.test(ftlPrefix))
|
||||
throw new Error(
|
||||
|
@ -100,9 +85,40 @@ function getFtlMetadata(propPath, ast) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
const { ftlPath, ftlRoot } = parseFtlPath(propPath, rawFtlPath)
|
||||
return { ftlPath, ftlRoot, ftlPrefix }
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} propPath
|
||||
* @param {string} raw
|
||||
*/
|
||||
function parseFtlPath(propPath, raw) {
|
||||
/** @type {string} */
|
||||
let ftlRoot
|
||||
const parts = raw.split('/')
|
||||
|
||||
const fi = parts.indexOf('en-US')
|
||||
if (fi !== -1) {
|
||||
ftlRoot = parts.splice(0, fi + 1).join('/')
|
||||
} else {
|
||||
const propPathParts = propPath.split('/')
|
||||
const i = propPathParts.indexOf('en-US')
|
||||
if (i === -1)
|
||||
throw new Error(`A full FTL file path is required for ${propPath}`)
|
||||
ftlRoot = propPathParts.slice(0, i + 1).join('/')
|
||||
}
|
||||
|
||||
const ftlPath = parts.join('/')
|
||||
if (!ftlPath.endsWith('.ftl'))
|
||||
throw new Error(
|
||||
`FTL file path should be fully qualified with an .ftl extension for ${propPath}`
|
||||
)
|
||||
|
||||
return { ftlPath, ftlRoot }
|
||||
}
|
||||
|
||||
const mplLicenseHeader = `
|
||||
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
|
||||
|
|
|
@ -18,6 +18,8 @@ const n = types.namedTypes
|
|||
* bug?: string,
|
||||
* dryRun?: boolean,
|
||||
* format?: string,
|
||||
* ftlPath?: string,
|
||||
* ftlPrefix?: string,
|
||||
* root?: string,
|
||||
* title?: string
|
||||
* }} TransformOptions
|
||||
|
@ -41,22 +43,22 @@ export async function transformJs(jsPath, options = {}) {
|
|||
|
||||
// Parse the source JS, XHTML & .properties files
|
||||
const ast = jsParse(await readFile(jsPath, 'utf8'))
|
||||
const { properties, xhtml } = await findExternalRefs(options.root, ast)
|
||||
const { properties, xhtml } = await findExternalRefs(ast, options)
|
||||
|
||||
let hasPropMigrations = false
|
||||
let migrationCount = 0
|
||||
for (const props of properties) {
|
||||
if (props.ftl) hasPropMigrations = true
|
||||
if (props.ftl) migrationCount += 1
|
||||
else
|
||||
console.warn(
|
||||
`Skipping ${relative(options.root, props.path)} (No FTL metadata)`
|
||||
)
|
||||
}
|
||||
if (!hasPropMigrations) {
|
||||
if (migrationCount === 0) {
|
||||
console.error(`
|
||||
Error: No migrations defined!
|
||||
|
||||
In order to migrate strings to Fluent, at least one of the .properties files
|
||||
must include FTL metadata comments:
|
||||
In order to migrate strings to Fluent, the --ftl-path option must be set or
|
||||
at least one of the .properties files must include FTL metadata comments:
|
||||
|
||||
# FTL path: foo/bar/baz.ftl
|
||||
# FTL prefix: foobar
|
||||
|
@ -65,6 +67,21 @@ For more information, see: https://github.com/eemeli/properties-to-ftl#readme
|
|||
`)
|
||||
process.exit(1)
|
||||
}
|
||||
if (migrationCount > 1 && (options.ftlPath || options.ftlPrefix)) {
|
||||
console.error(`
|
||||
Error: Multiple .properties found together with --ftl-path or --ftl-prefix!
|
||||
|
||||
When migrating strings from more than one .properties file, they must include
|
||||
FTL metadata comments directly, and cannot be given as command line arguments:
|
||||
|
||||
# FTL path: foo/bar/baz.ftl
|
||||
# FTL prefix: foobar
|
||||
|
||||
For more information, see: https://github.com/eemeli/properties-to-ftl#readme
|
||||
`)
|
||||
process.exit(1)
|
||||
|
||||
}
|
||||
|
||||
const fixmeNodes = new Set()
|
||||
|
||||
|
|
|
@ -21,10 +21,7 @@ const execFile = promisify(execFileCb)
|
|||
* @returns {Promise<boolean>} If `true`, at least one string was migrated.
|
||||
* If `false`, nothing was written to disk.
|
||||
*/
|
||||
export async function updateLocalizationFiles(
|
||||
propData,
|
||||
{ bug, dryRun, format, root, title }
|
||||
) {
|
||||
export async function updateLocalizationFiles(propData, options) {
|
||||
if (!propData.ftl) return false
|
||||
const n = Object.keys(propData.migrate).length
|
||||
if (n === 0) {
|
||||
|
@ -32,12 +29,14 @@ export async function updateLocalizationFiles(
|
|||
return false
|
||||
}
|
||||
|
||||
applyMigration(propData, options)
|
||||
|
||||
const { bug, dryRun, format, root, title } = options
|
||||
|
||||
const rpp = relative(root, propData.path)
|
||||
const fp = resolve(propData.ftlRoot, propData.ftlPath)
|
||||
|
||||
let pyPath = ''
|
||||
let propsRemoved = false
|
||||
applyMigration(propData)
|
||||
if (dryRun) {
|
||||
serializeFluent(propData.ftl)
|
||||
stringifyProperties(propData.ast, {
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
"scripts": {
|
||||
"clean": "git clean -fx test/artifacts && git restore test/artifacts",
|
||||
"test": "npm run test:dlu && npm run test:had",
|
||||
"test:dlu": "cd test/artifacts && node ../../cli.js toolkit/mozapps/downloads/DownloadUtils.jsm",
|
||||
"test:dlu": "cd test/artifacts && node ../../cli.js -p downloads.ftl -x downloads toolkit/mozapps/downloads/DownloadUtils.jsm",
|
||||
"test:had": "cd test/artifacts && node ../../cli.js toolkit/mozapps/downloads/HelperAppDlg.jsm"
|
||||
},
|
||||
"dependencies": {
|
||||
|
|
|
@ -2,9 +2,6 @@
|
|||
# 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/.
|
||||
|
||||
# FTL path: downloads.ftl
|
||||
# FTL prefix: downloads
|
||||
|
||||
# LOCALIZATION NOTE (shortSeconds): Semi-colon list of plural
|
||||
# forms. See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
|
||||
# s is the short form for seconds
|
||||
|
|
Загрузка…
Ссылка в новой задаче