feat(compiler): add "original" placeholder value on extracted XMB (#25079)

Update XMB placeholders(<ph>) to include the original value on top of an
example. Placeholders can by definition have one example(<ex>) tag and a
text node. The text node is used by TC as the "original" value from the
placeholder, while the example should represent a dummy value.
For example: <ph name="PET"><ex>Gopher</ex>{{ petName }}</ph>.
This change makes sure that we have the original text, but it *DOES NOT*
make sure that the example is correct. The example has the same wrong
behavior of showing the interpolation text rather than a useful
example.

No breaking changes, but tools that depend on the previous behavior and
don't consider the full XMB definition may fail to parse the XMB.
Fixes b/72565847

PR Close #25079
This commit is contained in:
Carlos Ortiz Garcia
2018-07-24 16:24:45 -07:00
committed by Igor Minar
parent 24789e9ad9
commit e99d860393
6 changed files with 171 additions and 39 deletions

View File

@ -15,7 +15,7 @@ import * as xml from './xml_helper';
const _MESSAGES_TAG = 'messagebundle';
const _MESSAGE_TAG = 'msg';
const _PLACEHOLDER_TAG = 'ph';
const _EXEMPLE_TAG = 'ex';
const _EXAMPLE_TAG = 'ex';
const _SOURCE_TAG = 'source';
const _DOCTYPE = `<!ELEMENT messagebundle (msg)*>
@ -115,30 +115,45 @@ class _Visitor implements i18n.Visitor {
}
visitTagPlaceholder(ph: i18n.TagPlaceholder, context?: any): xml.Node[] {
const startEx = new xml.Tag(_EXEMPLE_TAG, {}, [new xml.Text(`<${ph.tag}>`)]);
const startTagPh = new xml.Tag(_PLACEHOLDER_TAG, {name: ph.startName}, [startEx]);
const startTagAsText = new xml.Text(`<${ph.tag}>`);
const startEx = new xml.Tag(_EXAMPLE_TAG, {}, [startTagAsText]);
// TC requires PH to have a non empty EX, and uses the text node to show the "original" value.
const startTagPh =
new xml.Tag(_PLACEHOLDER_TAG, {name: ph.startName}, [startEx, startTagAsText]);
if (ph.isVoid) {
// void tags have no children nor closing tags
return [startTagPh];
}
const closeEx = new xml.Tag(_EXEMPLE_TAG, {}, [new xml.Text(`</${ph.tag}>`)]);
const closeTagPh = new xml.Tag(_PLACEHOLDER_TAG, {name: ph.closeName}, [closeEx]);
const closeTagAsText = new xml.Text(`</${ph.tag}>`);
const closeEx = new xml.Tag(_EXAMPLE_TAG, {}, [closeTagAsText]);
// TC requires PH to have a non empty EX, and uses the text node to show the "original" value.
const closeTagPh =
new xml.Tag(_PLACEHOLDER_TAG, {name: ph.closeName}, [closeEx, closeTagAsText]);
return [startTagPh, ...this.serialize(ph.children), closeTagPh];
}
visitPlaceholder(ph: i18n.Placeholder, context?: any): xml.Node[] {
const exTag = new xml.Tag(_EXEMPLE_TAG, {}, [new xml.Text(`{{${ph.value}}}`)]);
return [new xml.Tag(_PLACEHOLDER_TAG, {name: ph.name}, [exTag])];
const interpolationAsText = new xml.Text(`{{${ph.value}}}`);
// Example tag needs to be not-empty for TC.
const exTag = new xml.Tag(_EXAMPLE_TAG, {}, [interpolationAsText]);
return [
// TC requires PH to have a non empty EX, and uses the text node to show the "original" value.
new xml.Tag(_PLACEHOLDER_TAG, {name: ph.name}, [exTag, interpolationAsText])
];
}
visitIcuPlaceholder(ph: i18n.IcuPlaceholder, context?: any): xml.Node[] {
const exTag = new xml.Tag(_EXEMPLE_TAG, {}, [
new xml.Text(
`{${ph.value.expression}, ${ph.value.type}, ${Object.keys(ph.value.cases).map((value: string) => value + ' {...}').join(' ')}}`)
]);
return [new xml.Tag(_PLACEHOLDER_TAG, {name: ph.name}, [exTag])];
const icuExpression = ph.value.expression;
const icuType = ph.value.type;
const icuCases = Object.keys(ph.value.cases).map((value: string) => value + ' {...}').join(' ');
const icuAsText = new xml.Text(`{${icuExpression}, ${icuType}, ${icuCases}}`);
const exTag = new xml.Tag(_EXAMPLE_TAG, {}, [icuAsText]);
return [
// TC requires PH to have a non empty EX, and uses the text node to show the "original" value.
new xml.Tag(_PLACEHOLDER_TAG, {name: ph.name}, [exTag, icuAsText])
];
}
serialize(nodes: i18n.Node[]): xml.Node[] {
@ -161,7 +176,7 @@ class ExampleVisitor implements xml.IVisitor {
if (tag.name === _PLACEHOLDER_TAG) {
if (!tag.children || tag.children.length == 0) {
const exText = new xml.Text(tag.attrs['name'] || '...');
tag.children = [new xml.Tag(_EXEMPLE_TAG, {}, [exText])];
tag.children = [new xml.Tag(_EXAMPLE_TAG, {}, [exText])];
}
} else if (tag.children) {
tag.children.forEach(node => node.visit(this));