зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1453480 - Update fluent to 0.6.4 and fluent-dom to 0.2.0. r=stas
MozReview-Commit-ID: La8uSw0sq4p --HG-- extra : rebase_source : 95181fb06ec75c36ebc2c234fdac68956b9e049e
This commit is contained in:
Родитель
781fa96acd
Коммит
a8f62de0ae
|
@ -16,7 +16,7 @@
|
|||
*/
|
||||
|
||||
|
||||
/* fluent@0.6.3 */
|
||||
/* fluent-dom@0.2.0 */
|
||||
|
||||
const { Localization } =
|
||||
ChromeUtils.import("resource://gre/modules/Localization.jsm", {});
|
||||
|
@ -26,15 +26,18 @@ const { Localization } =
|
|||
const reOverlay = /<|&#?\w+;/;
|
||||
|
||||
/**
|
||||
* The list of elements that are allowed to be inserted into a localization.
|
||||
* Elements allowed in translations even if they are not present in the source
|
||||
* HTML. They are text-level elements as defined by the HTML5 spec:
|
||||
* https://www.w3.org/TR/html5/text-level-semantics.html with the exception of:
|
||||
*
|
||||
* Source: https://www.w3.org/TR/html5/text-level-semantics.html
|
||||
* - a - because we don't allow href on it anyways,
|
||||
* - ruby, rt, rp - because we don't allow nested elements to be inserted.
|
||||
*/
|
||||
const LOCALIZABLE_ELEMENTS = {
|
||||
const TEXT_LEVEL_ELEMENTS = {
|
||||
"http://www.w3.org/1999/xhtml": [
|
||||
"a", "em", "strong", "small", "s", "cite", "q", "dfn", "abbr", "data",
|
||||
"em", "strong", "small", "s", "cite", "q", "dfn", "abbr", "data",
|
||||
"time", "code", "var", "samp", "kbd", "sub", "sup", "i", "b", "u",
|
||||
"mark", "ruby", "rt", "rp", "bdi", "bdo", "span", "br", "wbr"
|
||||
"mark", "bdi", "bdo", "span", "br", "wbr"
|
||||
],
|
||||
};
|
||||
|
||||
|
@ -66,153 +69,172 @@ const LOCALIZABLE_ATTRIBUTES = {
|
|||
|
||||
|
||||
/**
|
||||
* Overlay translation onto a DOM element.
|
||||
* Translate an element.
|
||||
*
|
||||
* @param {Element} targetElement
|
||||
* @param {string|Object} translation
|
||||
* Translate the element's text content and attributes. Some HTML markup is
|
||||
* allowed in the translation. The element's children with the data-l10n-name
|
||||
* attribute will be treated as arguments to the translation. If the
|
||||
* translation defines the same children, their attributes and text contents
|
||||
* will be used for translating the matching source child.
|
||||
*
|
||||
* @param {Element} element
|
||||
* @param {Object} translation
|
||||
* @private
|
||||
*/
|
||||
function overlayElement(targetElement, translation) {
|
||||
function translateElement(element, translation) {
|
||||
const value = translation.value;
|
||||
|
||||
if (typeof value === "string") {
|
||||
if (!reOverlay.test(value)) {
|
||||
// If the translation doesn't contain any markup skip the overlay logic.
|
||||
targetElement.textContent = value;
|
||||
element.textContent = value;
|
||||
} else {
|
||||
// Else parse the translation's HTML using an inert template element,
|
||||
// sanitize it and replace the targetElement's content.
|
||||
const templateElement = targetElement.ownerDocument.createElementNS(
|
||||
"http://www.w3.org/1999/xhtml", "template");
|
||||
// sanitize it and replace the element's content.
|
||||
const templateElement = element.ownerDocument.createElementNS(
|
||||
"http://www.w3.org/1999/xhtml", "template"
|
||||
);
|
||||
// eslint-disable-next-line no-unsanitized/property
|
||||
templateElement.innerHTML = value;
|
||||
targetElement.appendChild(
|
||||
// The targetElement will be cleared at the end of sanitization.
|
||||
sanitizeUsing(templateElement.content, targetElement)
|
||||
);
|
||||
overlayChildNodes(templateElement.content, element);
|
||||
}
|
||||
}
|
||||
|
||||
const explicitlyAllowed = targetElement.hasAttribute("data-l10n-attrs")
|
||||
? targetElement.getAttribute("data-l10n-attrs")
|
||||
// Even if the translation doesn't define any localizable attributes, run
|
||||
// overlayAttributes to remove any localizable attributes set by previous
|
||||
// translations.
|
||||
overlayAttributes(translation, element);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace child nodes of an element with child nodes of another element.
|
||||
*
|
||||
* The contents of the target element will be cleared and fully replaced with
|
||||
* sanitized contents of the source element.
|
||||
*
|
||||
* @param {DocumentFragment} fromElement - The source of children to overlay.
|
||||
* @param {Element} toElement - The target of the overlay.
|
||||
* @private
|
||||
*/
|
||||
function overlayChildNodes(fromElement, toElement) {
|
||||
const content = toElement.ownerDocument.createDocumentFragment();
|
||||
|
||||
for (const childNode of fromElement.childNodes) {
|
||||
content.appendChild(sanitizeUsing(toElement, childNode));
|
||||
}
|
||||
|
||||
toElement.textContent = "";
|
||||
toElement.appendChild(content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transplant localizable attributes of an element to another element.
|
||||
*
|
||||
* Any localizable attributes already set on the target element will be
|
||||
* cleared.
|
||||
*
|
||||
* @param {Element|Object} fromElement - The source of child nodes to overlay.
|
||||
* @param {Element} toElement - The target of the overlay.
|
||||
* @private
|
||||
*/
|
||||
function overlayAttributes(fromElement, toElement) {
|
||||
const explicitlyAllowed = toElement.hasAttribute("data-l10n-attrs")
|
||||
? toElement.getAttribute("data-l10n-attrs")
|
||||
.split(",").map(i => i.trim())
|
||||
: null;
|
||||
|
||||
// Remove localizable attributes which may have been set by a previous
|
||||
// translation.
|
||||
for (const attr of Array.from(targetElement.attributes)) {
|
||||
if (isAttrNameLocalizable(attr.name, targetElement, explicitlyAllowed)) {
|
||||
targetElement.removeAttribute(attr.name);
|
||||
// Remove existing localizable attributes.
|
||||
for (const attr of Array.from(toElement.attributes)) {
|
||||
if (isAttrNameLocalizable(attr.name, toElement, explicitlyAllowed)) {
|
||||
toElement.removeAttribute(attr.name);
|
||||
}
|
||||
}
|
||||
|
||||
if (translation.attrs) {
|
||||
for (const {name, value} of translation.attrs) {
|
||||
if (isAttrNameLocalizable(name, targetElement, explicitlyAllowed)) {
|
||||
targetElement.setAttribute(name, value);
|
||||
}
|
||||
// fromElement might be a {value, attributes} object as returned by
|
||||
// Localization.messageFromContext. In which case attributes may be null to
|
||||
// save GC cycles.
|
||||
if (!fromElement.attributes) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set localizable attributes.
|
||||
for (const attr of Array.from(fromElement.attributes)) {
|
||||
if (isAttrNameLocalizable(attr.name, toElement, explicitlyAllowed)) {
|
||||
toElement.setAttribute(attr.name, attr.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize `translationFragment` using `sourceElement` to add functional
|
||||
* HTML attributes to children. `sourceElement` will have all its child nodes
|
||||
* removed.
|
||||
* Sanitize a child node created by the translation.
|
||||
*
|
||||
* The sanitization is conducted according to the following rules:
|
||||
* If childNode has the data-l10n-name attribute, try to find a corresponding
|
||||
* child in sourceElement and use it as the base for the sanitization. This
|
||||
* will preserve functional attribtues defined on the child element in the
|
||||
* source HTML.
|
||||
*
|
||||
* - Allow text nodes.
|
||||
* - Replace forbidden children with their textContent.
|
||||
* - Remove forbidden attributes from allowed children.
|
||||
* This function must return new nodes or clones in all code paths. The
|
||||
* returned nodes are immediately appended to the intermediate DocumentFragment
|
||||
* which also _removes_ them from the constructed <template> containing the
|
||||
* translation, which in turn breaks the for…of iteration over its child nodes.
|
||||
*
|
||||
* Additionally when a child of the same type is present in `sourceElement` its
|
||||
* attributes will be merged into the translated child. Whitelisted attributes
|
||||
* of the translated child will then overwrite the ones present in the source.
|
||||
*
|
||||
* The overlay logic is subject to the following limitations:
|
||||
*
|
||||
* - Children are always cloned. Event handlers attached to them are lost.
|
||||
* - Nested HTML in source and in translations is not supported.
|
||||
* - Multiple children of the same type will be matched in order.
|
||||
*
|
||||
* @param {DocumentFragment} translationFragment
|
||||
* @param {Element} sourceElement
|
||||
* @returns {DocumentFragment}
|
||||
* @param {Element} sourceElement - The source for data-l10n-name lookups.
|
||||
* @param {Element} childNode - The child node to be sanitized.
|
||||
* @returns {Element}
|
||||
* @private
|
||||
*/
|
||||
function sanitizeUsing(translationFragment, sourceElement) {
|
||||
const ownerDocument = translationFragment.ownerDocument;
|
||||
// Take one node from translationFragment at a time and check it against
|
||||
// the allowed list or try to match it with a corresponding element
|
||||
// in the source.
|
||||
for (const childNode of translationFragment.childNodes) {
|
||||
|
||||
if (childNode.nodeType === childNode.TEXT_NODE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the child is forbidden just take its textContent.
|
||||
if (!isElementLocalizable(childNode)) {
|
||||
const text = ownerDocument.createTextNode(childNode.textContent);
|
||||
translationFragment.replaceChild(text, childNode);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Start the sanitization with an empty element.
|
||||
const mergedChild = ownerDocument.createElement(childNode.localName);
|
||||
|
||||
// Explicitly discard nested HTML by serializing childNode to a TextNode.
|
||||
mergedChild.textContent = childNode.textContent;
|
||||
|
||||
// If a child of the same type exists in sourceElement, take its functional
|
||||
// (i.e. non-localizable) attributes. This also removes the child from
|
||||
// sourceElement.
|
||||
const sourceChild = shiftNamedElement(sourceElement, childNode.localName);
|
||||
|
||||
// Find the union of all safe attributes: localizable attributes from
|
||||
// childNode and functional attributes from sourceChild.
|
||||
const safeAttributes = sanitizeAttrsUsing(childNode, sourceChild);
|
||||
|
||||
for (const attr of safeAttributes) {
|
||||
mergedChild.setAttribute(attr.name, attr.value);
|
||||
}
|
||||
|
||||
translationFragment.replaceChild(mergedChild, childNode);
|
||||
function sanitizeUsing(sourceElement, childNode) {
|
||||
if (childNode.nodeType === childNode.TEXT_NODE) {
|
||||
return childNode.cloneNode(false);
|
||||
}
|
||||
|
||||
// SourceElement might have been already modified by shiftNamedElement.
|
||||
// Let's clear it to make sure other code doesn't rely on random leftovers.
|
||||
sourceElement.textContent = "";
|
||||
if (childNode.hasAttribute("data-l10n-name")) {
|
||||
const childName = childNode.getAttribute("data-l10n-name");
|
||||
const sourceChild = sourceElement.querySelector(
|
||||
`[data-l10n-name="${childName}"]`
|
||||
);
|
||||
|
||||
return translationFragment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize and merge attributes.
|
||||
*
|
||||
* Only localizable attributes from the translated child element and only
|
||||
* functional attributes from the source child element are considered safe.
|
||||
*
|
||||
* @param {Element} translatedElement
|
||||
* @param {Element} sourceElement
|
||||
* @returns {Array<Attr>}
|
||||
* @private
|
||||
*/
|
||||
function sanitizeAttrsUsing(translatedElement, sourceElement) {
|
||||
const localizedAttrs = Array.from(translatedElement.attributes).filter(
|
||||
attr => isAttrNameLocalizable(attr.name, translatedElement)
|
||||
);
|
||||
|
||||
if (!sourceElement) {
|
||||
return localizedAttrs;
|
||||
if (!sourceChild) {
|
||||
console.warn(
|
||||
`An element named "${childName}" wasn't found in the source.`
|
||||
);
|
||||
} else if (sourceChild.localName !== childNode.localName) {
|
||||
console.warn(
|
||||
`An element named "${childName}" was found in the translation ` +
|
||||
`but its type ${childNode.localName} didn't match the element ` +
|
||||
`found in the source (${sourceChild.localName}).`
|
||||
);
|
||||
} else {
|
||||
// Remove it from sourceElement so that the translation cannot use
|
||||
// the same reference name again.
|
||||
sourceElement.removeChild(sourceChild);
|
||||
// We can't currently guarantee that a translation won't remove
|
||||
// sourceChild from the element completely, which could break the app if
|
||||
// it relies on an event handler attached to the sourceChild. Let's make
|
||||
// this limitation explicit for now by breaking the identitiy of the
|
||||
// sourceChild by cloning it. This will destroy all event handlers
|
||||
// attached to sourceChild via addEventListener and via on<name>
|
||||
// properties.
|
||||
const clone = sourceChild.cloneNode(false);
|
||||
return shallowPopulateUsing(childNode, clone);
|
||||
}
|
||||
}
|
||||
|
||||
const functionalAttrs = Array.from(sourceElement.attributes).filter(
|
||||
attr => !isAttrNameLocalizable(attr.name, sourceElement)
|
||||
if (isElementAllowed(childNode)) {
|
||||
// Start with an empty element of the same type to remove nested children
|
||||
// and non-localizable attributes defined by the translation.
|
||||
const clone = childNode.ownerDocument.createElement(childNode.localName);
|
||||
return shallowPopulateUsing(childNode, clone);
|
||||
}
|
||||
|
||||
console.warn(
|
||||
`An element of forbidden type "${childNode.localName}" was found in ` +
|
||||
"the translation. Only elements with data-l10n-name can be overlaid " +
|
||||
"onto source elements of the same data-l10n-name."
|
||||
);
|
||||
|
||||
return localizedAttrs.concat(functionalAttrs);
|
||||
// If all else fails, convert the element to its text content.
|
||||
return childNode.ownerDocument.createTextNode(childNode.textContent);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -225,8 +247,8 @@ function sanitizeAttrsUsing(translatedElement, sourceElement) {
|
|||
* @returns {boolean}
|
||||
* @private
|
||||
*/
|
||||
function isElementLocalizable(element) {
|
||||
const allowed = LOCALIZABLE_ELEMENTS[element.namespaceURI];
|
||||
function isElementAllowed(element) {
|
||||
const allowed = TEXT_LEVEL_ELEMENTS[element.namespaceURI];
|
||||
return allowed && allowed.includes(element.localName);
|
||||
}
|
||||
|
||||
|
@ -287,21 +309,17 @@ function isAttrNameLocalizable(name, element, explicitlyAllowed = null) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Remove and return the first child of the given type.
|
||||
* Helper to set textContent and localizable attributes on an element.
|
||||
*
|
||||
* @param {DOMFragment} element
|
||||
* @param {string} localName
|
||||
* @returns {Element | null}
|
||||
* @param {Element} fromElement
|
||||
* @param {Element} toElement
|
||||
* @returns {Element}
|
||||
* @private
|
||||
*/
|
||||
function shiftNamedElement(element, localName) {
|
||||
for (const child of element.children) {
|
||||
if (child.localName === localName) {
|
||||
element.removeChild(child);
|
||||
return child;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
function shallowPopulateUsing(fromElement, toElement) {
|
||||
toElement.textContent = fromElement.textContent;
|
||||
overlayAttributes(fromElement, toElement);
|
||||
return toElement;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -332,19 +350,18 @@ function sanitizeTranslationForNodeLocalize(l10nItem, translation) {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (translation.attrs) {
|
||||
if (translation.attributes) {
|
||||
const explicitlyAllowed = l10nItem.l10nAttrs === null ? null :
|
||||
l10nItem.l10nAttrs.split(",").map(i => i.trim());
|
||||
for (const [j, {name}] of translation.attrs.entries()) {
|
||||
for (const [j, {name}] of translation.attributes.entries()) {
|
||||
if (!isAttrNameLocalizable(name, l10nItem, explicitlyAllowed)) {
|
||||
translation.attrs.splice(j, 1);
|
||||
translation.attributes.splice(j, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
const L10NID_ATTR_NAME = "data-l10n-id";
|
||||
const L10NARGS_ATTR_NAME = "data-l10n-args";
|
||||
|
||||
|
@ -543,7 +560,9 @@ class DOMLocalization extends Localization {
|
|||
for (const mutation of mutations) {
|
||||
switch (mutation.type) {
|
||||
case "attributes":
|
||||
this.pendingElements.add(mutation.target);
|
||||
if (mutation.target.hasAttribute("data-l10n-id")) {
|
||||
this.pendingElements.add(mutation.target);
|
||||
}
|
||||
break;
|
||||
case "childList":
|
||||
for (const addedNode of mutation.addedNodes) {
|
||||
|
@ -634,7 +653,7 @@ class DOMLocalization extends Localization {
|
|||
for (let i = 0; i < overlayTranslations.length; i++) {
|
||||
if (overlayTranslations[i] !== undefined &&
|
||||
untranslatedElements[i] !== undefined) {
|
||||
overlayElement(untranslatedElements[i], overlayTranslations[i]);
|
||||
translateElement(untranslatedElements[i], overlayTranslations[i]);
|
||||
}
|
||||
}
|
||||
this.resumeObserving();
|
||||
|
@ -679,7 +698,7 @@ class DOMLocalization extends Localization {
|
|||
|
||||
for (let i = 0; i < elements.length; i++) {
|
||||
if (translations[i] !== undefined) {
|
||||
overlayElement(elements[i], translations[i]);
|
||||
translateElement(elements[i], translations[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
*/
|
||||
|
||||
|
||||
/* fluent@0.6.3 */
|
||||
/* fluent-dom@0.2.0 */
|
||||
|
||||
/* eslint no-console: ["error", { allow: ["warn", "error"] }] */
|
||||
/* global console */
|
||||
|
@ -327,15 +327,15 @@ function messageFromContext(ctx, errors, id, args) {
|
|||
|
||||
const formatted = {
|
||||
value: ctx.format(msg, args, errors),
|
||||
attrs: null,
|
||||
attributes: null,
|
||||
};
|
||||
|
||||
if (msg.attrs) {
|
||||
formatted.attrs = [];
|
||||
for (const name in msg.attrs) {
|
||||
const value = ctx.format(msg.attrs[name], args, errors);
|
||||
formatted.attributes = [];
|
||||
for (const [name, attr] of Object.entries(msg.attrs)) {
|
||||
const value = ctx.format(attr, args, errors);
|
||||
if (value !== null) {
|
||||
formatted.attrs.push({ name, value });
|
||||
formatted.attributes.push({name, value});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1608,6 +1608,10 @@ function Pattern(env, ptn) {
|
|||
dirty.add(ptn);
|
||||
const result = [];
|
||||
|
||||
// Wrap interpolations with Directional Isolate Formatting characters
|
||||
// only when the pattern has more than one element.
|
||||
const useIsolating = ctx._useIsolating && ptn.length > 1;
|
||||
|
||||
for (const elem of ptn) {
|
||||
if (typeof elem === "string") {
|
||||
result.push(elem);
|
||||
|
@ -1616,7 +1620,7 @@ function Pattern(env, ptn) {
|
|||
|
||||
const part = Type(env, elem).toString(ctx);
|
||||
|
||||
if (ctx._useIsolating) {
|
||||
if (useIsolating) {
|
||||
result.push(FSI);
|
||||
}
|
||||
|
||||
|
@ -1632,7 +1636,7 @@ function Pattern(env, ptn) {
|
|||
result.push(part);
|
||||
}
|
||||
|
||||
if (ctx._useIsolating) {
|
||||
if (useIsolating) {
|
||||
result.push(PDI);
|
||||
}
|
||||
}
|
||||
|
@ -1775,8 +1779,16 @@ class MessageContext {
|
|||
if (id.startsWith("-")) {
|
||||
// Identifiers starting with a dash (-) define terms. Terms are private
|
||||
// and cannot be retrieved from MessageContext.
|
||||
if (this._terms.has(id)) {
|
||||
errors.push(`Attempt to override an existing term: "${id}"`);
|
||||
continue;
|
||||
}
|
||||
this._terms.set(id, entries[id]);
|
||||
} else {
|
||||
if (this._messages.has(id)) {
|
||||
errors.push(`Attempt to override an existing message: "${id}"`);
|
||||
continue;
|
||||
}
|
||||
this._messages.set(id, entries[id]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,21 +1,140 @@
|
|||
diff -uNr ./dist/DOMLocalization.jsm /home/zbraniecki/projects/mozilla-unified/intl/l10n/DOMLocalization.jsm
|
||||
--- ./dist/DOMLocalization.jsm 2018-01-30 13:46:58.589811108 -0800
|
||||
+++ /home/zbraniecki/projects/mozilla-unified/intl/l10n/DOMLocalization.jsm 2018-01-30 13:46:13.613146435 -0800
|
||||
@@ -18,7 +18,8 @@
|
||||
--- ./dist/DOMLocalization.jsm 2018-04-13 08:25:21.143138950 -0700
|
||||
+++ /home/zbraniecki/projects/mozilla-unified/intl/l10n/DOMLocalization.jsm 2018-04-13 08:27:11.658083766 -0700
|
||||
@@ -18,10 +18,8 @@
|
||||
|
||||
/* fluent@0.6.0 */
|
||||
/* fluent-dom@0.2.0 */
|
||||
|
||||
-import Localization from '../../fluent-dom/src/localization.js';
|
||||
-
|
||||
-/* eslint no-console: ["error", {allow: ["warn"]}] */
|
||||
-/* global console */
|
||||
+const { Localization } =
|
||||
+ Components.utils.import("resource://gre/modules/Localization.jsm", {});
|
||||
+ ChromeUtils.import("resource://gre/modules/Localization.jsm", {});
|
||||
|
||||
// Match the opening angle bracket (<) in HTML tags, and HTML entities like
|
||||
// &, &, &.
|
||||
@@ -623,36 +624,5 @@
|
||||
@@ -96,6 +94,7 @@
|
||||
const templateElement = element.ownerDocument.createElementNS(
|
||||
"http://www.w3.org/1999/xhtml", "template"
|
||||
);
|
||||
+ // eslint-disable-next-line no-unsanitized/property
|
||||
templateElement.innerHTML = value;
|
||||
overlayChildNodes(templateElement.content, element);
|
||||
}
|
||||
@@ -323,6 +322,46 @@
|
||||
return toElement;
|
||||
}
|
||||
|
||||
+/**
|
||||
+ * Sanitizes a translation before passing them to Node.localize API.
|
||||
+ *
|
||||
+ * It returns `false` if the translation contains DOM Overlays and should
|
||||
+ * not go into Node.localize.
|
||||
+ *
|
||||
+ * Note: There's a third item of work that JS DOM Overlays do - removal
|
||||
+ * of attributes from the previous translation.
|
||||
+ * This is not trivial to implement for Node.localize scenario, so
|
||||
+ * at the moment it is not supported.
|
||||
+ *
|
||||
+ * @param {{
|
||||
+ * localName: string,
|
||||
+ * namespaceURI: string,
|
||||
+ * type: string || null
|
||||
+ * l10nId: string,
|
||||
+ * l10nArgs: Array<Object> || null,
|
||||
+ * l10nAttrs: string ||null,
|
||||
+ * }} l10nItems
|
||||
+ * @param {{value: string, attrs: Object}} translations
|
||||
+ * @returns boolean
|
||||
+ * @private
|
||||
+ */
|
||||
+function sanitizeTranslationForNodeLocalize(l10nItem, translation) {
|
||||
+ if (reOverlay.test(translation.value)) {
|
||||
+ return false;
|
||||
+ }
|
||||
+
|
||||
+ if (translation.attributes) {
|
||||
+ const explicitlyAllowed = l10nItem.l10nAttrs === null ? null :
|
||||
+ l10nItem.l10nAttrs.split(",").map(i => i.trim());
|
||||
+ for (const [j, {name}] of translation.attributes.entries()) {
|
||||
+ if (!isAttrNameLocalizable(name, l10nItem, explicitlyAllowed)) {
|
||||
+ translation.attributes.splice(j, 1);
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ return true;
|
||||
+}
|
||||
+
|
||||
const L10NID_ATTR_NAME = "data-l10n-id";
|
||||
const L10NARGS_ATTR_NAME = "data-l10n-args";
|
||||
|
||||
@@ -568,6 +607,59 @@
|
||||
* @returns {Promise}
|
||||
*/
|
||||
translateFragment(frag) {
|
||||
+ if (frag.localize) {
|
||||
+ // This is a temporary fast-path offered by Gecko to workaround performance
|
||||
+ // issues coming from Fluent and XBL+Stylo performing unnecesary
|
||||
+ // operations during startup.
|
||||
+ // For details see bug 1441037, bug 1442262, and bug 1363862.
|
||||
+
|
||||
+ // A sparse array which will store translations separated out from
|
||||
+ // all translations that is needed for DOM Overlay.
|
||||
+ const overlayTranslations = [];
|
||||
+
|
||||
+ const getTranslationsForItems = async l10nItems => {
|
||||
+ const keys = l10nItems.map(l10nItem => [l10nItem.l10nId, l10nItem.l10nArgs]);
|
||||
+ const translations = await this.formatMessages(keys);
|
||||
+
|
||||
+ // Here we want to separate out elements that require DOM Overlays.
|
||||
+ // Those elements will have to be translated using our JS
|
||||
+ // implementation, while everything else is going to use the fast-path.
|
||||
+ for (const [i, translation] of translations.entries()) {
|
||||
+ if (translation === undefined) {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ const hasOnlyText =
|
||||
+ sanitizeTranslationForNodeLocalize(l10nItems[i], translation);
|
||||
+ if (!hasOnlyText) {
|
||||
+ // Removing from translations to make Node.localize skip it.
|
||||
+ // We will translate it below using JS DOM Overlays.
|
||||
+ overlayTranslations[i] = translations[i];
|
||||
+ translations[i] = undefined;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ // We pause translation observing here because Node.localize
|
||||
+ // will translate the whole DOM next, using the `translations`.
|
||||
+ //
|
||||
+ // The observer will be resumed after DOM Overlays are localized
|
||||
+ // in the next microtask.
|
||||
+ this.pauseObserving();
|
||||
+ return translations;
|
||||
+ };
|
||||
+
|
||||
+ return frag.localize(getTranslationsForItems.bind(this))
|
||||
+ .then(untranslatedElements => {
|
||||
+ for (let i = 0; i < overlayTranslations.length; i++) {
|
||||
+ if (overlayTranslations[i] !== undefined &&
|
||||
+ untranslatedElements[i] !== undefined) {
|
||||
+ translateElement(untranslatedElements[i], overlayTranslations[i]);
|
||||
+ }
|
||||
+ }
|
||||
+ this.resumeObserving();
|
||||
+ })
|
||||
+ .catch(() => this.resumeObserving());
|
||||
+ }
|
||||
return this.translateElements(this.getTranslatables(frag));
|
||||
}
|
||||
|
||||
@@ -647,37 +739,5 @@
|
||||
}
|
||||
}
|
||||
|
||||
-/* global L10nRegistry, Services */
|
||||
-
|
||||
-/**
|
||||
- * The default localization strategy for Gecko. It comabines locales
|
||||
- * available in L10nRegistry, with locales requested by the user to
|
||||
|
@ -47,40 +166,84 @@ diff -uNr ./dist/DOMLocalization.jsm /home/zbraniecki/projects/mozilla-unified/i
|
|||
-}
|
||||
-
|
||||
-this.DOMLocalization = GeckoDOMLocalization;
|
||||
-this.EXPORTED_SYMBOLS = ["DOMLocalization"];
|
||||
+this.DOMLocalization = DOMLocalization;
|
||||
this.EXPORTED_SYMBOLS = ['DOMLocalization'];
|
||||
+var EXPORTED_SYMBOLS = ["DOMLocalization"];
|
||||
diff -uNr ./dist/l10n.js /home/zbraniecki/projects/mozilla-unified/intl/l10n/l10n.js
|
||||
--- ./dist/l10n.js 2018-01-30 13:46:58.749811101 -0800
|
||||
+++ /home/zbraniecki/projects/mozilla-unified/intl/l10n/l10n.js 2018-01-26 20:52:09.106650798 -0800
|
||||
@@ -1,7 +1,6 @@
|
||||
--- ./dist/l10n.js 2018-04-13 08:25:21.307139138 -0700
|
||||
+++ /home/zbraniecki/projects/mozilla-unified/intl/l10n/l10n.js 2018-04-13 08:27:25.230296529 -0700
|
||||
@@ -1,20 +1,26 @@
|
||||
-/* global Components, document, window */
|
||||
{
|
||||
const { DOMLocalization } =
|
||||
- Components.utils.import('resource://gre/modules/DOMLocalization.jsm');
|
||||
+ Components.utils.import("resource://gre/modules/DOMLocalization.jsm");
|
||||
- Components.utils.import("resource://gre/modules/DOMLocalization.jsm");
|
||||
+ ChromeUtils.import("resource://gre/modules/DOMLocalization.jsm", {});
|
||||
|
||||
/**
|
||||
* Polyfill for document.ready polyfill.
|
||||
diff -uNr ./dist/Localization.jsm /home/zbraniecki/projects/mozilla-unified/intl/l10n/Localization.jsm
|
||||
--- ./dist/Localization.jsm 2018-01-30 13:46:58.393144450 -0800
|
||||
+++ /home/zbraniecki/projects/mozilla-unified/intl/l10n/Localization.jsm 2018-01-30 13:46:04.593146834 -0800
|
||||
@@ -18,92 +18,16 @@
|
||||
* See: https://github.com/whatwg/html/issues/127 for details.
|
||||
*
|
||||
+ * XXX: The callback is a temporary workaround for bug 1193394. Once Promises in Gecko
|
||||
+ * start beeing a microtask and stop pushing translation post-layout, we can
|
||||
+ * remove it and start using the returned Promise again.
|
||||
+ *
|
||||
+ * @param {Function} callback - function to be called when the document is ready.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
- function documentReady() {
|
||||
+ function documentReady(callback) {
|
||||
if (document.contentType === "application/vnd.mozilla.xul+xml") {
|
||||
// XUL
|
||||
return new Promise(
|
||||
resolve => document.addEventListener(
|
||||
- "MozBeforeInitialXULLayout", resolve, { once: true }
|
||||
+ "MozBeforeInitialXULLayout", () => {
|
||||
+ resolve(callback());
|
||||
+ }, { once: true }
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -22,11 +28,13 @@
|
||||
// HTML
|
||||
const rs = document.readyState;
|
||||
if (rs === "interactive" || rs === "completed") {
|
||||
- return Promise.resolve();
|
||||
+ return Promise.resolve(callback);
|
||||
}
|
||||
return new Promise(
|
||||
resolve => document.addEventListener(
|
||||
- "readystatechange", resolve, { once: true }
|
||||
+ "readystatechange", () => {
|
||||
+ resolve(callback());
|
||||
+ }, { once: true }
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -50,11 +58,8 @@
|
||||
// trigger first context to be fetched eagerly
|
||||
document.l10n.ctxs.touchNext();
|
||||
|
||||
/* fluent@0.6.0 */
|
||||
- document.l10n.ready = documentReady().then(() => {
|
||||
+ document.l10n.ready = documentReady(() => {
|
||||
document.l10n.registerObservers();
|
||||
- window.addEventListener("unload", () => {
|
||||
- document.l10n.unregisterObservers();
|
||||
- });
|
||||
document.l10n.connectRoot(document.documentElement);
|
||||
return document.l10n.translateRoots();
|
||||
});
|
||||
diff -uNr ./dist/Localization.jsm /home/zbraniecki/projects/mozilla-unified/intl/l10n/Localization.jsm
|
||||
--- ./dist/Localization.jsm 2018-04-13 08:25:20.946138732 -0700
|
||||
+++ /home/zbraniecki/projects/mozilla-unified/intl/l10n/Localization.jsm 2018-04-13 08:27:16.396155987 -0700
|
||||
@@ -18,70 +18,13 @@
|
||||
|
||||
/* fluent-dom@0.2.0 */
|
||||
|
||||
-/* eslint no-magic-numbers: [0] */
|
||||
-
|
||||
-/* global Intl */
|
||||
-
|
||||
-/**
|
||||
- * The `FluentType` class is the base of Fluent's type system.
|
||||
- *
|
||||
- * Fluent types wrap JavaScript values and store additional configuration for
|
||||
- * them, which can then be used in the `toString` method together with a proper
|
||||
- * `Intl` formatter.
|
||||
- */
|
||||
-
|
||||
-/**
|
||||
- * @overview
|
||||
- *
|
||||
- * The FTL resolver ships with a number of functions built-in.
|
||||
|
@ -142,30 +305,14 @@ diff -uNr ./dist/Localization.jsm /home/zbraniecki/projects/mozilla-unified/intl
|
|||
- * Set of patterns already encountered during this resolution.
|
||||
- * This is used to prevent cyclic resolutions.
|
||||
- */
|
||||
+const Cu = Components.utils;
|
||||
+const Cc = Components.classes;
|
||||
+const Ci = Components.interfaces;
|
||||
|
||||
-/**
|
||||
- * Message contexts are single-language stores of translations. They are
|
||||
- * responsible for parsing translation resources in the Fluent syntax and can
|
||||
- * format translation units (entities) to strings.
|
||||
- *
|
||||
- * Always use `MessageContext.format` to retrieve translation units from
|
||||
- * a context. Translations can contain references to other entities or
|
||||
- * external arguments, conditional logic in form of select expressions, traits
|
||||
- * which describe their grammatical features, and can use Fluent builtins which
|
||||
- * make use of the `Intl` formatters to format numbers, dates, lists and more
|
||||
- * into the context's language. See the documentation of the Fluent syntax for
|
||||
- * more information.
|
||||
- */
|
||||
+const { L10nRegistry } = Cu.import("resource://gre/modules/L10nRegistry.jsm", {});
|
||||
+const LocaleService = Cc["@mozilla.org/intl/localeservice;1"].getService(Ci.mozILocaleService);
|
||||
+const ObserverService = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
|
||||
+const { XPCOMUtils } = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm", {});
|
||||
+const { L10nRegistry } = ChromeUtils.import("resource://gre/modules/L10nRegistry.jsm", {});
|
||||
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm", {});
|
||||
+const { AppConstants } = ChromeUtils.import("resource://gre/modules/AppConstants.jsm", {});
|
||||
|
||||
/*
|
||||
* CachedIterable caches the elements yielded by an iterable.
|
||||
@@ -170,87 +94,6 @@
|
||||
@@ -148,58 +91,19 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -212,75 +359,30 @@ diff -uNr ./dist/Localization.jsm /home/zbraniecki/projects/mozilla-unified/intl
|
|||
- */
|
||||
-
|
||||
-/*
|
||||
- * Synchronously map an identifier or an array of identifiers to the best
|
||||
- * `MessageContext` instance(s).
|
||||
- *
|
||||
- * @param {Iterable} iterable
|
||||
- * @param {string|Array<string>} ids
|
||||
- * @returns {MessageContext|Array<MessageContext>}
|
||||
- */
|
||||
-
|
||||
-
|
||||
-/*
|
||||
- * Asynchronously map an identifier or an array of identifiers to the best
|
||||
- * `MessageContext` instance(s).
|
||||
- *
|
||||
- * @param {AsyncIterable} iterable
|
||||
- * @param {string|Array<string>} ids
|
||||
- * @returns {Promise<MessageContext|Array<MessageContext>>}
|
||||
- */
|
||||
-
|
||||
-/**
|
||||
- * Template literal tag for dedenting FTL code.
|
||||
- *
|
||||
- * Strip the common indent of non-blank lines. Remove blank lines.
|
||||
- *
|
||||
- * @param {Array<string>} strings
|
||||
- */
|
||||
-
|
||||
-/*
|
||||
- * @module fluent
|
||||
- * @overview
|
||||
- *
|
||||
- * `fluent` is a JavaScript implementation of Project Fluent, a localization
|
||||
- * framework designed to unleash the expressive power of the natural language.
|
||||
- *
|
||||
- */
|
||||
-
|
||||
-/* eslint no-console: ["error", { allow: ["warn", "error"] }] */
|
||||
-/* global console */
|
||||
-
|
||||
/**
|
||||
* Specialized version of an Error used to indicate errors that are result
|
||||
* of a problem during the localization process.
|
||||
@@ -269,6 +112,26 @@
|
||||
}
|
||||
}
|
||||
|
||||
+ /**
|
||||
+/**
|
||||
+ * The default localization strategy for Gecko. It comabines locales
|
||||
+ * available in L10nRegistry, with locales requested by the user to
|
||||
+ * generate the iterator over MessageContexts.
|
||||
+ *
|
||||
*
|
||||
+ * In the future, we may want to allow certain modules to override this
|
||||
+ * with a different negotitation strategy to allow for the module to
|
||||
+ * be localized into a different language - for example DevTools.
|
||||
+ */
|
||||
*/
|
||||
-
|
||||
-/* eslint no-console: ["error", { allow: ["warn", "error"] }] */
|
||||
+function defaultGenerateMessages(resourceIds) {
|
||||
+ const availableLocales = L10nRegistry.getAvailableLocales();
|
||||
+
|
||||
+ const requestedLocales = LocaleService.getRequestedLocales();
|
||||
+ const defaultLocale = LocaleService.defaultLocale;
|
||||
+ const locales = LocaleService.negotiateLanguages(
|
||||
+ requestedLocales, availableLocales, defaultLocale,
|
||||
+ );
|
||||
+ return L10nRegistry.generateContexts(locales, resourceIds);
|
||||
+ const appLocales = Services.locale.getAppLocalesAsLangTags();
|
||||
+ return L10nRegistry.generateContexts(appLocales, resourceIds);
|
||||
+}
|
||||
+
|
||||
|
||||
/**
|
||||
* The `Localization` class is a central high-level API for vanilla
|
||||
* JavaScript use of Fluent.
|
||||
@@ -283,7 +146,7 @@
|
||||
@@ -215,7 +119,7 @@
|
||||
*
|
||||
* @returns {Localization}
|
||||
*/
|
||||
|
@ -289,35 +391,35 @@ diff -uNr ./dist/Localization.jsm /home/zbraniecki/projects/mozilla-unified/intl
|
|||
this.resourceIds = resourceIds;
|
||||
this.generateMessages = generateMessages;
|
||||
this.ctxs = new CachedIterable(this.generateMessages(this.resourceIds));
|
||||
@@ -303,7 +166,7 @@
|
||||
*/
|
||||
@@ -236,7 +140,7 @@
|
||||
async formatWithFallback(keys, method) {
|
||||
const translations = [];
|
||||
|
||||
- for (let ctx of this.ctxs) {
|
||||
+ for await (let ctx of this.ctxs) {
|
||||
// This can operate on synchronous and asynchronous
|
||||
// contexts coming from the iterator.
|
||||
if (typeof ctx.then === 'function') {
|
||||
@@ -394,8 +257,38 @@
|
||||
if (typeof ctx.then === "function") {
|
||||
@@ -248,7 +152,7 @@
|
||||
break;
|
||||
}
|
||||
|
||||
- if (typeof console !== "undefined") {
|
||||
+ if (AppConstants.NIGHTLY_BUILD) {
|
||||
const locale = ctx.locales[0];
|
||||
const ids = Array.from(missingIds).join(", ");
|
||||
console.warn(`Missing translations in ${locale}: ${ids}`);
|
||||
@@ -335,8 +239,28 @@
|
||||
return val;
|
||||
}
|
||||
|
||||
- handleEvent() {
|
||||
- this.onLanguageChange();
|
||||
+ /**
|
||||
+ * Register observers on events that will trigger cache invalidation
|
||||
+ * Register weak observers on events that will trigger cache invalidation
|
||||
+ */
|
||||
+ registerObservers() {
|
||||
+ ObserverService.addObserver(this, 'l10n:available-locales-changed', false);
|
||||
+ ObserverService.addObserver(this, 'intl:requested-locales-changed', false);
|
||||
+ }
|
||||
+
|
||||
+ /**
|
||||
+ * Unregister observers on events that will trigger cache invalidation
|
||||
+ */
|
||||
+ unregisterObservers() {
|
||||
+ ObserverService.removeObserver(this, 'l10n:available-locales-changed');
|
||||
+ ObserverService.removeObserver(this, 'intl:requested-locales-changed');
|
||||
+ Services.obs.addObserver(this, "intl:app-locales-changed", true);
|
||||
+ }
|
||||
+
|
||||
+ /**
|
||||
|
@ -329,8 +431,7 @@ diff -uNr ./dist/Localization.jsm /home/zbraniecki/projects/mozilla-unified/intl
|
|||
+ */
|
||||
+ observe(subject, topic, data) {
|
||||
+ switch (topic) {
|
||||
+ case 'l10n:available-locales-changed':
|
||||
+ case 'intl:requested-locales-changed':
|
||||
+ case "intl:app-locales-changed":
|
||||
+ this.onLanguageChange();
|
||||
+ break;
|
||||
+ default:
|
||||
|
@ -339,18 +440,27 @@ diff -uNr ./dist/Localization.jsm /home/zbraniecki/projects/mozilla-unified/intl
|
|||
}
|
||||
|
||||
/**
|
||||
@@ -538,7 +431,8 @@
|
||||
hasErrors = true;
|
||||
}
|
||||
@@ -348,6 +272,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
- if (messageErrors.length && typeof console !== 'undefined') {
|
||||
+ if (messageErrors.length) {
|
||||
+ const { console } = Cu.import("resource://gre/modules/Console.jsm", {});
|
||||
messageErrors.forEach(error => console.warn(error));
|
||||
}
|
||||
});
|
||||
@@ -546,45 +440,5 @@
|
||||
return hasErrors;
|
||||
+Localization.prototype.QueryInterface = XPCOMUtils.generateQI([
|
||||
+ Ci.nsISupportsWeakReference
|
||||
+]);
|
||||
+
|
||||
/**
|
||||
* Format the value of a message into a string.
|
||||
*
|
||||
@@ -368,6 +296,7 @@
|
||||
*/
|
||||
function valueFromContext(ctx, errors, id, args) {
|
||||
const msg = ctx.getMessage(id);
|
||||
+
|
||||
return ctx.format(msg, args, errors);
|
||||
}
|
||||
|
||||
@@ -467,44 +396,5 @@
|
||||
return missingIds;
|
||||
}
|
||||
|
||||
-/* global Components */
|
||||
|
@ -361,12 +471,11 @@ diff -uNr ./dist/Localization.jsm /home/zbraniecki/projects/mozilla-unified/intl
|
|||
-const Ci = Components.interfaces;
|
||||
-
|
||||
-const { L10nRegistry } =
|
||||
- Cu.import('resource://gre/modules/L10nRegistry.jsm', {});
|
||||
- Cu.import("resource://gre/modules/L10nRegistry.jsm", {});
|
||||
-const ObserverService =
|
||||
- Cc['@mozilla.org/observer-service;1'].getService(Ci.nsIObserverService);
|
||||
- Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
|
||||
-const { Services } =
|
||||
- Cu.import('resource://gre/modules/Services.jsm', {});
|
||||
-
|
||||
- Cu.import("resource://gre/modules/Services.jsm", {});
|
||||
-
|
||||
-/**
|
||||
- * The default localization strategy for Gecko. It comabines locales
|
||||
|
@ -394,12 +503,22 @@ diff -uNr ./dist/Localization.jsm /home/zbraniecki/projects/mozilla-unified/intl
|
|||
-}
|
||||
-
|
||||
-this.Localization = GeckoLocalization;
|
||||
-this.EXPORTED_SYMBOLS = ["Localization"];
|
||||
+this.Localization = Localization;
|
||||
this.EXPORTED_SYMBOLS = ['Localization'];
|
||||
+var EXPORTED_SYMBOLS = ["Localization"];
|
||||
diff -uNr ./dist/MessageContext.jsm /home/zbraniecki/projects/mozilla-unified/intl/l10n/MessageContext.jsm
|
||||
--- ./dist/MessageContext.jsm 2018-01-30 13:46:58.119811129 -0800
|
||||
+++ /home/zbraniecki/projects/mozilla-unified/intl/l10n/MessageContext.jsm 2018-01-30 13:53:23.036460739 -0800
|
||||
@@ -1838,90 +1838,5 @@
|
||||
--- ./dist/MessageContext.jsm 2018-04-13 08:25:20.698138486 -0700
|
||||
+++ /home/zbraniecki/projects/mozilla-unified/intl/l10n/MessageContext.jsm 2018-04-13 08:27:20.944227388 -0700
|
||||
@@ -16,7 +16,7 @@
|
||||
*/
|
||||
|
||||
|
||||
-/* fluent-dom@0.2.0 */
|
||||
+/* fluent@0.6.3 */
|
||||
|
||||
/* eslint no-magic-numbers: [0] */
|
||||
|
||||
@@ -1858,63 +1858,5 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -453,33 +572,6 @@ diff -uNr ./dist/MessageContext.jsm /home/zbraniecki/projects/mozilla-unified/in
|
|||
- */
|
||||
-
|
||||
-/*
|
||||
- * Synchronously map an identifier or an array of identifiers to the best
|
||||
- * `MessageContext` instance(s).
|
||||
- *
|
||||
- * @param {Iterable} iterable
|
||||
- * @param {string|Array<string>} ids
|
||||
- * @returns {MessageContext|Array<MessageContext>}
|
||||
- */
|
||||
-
|
||||
-
|
||||
-/*
|
||||
- * Asynchronously map an identifier or an array of identifiers to the best
|
||||
- * `MessageContext` instance(s).
|
||||
- *
|
||||
- * @param {AsyncIterable} iterable
|
||||
- * @param {string|Array<string>} ids
|
||||
- * @returns {Promise<MessageContext|Array<MessageContext>>}
|
||||
- */
|
||||
-
|
||||
-/**
|
||||
- * Template literal tag for dedenting FTL code.
|
||||
- *
|
||||
- * Strip the common indent of non-blank lines. Remove blank lines.
|
||||
- *
|
||||
- * @param {Array<string>} strings
|
||||
- */
|
||||
-
|
||||
-/*
|
||||
- * @module fluent
|
||||
- * @overview
|
||||
- *
|
||||
|
@ -489,4 +581,5 @@ diff -uNr ./dist/MessageContext.jsm /home/zbraniecki/projects/mozilla-unified/in
|
|||
- */
|
||||
-
|
||||
this.MessageContext = MessageContext;
|
||||
this.EXPORTED_SYMBOLS = ['MessageContext'];
|
||||
-this.EXPORTED_SYMBOLS = ["MessageContext"];
|
||||
+var EXPORTED_SYMBOLS = ["MessageContext"];
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
async function* mockGenerateMessages(locales, resourceIds) {
|
||||
const mc = new MessageContext(locales);
|
||||
mc.addMessages("title = <strong>Hello</strong> World");
|
||||
mc.addMessages("title2 = This is <a>a link</a>!");
|
||||
mc.addMessages(`title2 = This is <a data-l10n-name="link">a link</a>!`);
|
||||
yield mc;
|
||||
}
|
||||
|
||||
|
@ -50,7 +50,7 @@
|
|||
<body>
|
||||
<p data-l10n-id="title" />
|
||||
<p data-l10n-id="title2">
|
||||
<a href="http://www.mozilla.org"></a>
|
||||
<a href="http://www.mozilla.org" data-l10n-name="link"></a>
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
async function* mockGenerateMessages(locales, resourceIds) {
|
||||
const mc = new MessageContext(locales);
|
||||
mc.addMessages("title = Visit <a>Mozilla</a> or <a>Firefox</a> website!");
|
||||
mc.addMessages(`title = Visit <a data-l10n-name="mozilla-link">Mozilla</a> or <a data-l10n-name="firefox-link">Firefox</a> website!`);
|
||||
yield mc;
|
||||
}
|
||||
|
||||
|
@ -46,9 +46,9 @@
|
|||
</head>
|
||||
<body>
|
||||
<p data-l10n-id="title">
|
||||
<a href="http://www.mozilla.org"></a>
|
||||
<a href="http://www.firefox.com"></a>
|
||||
<a href="http://www.w3.org"></a>
|
||||
<a href="http://www.mozilla.org" data-l10n-name="mozilla-link"></a>
|
||||
<a href="http://www.firefox.com" data-l10n-name="firefox-link"></a>
|
||||
<a href="http://www.w3.org" data-l10n-name="w3-link"></a>
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
async function* mockGenerateMessages(locales, resourceIds) {
|
||||
const mc = new MessageContext(locales);
|
||||
mc.addMessages("title = Visit <a>Mozilla</a> or <a>Firefox</a> website!");
|
||||
mc.addMessages(`title = Visit <a data-l10n-name="mozilla-link">Mozilla</a> or <a data-l10n-name="firefox-link">Firefox</a> website!`);
|
||||
yield mc;
|
||||
}
|
||||
|
||||
|
@ -44,8 +44,8 @@
|
|||
</head>
|
||||
<body>
|
||||
<p data-l10n-id="title">
|
||||
<a href="http://www.mozilla.org"></a>
|
||||
<a href="http://www.firefox.com"></a>
|
||||
<a href="http://www.mozilla.org" data-l10n-name="mozilla-link"></a>
|
||||
<a href="http://www.firefox.com" data-l10n-name="firefox-link"></a>
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
mc.addMessages(`
|
||||
key1 = Translation For Key 1
|
||||
|
||||
key2 = Visit <a>this link<a/>.
|
||||
key2 = Visit <a data-l10n-name="link">this link<a/>.
|
||||
`);
|
||||
yield mc;
|
||||
}
|
||||
|
@ -53,11 +53,11 @@ key2 = Visit <a>this link<a/>.
|
|||
<h2 id="elem2" data-l10n-id="key1"></h2>
|
||||
|
||||
<p id="elem3" data-l10n-id="key2">
|
||||
<a href="http://www.mozilla.org"></a>
|
||||
<a href="http://www.mozilla.org" data-l10n-name="link"></a>
|
||||
</p>
|
||||
|
||||
<p id="elem4" data-l10n-id="key2">
|
||||
<a href="http://www.firefox.com"></a>
|
||||
<a href="http://www.firefox.com" data-l10n-name="link"></a>
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
|
|
Загрузка…
Ссылка в новой задаче