feat(compiler): add target locale to the translation bundles (#14290)
PR Close #14290
This commit is contained in:

committed by
Miško Hevery

parent
4676df5833
commit
bb4db2d8f3
@ -14,7 +14,8 @@ export abstract class Serializer {
|
||||
// - Placeholder names are already map to public names using the provided mapper
|
||||
abstract write(messages: i18n.Message[]): string;
|
||||
|
||||
abstract load(content: string, url: string): {[msgId: string]: i18n.Node[]};
|
||||
abstract load(content: string, url: string):
|
||||
{locale: string | null, i18nNodesByMsgId: {[msgId: string]: i18n.Node[]}};
|
||||
|
||||
abstract digest(message: i18n.Message): string;
|
||||
|
||||
|
@ -21,6 +21,7 @@ const _XMLNS = 'urn:oasis:names:tc:xliff:document:1.2';
|
||||
const _SOURCE_LANG = 'en';
|
||||
const _PLACEHOLDER_TAG = 'x';
|
||||
|
||||
const _FILE_TAG = 'file';
|
||||
const _SOURCE_TAG = 'source';
|
||||
const _TARGET_TAG = 'target';
|
||||
const _UNIT_TAG = 'trans-unit';
|
||||
@ -68,10 +69,11 @@ export class Xliff extends Serializer {
|
||||
]);
|
||||
}
|
||||
|
||||
load(content: string, url: string): {[msgId: string]: i18n.Node[]} {
|
||||
load(content: string, url: string):
|
||||
{locale: string, i18nNodesByMsgId: {[msgId: string]: i18n.Node[]}} {
|
||||
// xliff to xml nodes
|
||||
const xliffParser = new XliffParser();
|
||||
const {mlNodesByMsgId, errors} = xliffParser.parse(content, url);
|
||||
const {locale, mlNodesByMsgId, errors} = xliffParser.parse(content, url);
|
||||
|
||||
// xml nodes to i18n nodes
|
||||
const i18nNodesByMsgId: {[msgId: string]: i18n.Node[]} = {};
|
||||
@ -86,7 +88,7 @@ export class Xliff extends Serializer {
|
||||
throw new Error(`xliff parse errors:\n${errors.join('\n')}`);
|
||||
}
|
||||
|
||||
return i18nNodesByMsgId;
|
||||
return {locale, i18nNodesByMsgId};
|
||||
}
|
||||
|
||||
digest(message: i18n.Message): string { return digest(message); }
|
||||
@ -154,6 +156,7 @@ class XliffParser implements ml.Visitor {
|
||||
private _unitMlNodes: ml.Node[];
|
||||
private _errors: I18nError[];
|
||||
private _mlNodesByMsgId: {[msgId: string]: ml.Node[]};
|
||||
private _locale: string|null = null;
|
||||
|
||||
parse(xliff: string, url: string) {
|
||||
this._unitMlNodes = [];
|
||||
@ -167,6 +170,7 @@ class XliffParser implements ml.Visitor {
|
||||
return {
|
||||
mlNodesByMsgId: this._mlNodesByMsgId,
|
||||
errors: this._errors,
|
||||
locale: this._locale,
|
||||
};
|
||||
}
|
||||
|
||||
@ -200,6 +204,14 @@ class XliffParser implements ml.Visitor {
|
||||
this._unitMlNodes = element.children;
|
||||
break;
|
||||
|
||||
case _FILE_TAG:
|
||||
const localeAttr = element.attrs.find((attr) => attr.name === 'target-language');
|
||||
if (localeAttr) {
|
||||
this._locale = localeAttr.value;
|
||||
}
|
||||
ml.visitAll(this, element.children, null);
|
||||
break;
|
||||
|
||||
default:
|
||||
// TODO(vicb): assert file structure, xliff version
|
||||
// For now only recurse on unhandled nodes
|
||||
|
@ -70,7 +70,8 @@ export class Xmb extends Serializer {
|
||||
]);
|
||||
}
|
||||
|
||||
load(content: string, url: string): {[msgId: string]: i18n.Node[]} {
|
||||
load(content: string, url: string):
|
||||
{locale: string, i18nNodesByMsgId: {[msgId: string]: i18n.Node[]}} {
|
||||
throw new Error('Unsupported');
|
||||
}
|
||||
|
||||
|
@ -21,10 +21,11 @@ const _PLACEHOLDER_TAG = 'ph';
|
||||
export class Xtb extends Serializer {
|
||||
write(messages: i18n.Message[]): string { throw new Error('Unsupported'); }
|
||||
|
||||
load(content: string, url: string): {[msgId: string]: i18n.Node[]} {
|
||||
load(content: string, url: string):
|
||||
{locale: string, i18nNodesByMsgId: {[msgId: string]: i18n.Node[]}} {
|
||||
// xtb to xml nodes
|
||||
const xtbParser = new XtbParser();
|
||||
const {msgIdToHtml, errors} = xtbParser.parse(content, url);
|
||||
const {locale, msgIdToHtml, errors} = xtbParser.parse(content, url);
|
||||
|
||||
// xml nodes to i18n nodes
|
||||
const i18nNodesByMsgId: {[msgId: string]: i18n.Node[]} = {};
|
||||
@ -48,7 +49,7 @@ export class Xtb extends Serializer {
|
||||
throw new Error(`xtb parse errors:\n${errors.join('\n')}`);
|
||||
}
|
||||
|
||||
return i18nNodesByMsgId;
|
||||
return {locale, i18nNodesByMsgId};
|
||||
}
|
||||
|
||||
digest(message: i18n.Message): string { return digest(message); }
|
||||
@ -76,6 +77,7 @@ class XtbParser implements ml.Visitor {
|
||||
private _bundleDepth: number;
|
||||
private _errors: I18nError[];
|
||||
private _msgIdToHtml: {[msgId: string]: string};
|
||||
private _locale: string|null = null;
|
||||
|
||||
parse(xtb: string, url: string) {
|
||||
this._bundleDepth = 0;
|
||||
@ -91,6 +93,7 @@ class XtbParser implements ml.Visitor {
|
||||
return {
|
||||
msgIdToHtml: this._msgIdToHtml,
|
||||
errors: this._errors,
|
||||
locale: this._locale,
|
||||
};
|
||||
}
|
||||
|
||||
@ -101,6 +104,10 @@ class XtbParser implements ml.Visitor {
|
||||
if (this._bundleDepth > 1) {
|
||||
this._addError(element, `<${_TRANSLATIONS_TAG}> elements can not be nested`);
|
||||
}
|
||||
const langAttr = element.attrs.find((attr) => attr.name === 'lang');
|
||||
if (langAttr) {
|
||||
this._locale = langAttr.value;
|
||||
}
|
||||
ml.visitAll(this, element.children, null);
|
||||
this._bundleDepth--;
|
||||
break;
|
||||
|
@ -23,13 +23,13 @@ export class TranslationBundle {
|
||||
private _i18nToHtml: I18nToHtmlVisitor;
|
||||
|
||||
constructor(
|
||||
private _i18nNodesByMsgId: {[msgId: string]: i18n.Node[]} = {},
|
||||
private _i18nNodesByMsgId: {[msgId: string]: i18n.Node[]} = {}, locale: string|null,
|
||||
public digest: (m: i18n.Message) => string,
|
||||
public mapperFactory?: (m: i18n.Message) => PlaceholderMapper,
|
||||
missingTranslationStrategy: MissingTranslationStrategy = MissingTranslationStrategy.Warning,
|
||||
console?: Console) {
|
||||
this._i18nToHtml = new I18nToHtmlVisitor(
|
||||
_i18nNodesByMsgId, digest, mapperFactory, missingTranslationStrategy, console);
|
||||
_i18nNodesByMsgId, locale, digest, mapperFactory, missingTranslationStrategy, console);
|
||||
}
|
||||
|
||||
// Creates a `TranslationBundle` by parsing the given `content` with the `serializer`.
|
||||
@ -37,11 +37,11 @@ export class TranslationBundle {
|
||||
content: string, url: string, serializer: Serializer,
|
||||
missingTranslationStrategy: MissingTranslationStrategy,
|
||||
console?: Console): TranslationBundle {
|
||||
const i18nNodesByMsgId = serializer.load(content, url);
|
||||
const {locale, i18nNodesByMsgId} = serializer.load(content, url);
|
||||
const digestFn = (m: i18n.Message) => serializer.digest(m);
|
||||
const mapperFactory = (m: i18n.Message) => serializer.createNameMapper(m);
|
||||
return new TranslationBundle(
|
||||
i18nNodesByMsgId, digestFn, mapperFactory, missingTranslationStrategy, console);
|
||||
i18nNodesByMsgId, locale, digestFn, mapperFactory, missingTranslationStrategy, console);
|
||||
}
|
||||
|
||||
// Returns the translation as HTML nodes from the given source message.
|
||||
@ -65,7 +65,7 @@ class I18nToHtmlVisitor implements i18n.Visitor {
|
||||
private _mapper: (name: string) => string;
|
||||
|
||||
constructor(
|
||||
private _i18nNodesByMsgId: {[msgId: string]: i18n.Node[]} = {},
|
||||
private _i18nNodesByMsgId: {[msgId: string]: i18n.Node[]} = {}, private _locale: string|null,
|
||||
private _digest: (m: i18n.Message) => string,
|
||||
private _mapperFactory: (m: i18n.Message) => PlaceholderMapper,
|
||||
private _missingTranslationStrategy: MissingTranslationStrategy, private _console?: Console) {
|
||||
@ -166,11 +166,13 @@ class I18nToHtmlVisitor implements i18n.Visitor {
|
||||
// - use the nodes from the original message
|
||||
// - placeholders are already internal and need no mapper
|
||||
if (this._missingTranslationStrategy === MissingTranslationStrategy.Error) {
|
||||
this._addError(srcMsg.nodes[0], `Missing translation for message "${id}"`);
|
||||
const ctx = this._locale ? ` for locale "${this._locale}"` : '';
|
||||
this._addError(srcMsg.nodes[0], `Missing translation for message "${id}"${ctx}`);
|
||||
} else if (
|
||||
this._console &&
|
||||
this._missingTranslationStrategy === MissingTranslationStrategy.Warning) {
|
||||
this._console.warn(`Missing translation for message "${id}"`);
|
||||
const ctx = this._locale ? ` for locale "${this._locale}"` : '';
|
||||
this._console.warn(`Missing translation for message "${id}"${ctx}`);
|
||||
}
|
||||
nodes = srcMsg.nodes;
|
||||
this._mapper = (name: string) => name;
|
||||
|
Reference in New Issue
Block a user