@lit/localize-tools
Advanced tools
Comparing version 0.3.2 to 0.3.3
@@ -140,9 +140,39 @@ /** | ||
// For example, if some placeholders were reordered from [0 1 2] to [2 0 1], | ||
// then we'll generate a template like: html`foo ${2} bar ${0} baz ${1}` | ||
const placeholderOrder = new Map(canon.contents | ||
.filter((value) => typeof value !== 'string') | ||
.map((placeholder, idx) => [ | ||
// then we'll generate a template like: html`foo ${2} bar ${0} baz ${1}`. | ||
// | ||
// This map provides the absolute index within a template for each expression | ||
// within the template. We identify each expression with the compound key | ||
// [placeholder id, relative expression index]. | ||
// | ||
// Note that any given XLIFF/XLB <ph> placeholder can contain zero, one, or | ||
// many ${} expressions, so the index of the _placeholder_ is not the same as | ||
// the index of the _expression_: | ||
// | ||
// <ph><a href="http://example.com/"></ph> | ||
// <ph><a href="${/*0*/ url}"></ph> | ||
// <ph><a href="${/*1*/ url}/${/*2*/ path}"></ph> | ||
const placeholderOrder = new Map(); | ||
const placeholderOrderKey = (placeholder, placeholderRelativeExpressionIdx) => JSON.stringify([ | ||
// TODO(aomarks) For XLIFF files, we have a unique numeric ID for each | ||
// placeholder that would be preferable to use as the key here over the | ||
// placeholder text itself. However, we don't currently have that ID for | ||
// XLB. To add it to XLB, we need to do some research into the correct XML | ||
// representation, and then make a breaking change. See | ||
// https://github.com/lit/lit/issues/1897. | ||
placeholder.untranslatable, | ||
idx, | ||
])); | ||
placeholderRelativeExpressionIdx, | ||
]); | ||
let absIdx = 0; | ||
for (const content of canon.contents) { | ||
if (typeof content === 'string') { | ||
continue; | ||
} | ||
const template = parseStringAsTemplateLiteral(content.untranslatable); | ||
if (ts.isNoSubstitutionTemplateLiteral(template)) { | ||
continue; | ||
} | ||
for (let relIdx = 0; relIdx < template.templateSpans.length; relIdx++) { | ||
placeholderOrder.set(placeholderOrderKey(content, relIdx), absIdx++); | ||
} | ||
} | ||
const fragments = []; | ||
@@ -160,6 +190,6 @@ for (const content of contents) { | ||
fragments.push(template.head.text); | ||
for (const span of template.templateSpans) { | ||
// Substitute the value with the index (see note above). | ||
fragments.push('${' + placeholderOrder.get(content.untranslatable) + '}'); | ||
fragments.push(span.literal.text); | ||
for (let relIdx = 0; relIdx < template.templateSpans.length; relIdx++) { | ||
const absIdx = placeholderOrder.get(placeholderOrderKey(content, relIdx)); | ||
fragments.push('${' + absIdx + '}'); | ||
fragments.push(template.templateSpans[relIdx].literal.text); | ||
} | ||
@@ -166,0 +196,0 @@ } |
@@ -124,3 +124,3 @@ /** | ||
const moduleSymbol = this.typeChecker.getSymbolAtLocation(node.moduleSpecifier); | ||
if (moduleSymbol && this.isLitLocalizeModule(moduleSymbol)) { | ||
if (moduleSymbol && this.fileNameAppearsToBeLitLocalize(moduleSymbol)) { | ||
return undefined; | ||
@@ -183,3 +183,4 @@ } | ||
const sourceFileSymbol = this.typeChecker.getSymbolAtLocation(sourceFile); | ||
if (sourceFileSymbol && this.isLitLocalizeModule(sourceFileSymbol)) { | ||
if (sourceFileSymbol && | ||
this.fileNameAppearsToBeLitLocalize(sourceFileSymbol)) { | ||
return ts.createStringLiteral('lit-localize-status'); | ||
@@ -342,15 +343,13 @@ } | ||
* Return whether the given symbol looks like one of the lit-localize modules | ||
* (because it exports one of the special tagged functions). | ||
* based on its filename. Note when we call this function, we're already | ||
* strongly suspecting a lit-localize call. | ||
*/ | ||
isLitLocalizeModule(moduleSymbol) { | ||
if (!moduleSymbol.exports) { | ||
return false; | ||
} | ||
const exports = moduleSymbol.exports.values(); | ||
for (const xport of exports) { | ||
const type = this.typeChecker.getTypeAtLocation(xport.valueDeclaration); | ||
const props = this.typeChecker.getPropertiesOfType(type); | ||
if (props.some((prop) => prop.escapedName === '_LIT_LOCALIZE_MSG_' || | ||
prop.escapedName === '_LIT_LOCALIZE_CONTROLLER_FN_' || | ||
prop.escapedName === '_LIT_LOCALIZE_DECORATOR_')) { | ||
fileNameAppearsToBeLitLocalize(moduleSymbol) { | ||
// TODO(aomarks) Find a better way to implement this. We could probably just | ||
// check for any file path matching '/@lit/localize/` -- however that will | ||
// fail our tests because we import with a relative path in that case. | ||
for (const decl of moduleSymbol.declarations) { | ||
if (ts.isSourceFile(decl) && | ||
(decl.fileName.endsWith('/localize/lit-localize.d.ts') || | ||
decl.fileName.endsWith('/localize/internal/locale-status-event.d.ts'))) { | ||
return true; | ||
@@ -357,0 +356,0 @@ } |
@@ -9,3 +9,3 @@ /** | ||
import { createDiagnostic } from './typescript.js'; | ||
import { generateMsgId, HASH_DELIMITER } from './id-generation.js'; | ||
import { generateMsgId, HASH_DELIMITER, } from '@lit/localize/internal/id-generation.js'; | ||
/** | ||
@@ -259,3 +259,5 @@ * Extract translation messages from all files in a TypeScript program. | ||
const EXPRESSION_START = `_START_LIT_LOCALIZE_EXPR_${EXPRESSION_RAND}_`; | ||
const EXPRESSION_START_REGEXP = new RegExp(EXPRESSION_START, 'g'); | ||
const EXPRESSION_END = `_END_LIT_LOCALIZE_EXPR_${EXPRESSION_RAND}_`; | ||
const EXPRESSION_END_REGEXP = new RegExp(EXPRESSION_END, 'g'); | ||
/** | ||
@@ -316,4 +318,4 @@ * Our template is split apart based on template string literal expressions. | ||
untranslatable: part.untranslatable | ||
.replace(EXPRESSION_START, '${') | ||
.replace(EXPRESSION_END, '}'), | ||
.replace(EXPRESSION_START_REGEXP, '${') | ||
.replace(EXPRESSION_END_REGEXP, '}'), | ||
}); | ||
@@ -320,0 +322,0 @@ } |
{ | ||
"name": "@lit/localize-tools", | ||
"version": "0.3.2", | ||
"version": "0.3.3", | ||
"publishConfig": { | ||
@@ -25,5 +25,5 @@ "access": "public" | ||
"generate-json-schema": "typescript-json-schema tsconfig.schema.json ConfigFile --include=src/types/config.d.ts --required --noExtraProps > config.schema.json", | ||
"test": "npm run build && npm run test:tape && npm run test:check-tsc", | ||
"test:tape": "tape-es 'lib/tests/**/*.test.js' | tap-spec", | ||
"test:update-goldens": "npm run build && UPDATE_TEST_GOLDENS=true npm run test:tape", | ||
"test": "npm run build && npm run test:unit && npm run test:check-tsc", | ||
"test:unit": "uvu lib/tests '\\.test\\.js$'", | ||
"test:update-goldens": "npm run build && UPDATE_TEST_GOLDENS=true npm run test:unit", | ||
"test:check-tsc": "ls testdata/*/output/tsconfig.json | xargs -n 1 tsc --noEmit --project", | ||
@@ -60,3 +60,2 @@ "prepack": "npm run build" | ||
"@types/prettier": "^2.0.1", | ||
"@types/tape": "^4.13.0", | ||
"@types/xmldom": "^0.1.29", | ||
@@ -67,7 +66,5 @@ "diff": "^5.0.0", | ||
"rimraf": "^3.0.2", | ||
"tap-spec": "^5.0.0", | ||
"tape": "^5.2.2", | ||
"tape-es": "^1.2.11", | ||
"typescript-json-schema": "^0.50.0" | ||
"typescript-json-schema": "^0.50.0", | ||
"uvu": "^0.5.1" | ||
} | ||
} |
@@ -198,12 +198,46 @@ /** | ||
// For example, if some placeholders were reordered from [0 1 2] to [2 0 1], | ||
// then we'll generate a template like: html`foo ${2} bar ${0} baz ${1}` | ||
const placeholderOrder = new Map<string, number>( | ||
canon.contents | ||
.filter((value) => typeof value !== 'string') | ||
.map((placeholder, idx) => [ | ||
(placeholder as Placeholder).untranslatable, | ||
idx, | ||
]) | ||
); | ||
// then we'll generate a template like: html`foo ${2} bar ${0} baz ${1}`. | ||
// | ||
// This map provides the absolute index within a template for each expression | ||
// within the template. We identify each expression with the compound key | ||
// [placeholder id, relative expression index]. | ||
// | ||
// Note that any given XLIFF/XLB <ph> placeholder can contain zero, one, or | ||
// many ${} expressions, so the index of the _placeholder_ is not the same as | ||
// the index of the _expression_: | ||
// | ||
// <ph><a href="http://example.com/"></ph> | ||
// <ph><a href="${/*0*/ url}"></ph> | ||
// <ph><a href="${/*1*/ url}/${/*2*/ path}"></ph> | ||
const placeholderOrder = new Map<string, number>(); | ||
const placeholderOrderKey = ( | ||
placeholder: Placeholder, | ||
placeholderRelativeExpressionIdx: number | ||
) => | ||
JSON.stringify([ | ||
// TODO(aomarks) For XLIFF files, we have a unique numeric ID for each | ||
// placeholder that would be preferable to use as the key here over the | ||
// placeholder text itself. However, we don't currently have that ID for | ||
// XLB. To add it to XLB, we need to do some research into the correct XML | ||
// representation, and then make a breaking change. See | ||
// https://github.com/lit/lit/issues/1897. | ||
placeholder.untranslatable, | ||
placeholderRelativeExpressionIdx, | ||
]); | ||
let absIdx = 0; | ||
for (const content of canon.contents) { | ||
if (typeof content === 'string') { | ||
continue; | ||
} | ||
const template = parseStringAsTemplateLiteral(content.untranslatable); | ||
if (ts.isNoSubstitutionTemplateLiteral(template)) { | ||
continue; | ||
} | ||
for (let relIdx = 0; relIdx < template.templateSpans.length; relIdx++) { | ||
placeholderOrder.set(placeholderOrderKey(content, relIdx), absIdx++); | ||
} | ||
} | ||
const fragments = []; | ||
@@ -219,8 +253,8 @@ for (const content of contents) { | ||
fragments.push(template.head.text); | ||
for (const span of template.templateSpans) { | ||
// Substitute the value with the index (see note above). | ||
fragments.push( | ||
'${' + placeholderOrder.get(content.untranslatable) + '}' | ||
); | ||
fragments.push(span.literal.text); | ||
for (let relIdx = 0; relIdx < template.templateSpans.length; relIdx++) { | ||
const absIdx: number = placeholderOrder.get( | ||
placeholderOrderKey(content, relIdx) | ||
)!; | ||
fragments.push('${' + absIdx + '}'); | ||
fragments.push(template.templateSpans[relIdx].literal.text); | ||
} | ||
@@ -227,0 +261,0 @@ } |
@@ -203,3 +203,3 @@ /** | ||
); | ||
if (moduleSymbol && this.isLitLocalizeModule(moduleSymbol)) { | ||
if (moduleSymbol && this.fileNameAppearsToBeLitLocalize(moduleSymbol)) { | ||
return undefined; | ||
@@ -301,3 +301,6 @@ } | ||
); | ||
if (sourceFileSymbol && this.isLitLocalizeModule(sourceFileSymbol)) { | ||
if ( | ||
sourceFileSymbol && | ||
this.fileNameAppearsToBeLitLocalize(sourceFileSymbol) | ||
) { | ||
return ts.createStringLiteral('lit-localize-status'); | ||
@@ -503,21 +506,14 @@ } | ||
* Return whether the given symbol looks like one of the lit-localize modules | ||
* (because it exports one of the special tagged functions). | ||
* based on its filename. Note when we call this function, we're already | ||
* strongly suspecting a lit-localize call. | ||
*/ | ||
isLitLocalizeModule(moduleSymbol: ts.Symbol): boolean { | ||
if (!moduleSymbol.exports) { | ||
return false; | ||
} | ||
const exports = moduleSymbol.exports.values(); | ||
for (const xport of exports as typeof exports & { | ||
[Symbol.iterator](): Iterator<ts.Symbol>; | ||
}) { | ||
const type = this.typeChecker.getTypeAtLocation(xport.valueDeclaration); | ||
const props = this.typeChecker.getPropertiesOfType(type); | ||
fileNameAppearsToBeLitLocalize(moduleSymbol: ts.Symbol): boolean { | ||
// TODO(aomarks) Find a better way to implement this. We could probably just | ||
// check for any file path matching '/@lit/localize/` -- however that will | ||
// fail our tests because we import with a relative path in that case. | ||
for (const decl of moduleSymbol.declarations) { | ||
if ( | ||
props.some( | ||
(prop) => | ||
prop.escapedName === '_LIT_LOCALIZE_MSG_' || | ||
prop.escapedName === '_LIT_LOCALIZE_CONTROLLER_FN_' || | ||
prop.escapedName === '_LIT_LOCALIZE_DECORATOR_' | ||
) | ||
ts.isSourceFile(decl) && | ||
(decl.fileName.endsWith('/localize/lit-localize.d.ts') || | ||
decl.fileName.endsWith('/localize/internal/locale-status-event.d.ts')) | ||
) { | ||
@@ -524,0 +520,0 @@ return true; |
@@ -11,3 +11,6 @@ /** | ||
import {createDiagnostic} from './typescript.js'; | ||
import {generateMsgId, HASH_DELIMITER} from './id-generation.js'; | ||
import { | ||
generateMsgId, | ||
HASH_DELIMITER, | ||
} from '@lit/localize/internal/id-generation.js'; | ||
@@ -21,5 +24,6 @@ type ResultOrError<R, E> = | ||
*/ | ||
export function extractMessagesFromProgram( | ||
program: ts.Program | ||
): {messages: ProgramMessage[]; errors: ts.Diagnostic[]} { | ||
export function extractMessagesFromProgram(program: ts.Program): { | ||
messages: ProgramMessage[]; | ||
errors: ts.Diagnostic[]; | ||
} { | ||
const messages: ProgramMessage[] = []; | ||
@@ -371,3 +375,5 @@ const errors: ts.Diagnostic[] = []; | ||
const EXPRESSION_START = `_START_LIT_LOCALIZE_EXPR_${EXPRESSION_RAND}_`; | ||
const EXPRESSION_START_REGEXP = new RegExp(EXPRESSION_START, 'g'); | ||
const EXPRESSION_END = `_END_LIT_LOCALIZE_EXPR_${EXPRESSION_RAND}_`; | ||
const EXPRESSION_END_REGEXP = new RegExp(EXPRESSION_END, 'g'); | ||
@@ -431,4 +437,4 @@ /** | ||
untranslatable: part.untranslatable | ||
.replace(EXPRESSION_START, '${') | ||
.replace(EXPRESSION_END, '}'), | ||
.replace(EXPRESSION_START_REGEXP, '${') | ||
.replace(EXPRESSION_END_REGEXP, '}'), | ||
}); | ||
@@ -525,5 +531,6 @@ } | ||
*/ | ||
function serializeOpenCloseTags( | ||
node: parse5.ChildNode | ||
): {open: string; close: string} { | ||
function serializeOpenCloseTags(node: parse5.ChildNode): { | ||
open: string; | ||
close: string; | ||
} { | ||
const withoutChildren: parse5.ChildNode = {...node, childNodes: []}; | ||
@@ -629,5 +636,6 @@ const fakeParent = {childNodes: [withoutChildren]} as parse5.Node; | ||
*/ | ||
function dedupeMessages( | ||
messages: ProgramMessage[] | ||
): {messages: ProgramMessage[]; errors: ts.Diagnostic[]} { | ||
function dedupeMessages(messages: ProgramMessage[]): { | ||
messages: ProgramMessage[]; | ||
errors: ts.Diagnostic[]; | ||
} { | ||
const errors: ts.Diagnostic[] = []; | ||
@@ -634,0 +642,0 @@ const cache = new Map<string, ProgramMessage>(); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
14
0
269632
73
5854