Skip to content

Commit f34a42c

Browse files
authored
Overlay child elements by name (#168)
Background The overlay mechanic allows some safe HTML to be present in translations. Previously only text-level elements were allowed in translations. Additionally, if a element of the same type was present in the source HTML, its functional attributes were copied to the translated element. This approach allowed translations to use the <a> element and have its href attribute copied from the source. It was limited, however, to text-level elements (<img> or <button> were not supported) and to overlaying child elements in the order defined by the source. Use-Cases This PR refactors a new mechanism for sanitizing the HTML markup found in translations. It introduces two separate use-cases: - Text-level elements. Localizers may want to use some HTML markup to make the translation correct according to the rules of grammar and spelling. For instance, <em> may be used for words borrowed from foreign languages, <sup> may be used for ordinals or abbreviations, etc. - Functional elements. Developers may want to pass elements as arguments to the translation and create rich language-agnostic UIs. For instance, they may define an <img> element which should be placed inline inside of the translation. Each language will need to decide where exactly the <img> should go. Text-Level Elements For the first use-case, fluent-dom will always allow a safe set of text-level elements such as <em> and <strong>. Their contents will always be converted to pure text (no nesting is allowed) and their attributes will be sanitized using a well-defined list of safe attributes. hello = Hello, <em title="It's a wonderful world.">world</em>! Functional Elements For the latter use-case, developers may put child elements inside of the element which is the target of the translation. These child elements must be annotated with the data-l10n-name attribute. fluent-dom will look for corresponding child elements defined by the translation and clone them into the final translated result. The cloning preserves functional attributes defined in the source (href, class, src) but destroys any event listeners attached to the child element. (We may be able to relax this limitation in the future.) Safe attributes found on the matching child element from the translation will be copied to the resulting child. <p data-l10n-id="hello-icon"> <img data-l10n-name="world" src="world.png"> </p> hello-icon = Hello, <img data-l10n-name="world">!
1 parent 18e25d5 commit f34a42c

9 files changed

+738
-467
lines changed

fluent-dom/src/dom_localization.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import overlayElement from "./overlay";
1+
import translateElement from "./overlay";
22
import Localization from "./localization";
33

44
const L10NID_ATTR_NAME = "data-l10n-id";
@@ -165,7 +165,7 @@ export default class DOMLocalization extends Localization {
165165
translateRoots() {
166166
const roots = Array.from(this.roots);
167167
return Promise.all(
168-
roots.map(root => this.translateElements(this.getTranslatables(root)))
168+
roots.map(root => this.translateFragment(root))
169169
);
170170
}
171171

@@ -232,7 +232,6 @@ export default class DOMLocalization extends Localization {
232232
}
233233
}
234234

235-
236235
/**
237236
* Translate a DOM element or fragment asynchronously using this
238237
* `DOMLocalization` object.
@@ -285,7 +284,7 @@ export default class DOMLocalization extends Localization {
285284

286285
for (let i = 0; i < elements.length; i++) {
287286
if (translations[i] !== undefined) {
288-
overlayElement(elements[i], translations[i]);
287+
translateElement(elements[i], translations[i]);
289288
}
290289
}
291290

fluent-dom/src/localization.js

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -200,18 +200,15 @@ function messageFromContext(ctx, errors, id, args) {
200200

201201
const formatted = {
202202
value: ctx.format(msg, args, errors),
203-
attrs: null,
203+
attributes: null,
204204
};
205205

206206
if (msg.attrs) {
207-
formatted.attrs = [];
208-
for (const attrName in msg.attrs) {
209-
const formattedAttr = ctx.format(msg.attrs[attrName], args, errors);
210-
if (formattedAttr !== null) {
211-
formatted.attrs.push([
212-
attrName,
213-
formattedAttr
214-
]);
207+
formatted.attributes = [];
208+
for (const [name, attr] of Object.entries(msg.attrs)) {
209+
const value = ctx.format(attr, args, errors);
210+
if (value !== null) {
211+
formatted.attributes.push({name, value});
215212
}
216213
}
217214
}

0 commit comments

Comments
 (0)