Skip to content

Commit da1f4c3

Browse files
committed
Update JSDoc
1 parent 95040df commit da1f4c3

9 files changed

Lines changed: 317 additions & 66 deletions

File tree

docs/Localizer.js.html

Lines changed: 136 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
<![endif]-->
1414
<link type="text/css" rel="stylesheet" href="styles/prettify.css">
1515
<link type="text/css" rel="stylesheet" href="styles/jsdoc.css">
16+
<script src="scripts/nav.js" defer></script>
1617
<meta name="viewport" content="width=device-width, initial-scale=1.0">
1718
</head>
1819
<body>
@@ -54,6 +55,10 @@ <h1 class="page-title">Localizer.js</h1>
5455
const I18N_DATASET = "i18n";
5556
const I18N_DATASET_INT = I18N_DATASET.length;
5657

58+
const I18N_DATASET_KEEP_CHILDREN = "optI18nKeepChildren";
59+
const UNIQUE_REPLACEMENT_SPLIT = "$i18nSplit$";
60+
const UNIQUE_REPLACEMENT_ID = "i18nKeepChildren#";
61+
5762
/**
5863
* Splits the _MSG__*__ format and returns the actual tag.
5964
*
@@ -106,7 +111,7 @@ <h1 class="page-title">Localizer.js</h1>
106111
*
107112
* @private
108113
* @param {string} messageName
109-
* @param {string[]} [substitutions]
114+
* @param {string[]} substitutions
110115
* @returns {string} translated string
111116
* @throws {Error} if no translation could be found
112117
* @see {@link https://developer.mozilla.org/docs/Mozilla/Add-ons/WebExtensions/API/i18n/getMessage}
@@ -121,6 +126,88 @@ <h1 class="page-title">Localizer.js</h1>
121126
return translatedMessage;
122127
}
123128

129+
/**
130+
* Translates only the text nodes of the element, and adjusts the psition of the
131+
* other HTML elements.
132+
*
133+
* It does this wout inserting HTML just by moving DOM elements and inserting text,
134+
* so it works around potential security problems of innerHtml etc.
135+
*
136+
* @private
137+
* @param {HTMLElement} parent the element to tramslate
138+
* @param {string} translatedMessage the already translated and prepared string
139+
* @param {Object} subsContainer
140+
* @param {string[]} subsContainer.substitutions IDs correspond to number of htmlOnlyChilds
141+
* @param {Node[]} subsContainer.textOnlyChilds
142+
* @param {HTMLElement[]} subsContainer.htmlOnlyChilds
143+
* @param {Node[]} [subsContainer.allChilds] not actually used currently
144+
* @returns {void}
145+
*/
146+
function innerTranslateTextNodes(parent, translatedMessage, subsContainer) {
147+
const splitTranslatedMessage = translatedMessage.split(UNIQUE_REPLACEMENT_SPLIT);
148+
149+
console.log("Replacing text nodes for", parent, ", message:", translatedMessage, "detected elements:", subsContainer);
150+
151+
// sanity check whether all translations were used
152+
// We also trigger for =, because we assume we have at least one text node, which
153+
// is also returned in splitTranslatedMessage
154+
if (splitTranslatedMessage.length &lt;= subsContainer.substitutions.length) {
155+
console.warn(
156+
"You used only", splitTranslatedMessage.length, "message blocks, altghough you could use",
157+
subsContainer.substitutions.length, "substitutions. Possibly you did not include all substitutions in your translation?",
158+
"Check for typos in the placeholder name e.g.",
159+
{
160+
translationObject: splitTranslatedMessage,
161+
intendedSubstitutions: subsContainer.substitutions
162+
}
163+
);
164+
}
165+
166+
// create iterator out of arrays
167+
const textOnlyIterator = subsContainer.textOnlyChilds[Symbol.iterator]();
168+
169+
// for first element, fake the first element as the next element
170+
let previousElement = { nextSibling: parent.fistChild };
171+
for (const message of splitTranslatedMessage) {
172+
// if it is placeholder, replace it with HTML element
173+
if (message.startsWith(UNIQUE_REPLACEMENT_ID)) {
174+
const childId = message.slice(UNIQUE_REPLACEMENT_ID.length);
175+
const child = subsContainer.htmlOnlyChilds[childId - 1];
176+
177+
// move child element in there, *after* the last element = before the next one
178+
const newElement = parent.insertBefore(child, previousElement.nextSibling);
179+
180+
// save last element
181+
previousElement = newElement;
182+
} else {
183+
// otherwise we have a text message, which we need to put into a
184+
// text node
185+
186+
const nextText = textOnlyIterator.next();
187+
const nextTextElement = nextText.value;
188+
189+
// if we have no more text elements
190+
if (nextText.done) {
191+
console.warn("Translation contained more text then HTML template. We now add a note. Triggered for translation: ", message);
192+
// just create &amp; add a new one
193+
const newTextNode = document.createTextNode(message);
194+
195+
// move child element in there, *after* the last element = before the next one
196+
parent.insertBefore(newTextNode, previousElement.nextSibling);
197+
198+
// save last element
199+
previousElement = nextTextElement;
200+
} else {
201+
// replace the next text element
202+
nextTextElement.textContent = message;
203+
204+
// save last element
205+
previousElement = nextTextElement;
206+
}
207+
}
208+
}
209+
}
210+
124211
/**
125212
* Replaces attribute or inner text of element with string.
126213
*
@@ -146,6 +233,40 @@ <h1 class="page-title">Localizer.js</h1>
146233
}
147234
}
148235

236+
/**
237+
* Returns the HTML children..
238+
*
239+
* @private
240+
* @param {HTMLElement} elem
241+
* @returns {void}
242+
*/
243+
function getSubitems(elem) {
244+
// only keep subitems if enabled
245+
if (!(I18N_DATASET_KEEP_CHILDREN in elem.dataset)) {
246+
return {};
247+
}
248+
249+
// always creates arrays to freeze elements, so later DOM changes do not
250+
// affect it
251+
252+
// get all children elements
253+
const childs = Array.from(elem.childNodes);
254+
255+
// filter out text childs
256+
const htmlOnlyChilds = Array.from(elem.children);
257+
const textOnlyChilds = childs.filter((node) => node.nodeType === Node.TEXT_NODE);
258+
259+
// create list of substitutions, i.e. $1, $2, §3 etc.
260+
const substitutions = htmlOnlyChilds.map((elem, num) => `${UNIQUE_REPLACEMENT_SPLIT}${UNIQUE_REPLACEMENT_ID}${num + 1}${UNIQUE_REPLACEMENT_SPLIT}`);
261+
262+
return {
263+
substitutions: substitutions,
264+
allChilds: childs,
265+
textOnlyChilds: textOnlyChilds,
266+
htmlOnlyChilds: htmlOnlyChilds
267+
};
268+
}
269+
149270
/**
150271
* Localises the strings to localize in the HTMLElement.
151272
*
@@ -155,11 +276,20 @@ <h1 class="page-title">Localizer.js</h1>
155276
* @returns {void}
156277
*/
157278
function replaceI18n(elem, tag) {
279+
const subsContainer = getSubitems(elem);
280+
158281
// localize main content
159282
if (tag !== "") {
160283
try {
161-
const translatedMessage = getTranslatedMessage(getMessageTag(tag));
162-
replaceWith(elem, null, translatedMessage);
284+
const translatedMessage = getTranslatedMessage(getMessageTag(tag), subsContainer.substitutions);
285+
286+
// if we have substrings to replace
287+
if (subsContainer.substitutions) {
288+
innerTranslateTextNodes(elem, translatedMessage, subsContainer);
289+
} else {
290+
// otherwise do "usual" full replacement
291+
replaceWith(elem, null, translatedMessage);
292+
}
163293
} catch (error) {
164294
// log error but continue translating as it was likely just one problem in one translation
165295
console.error(error.message, "for element", elem);
@@ -219,12 +349,14 @@ <h1 class="page-title">Localizer.js</h1>
219349
<br class="clear">
220350

221351
<footer>
222-
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Sun Jan 20 2019 21:55:59 GMT+0100 (Mitteleuropäische Normalzeit) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
352+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.6.2</a> on Sun Jun 30 2019 23:07:02 GMT+0200 (GMT+02:00) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
223353
</footer>
224354

225355
<script>prettyPrint();</script>
356+
<script src="scripts/polyfill.js"></script>
226357
<script src="scripts/linenumber.js"></script>
227358

228359

360+
229361
</body>
230362
</html>

docs/index.html

Lines changed: 57 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
<![endif]-->
1414
<link type="text/css" rel="stylesheet" href="styles/prettify.css">
1515
<link type="text/css" rel="stylesheet" href="styles/jsdoc.css">
16+
<script src="scripts/nav.js" defer></script>
1617
<meta name="viewport" content="width=device-width, initial-scale=1.0">
1718
</head>
1819
<body>
@@ -39,6 +40,7 @@ <h2><a href="index.html">Home</a></h2><h2><a href="https://github.com/TinyWebEx/
3940

4041

4142

43+
4244
<section class="readme">
4345
<article><h1 id="tinywebex-localizer">TinyWebEx Localizer</h1>
4446
<p>A tiny library that translates your HTML file with the WebExtension translation system. It's easy to use, and you only need to modify the HTML, everything else is done by this library.</p>
@@ -49,10 +51,11 @@ <h2 id="features">Features</h2>
4951
<li>allows (English) HTML <a href="#fallbacks">fallbacks</a> in the HTML file, but does not require them</li>
5052
<li>does not translate whole document via string replacement, but relies on a proper HTML syntax</li>
5153
<li>properly sets the <a href="https://developer.mozilla.org/docs/Web/HTML/Global_attributes/lang">&quot;lang&quot; attribute</a> <a href="https://developer.mozilla.org/docs/Web/HTML/Global_attributes#attr-lang">of your HTML tag</a>, so you can e.g. use the CSS <a href="https://developer.mozilla.org/docs/Web/CSS/:lang">lang selector</a>.</li>
54+
<li>supports <a href="#keeping-child-elements-in-HTML">including sub-HTML elements in translations</a>, so you do not need to use HTML replacements, but let translators dynamically place HTML elements such as links</li>
5255
</ul>
53-
<h2 id="how-to-use-">How to use?</h2>
54-
<p>To enable it, you just import the <a href="Localizer.js"><code>Localizer.js</code></a>. Everything is done automatically, you do not need to call any JavaScript function or initialize something.</p>
55-
<h3 id="html-setup-for-internationalisation-i18n-">HTML setup for internationalisation (i18n)</h3>
56+
<h2 id="how-to-use%3F">How to use?</h2>
57+
<p>To enable it, you just import the <a href="Localizer.js"><code>Localizer.js</code></a> module. Everything is done automatically, you do not need to call any JavaScript function or initialize something.</p>
58+
<h3 id="html-setup-for-internationalisation-(i18n)">HTML setup for internationalisation (i18n)</h3>
5659
<p>The real thing you need to do is to adjust your HTML. Actually, here is how it works:</p>
5760
<ul>
5861
<li>First, it always uses <a href="https://developer.mozilla.org/docs/Learn/HTML/Howto/Use_data_attributes">data attributes</a>. To avoid clashes there, it also always uses a prefix called <code>i18n</code>.</li>
@@ -63,7 +66,7 @@ <h3 id="html-setup-for-internationalisation-i18n-">HTML setup for internationali
6366
<p><strong>Note:</strong> Remember, that even for translating only attributes, you need to add (an empty) attribute <code>data-i18n</code> to the HTML node. Otherwise it won't be detected and translated.</p>
6467
<h3 id="fallbacks">Fallbacks</h3>
6568
<p>As translation strings are not specified in the user-facing content, i.e. e.g. text content, but in special attributes; you can fill the &quot;original&quot; places of these strings with fallbacks, e.g. to the English langauge.
66-
Taht means, you can e.g. add <code>aria-label=&quot;error message&quot; data-i18n-aria-label=&quot;__MSG_errorMessage__&quot;</code> and the <code>aria-label</code> will always show a valid label, even if it has not yet been loaded via JS.</p>
69+
That means, you can e.g. add <code>aria-label=&quot;error message&quot; data-i18n data-i18n-aria-label=&quot;__MSG_errorMessage__&quot;</code> and the <code>aria-label</code> will always show a valid label, even if it has not (yet) been loaded via JS.</p>
6770
<p>Note, however, this is not required and you can easily leave it away, because <a href="https://developer.mozilla.org/docs/Mozilla/Add-ons/WebExtensions/Internationalization#Localized_string_selection">the WebExtension API includes an automatic fallback</a> and may thus fallback by itself. This of course also applies to the library here, so it will also set/replace the strings that you specify to translate, even if you e.g. hardcode translations.</p>
6871
<h3 id="example">Example</h3>
6972
<pre class="prettyprint source lang-html"><code>&lt;!-- translate content, with hardcoded English fallback -->
@@ -74,18 +77,18 @@ <h3 id="example">Example</h3>
7477
&lt;!-- translate only attributes, no hardcoded fallback -->
7578
&lt;div class=&quot;icon-dismiss&quot; data-i18n data-i18n-aria-label=&quot;__MSG_dismissIconDescription__&quot;>
7679

77-
&lt;!-- translate only attributes, no hardcoded fallback -->
78-
&lt;div class=&quot;icon-dismiss&quot; data-i18n data-i18n-aria-label=&quot;__MSG_dismissIconDescription__&quot;>
79-
8080
&lt;!-- translate only attributes, with hardcoded English fallback -->
8181
&lt;div class=&quot;icon-dismiss&quot; data-i18n aria-label=&quot;close&quot; data-i18n-aria-label=&quot;__MSG_dismissIconDescription__&quot;>
8282

8383
&lt;!-- translate content and attributes, with hardcoded English fallback -->
84-
&lt;a data-i18n=&quot;__MSG_optionLearnMore__&quot; data-i18n-href=&quot;__MSG_optionErrorCorrectionDescrLink__&quot; href=&quot;https://en.wikipedia.org/wiki/QR_code#Error_correction&quot;>Learn more&lt;/a></code></pre><h3 id="localisation-l10n-">Localisation (l10n)</h3>
84+
&lt;a data-i18n=&quot;__MSG_optionLearnMore__&quot; data-i18n-href=&quot;__MSG_optionErrorCorrectionDescrLink__&quot; href=&quot;https://en.wikipedia.org/wiki/QR_code#Error_correction&quot;>Learn more&lt;/a>
85+
</code></pre>
86+
<h3 id="localisation-(l10n)">Localisation (l10n)</h3>
8587
<p>As mentioned, it uses the WebExtension translation system, so you <a href="https://developer.mozilla.org/docs/Mozilla/Add-ons/WebExtensions/Internationalization#Providing_localized_strings_in__locales">just add the strings to translate to your <code>messages.json</code></a>.</p>
8688
<p>For translators, it is also useful to include a small guide in your contributing guide, such as <a href="https://github.com/TinyWebEx/common/blob/contribimprove/CONTRIBUTING.md#internationalisation-of-html-files">ours here</a>.</p>
8789
<h3 id="using-html-in-translations">Using HTML in translations</h3>
8890
<p><strong>Important:</strong> By default you can only include plain text in your translations. HTML translations are disabled by default for security reasons!</p>
91+
<p><em>Instead</em> of using HTML it is strongly suggested to use [the child-keeping method explained below](<a href="#keeping-child-elements-in-HTML">including sub-HTML elements in translations</a>).</p>
8992
<p>Whether HTML translations are enabled depends on one source code file in this repo, i.e. <a href="replaceInnerContent.js"><code>replaceInnerContent.js</code></a>. The reason for this is that <a href="https://github.com/mozilla/addons-linter">the linting tool</a> used on addons.mozilla.org (AMO) otherwise complains about a potential security issue if HTML translations are enabled. This results in a warning when uploading the add-on to AMO as the HTML version makes use of <code>innerHtml</code> in the JavaScript file.</p>
9093
<p>If you want to enable HTML translations though, you need to:</p>
9194
<ul>
@@ -95,8 +98,49 @@ <h3 id="using-html-in-translations">Using HTML in translations</h3>
9598
<p>So a sentence could look like this:</p>
9699
<pre class="prettyprint source lang-json"><code>&quot;boldSentence&quot;: {
97100
&quot;message&quot;: &quot;!HTML! &lt;b>This sentence is bold.&lt;/b>&quot;
98-
}</code></pre><p>This is also explained in short for <a href="https://github.com/TinyWebEx/common/blob/contribimprove/CONTRIBUTING.md#using-html-in-translations">translators in the contributing guide</a>.</p>
99-
<p><strong>Warning:</strong> If you allow/use HTML translation, note that translators could inject malicious (HTML) code then. As such, you need to take care when reviewing the translation files then. The marker <code>!HTML!</code> can help you here as you can just search for it.</p></article>
101+
}
102+
</code></pre>
103+
<p>This can also explained in short for <a href="https://github.com/TinyWebEx/common/blob/10ff5cace217841591d702408e64f1ead8c26a64/CONTRIBUTING.md#using-html-in-translations">translators in the contributing guide</a> (permalink, as I now discourage using that feature, anyway).</p>
104+
<p><strong>Warning:</strong> If you allow/use HTML translation, note that translators could inject malicious (HTML) code then. As such, you need to take care when reviewing the translation files then. The marker <code>!HTML!</code> can help you here as you can just search for it.</p>
105+
<h3 id="keeping-child-elements-in-html">Keeping child elements in HTML</h3>
106+
<p>By default, this module just replaces the whole text/content of an HTML tag.
107+
A common use case is that you may need to include different sub-HTML items (children of the HTML parent) in your translation however. Especially, you may want to allow translators to adjust their position in the translation.
108+
This is e.g. useful when you want to include links in your translation.</p>
109+
<p>To do so, you just need to add <code>data-opt-i18n-keep-children</code> to the HTML parent that needs to be translated this way. It will then:</p>
110+
<ul>
111+
<li>put all HTML tags into a substitutions (<code>$1</code>, <code>$2</code>, …), which you can use to refer to your content in the translation</li>
112+
<li>translate only the actual text content and adjust the position/ordering of the HTML tags, so they meet the translation result</li>
113+
</ul>
114+
<p>In the end, you e.g. can have a translation like this:</p>
115+
<pre class="prettyprint source lang-json"><code>&quot;parent&quot;: {
116+
&quot;message&quot;: &quot;Check out $LINK$!&quot;,
117+
&quot;description&quot;: &quot;The text shown.&quot;,
118+
&quot;placeholders&quot;: {
119+
&quot;link&quot;: {
120+
&quot;content&quot;: &quot;$1&quot;,
121+
&quot;example&quot;: &quot;&lt;a href=\&quot;https://example.com\&quot;>for more details&lt;/a>&quot;
122+
}
123+
}
124+
},
125+
&quot;link&quot;: {
126+
&quot;message&quot;: &quot;https://example.com&quot;,
127+
&quot;description&quot;: &quot;The link location.&quot;
128+
},
129+
&quot;linkText&quot;: {
130+
&quot;message&quot;: &quot;for more details&quot;,
131+
&quot;description&quot;: &quot;The text shown for the link.&quot;
132+
},
133+
</code></pre>
134+
<p>The corresponding HTML code is the following one:</p>
135+
<pre class="prettyprint source lang-html"><code>&lt;p data-i18n=&quot;__MSG_parent__&quot; data-opt-i18n-keep-children>
136+
Check out &lt;a data-i18n=&quot;__MSG_linkText__&quot; data-i18n-href=&quot;__MSG_link__&quot; href=&quot;https://example.com&quot;>for more details&lt;/a>!
137+
&lt;/p>
138+
</code></pre>
139+
<p>As you can, see in the example <code>$1</code> will be replaced with the <code>a</code> tag for the link. As you can see, obviously, you need to translate the <code>a</code> tag as usual, too, and thus you just use the same principles as usual.
140+
It does also keep the HTML item intact and just move it, it will never be removed from the DOM and you can thus just use it as every other HTML tag.
141+
You should also be able to nest this feature, as you want.</p>
142+
<p><strong>Note:</strong> If you use this feature, you should make sure your translations also actually also refer to the HTML tag (the substitution) via <code>$1</code>, <code>$2</code> etc.
143+
If they don't, the Localizer does not know where to put the HTML tag and it usually is just placed at the end. As said, it does never delete a HTML tag, so it just stays &quot;in place&quot; then.</p></article>
100144
</section>
101145

102146

@@ -111,12 +155,14 @@ <h3 id="using-html-in-translations">Using HTML in translations</h3>
111155
<br class="clear">
112156

113157
<footer>
114-
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Sun Jan 20 2019 21:55:59 GMT+0100 (Mitteleuropäische Normalzeit) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
158+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.6.2</a> on Sun Jun 30 2019 23:07:02 GMT+0200 (GMT+02:00) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
115159
</footer>
116160

117161
<script>prettyPrint();</script>
162+
<script src="scripts/polyfill.js"></script>
118163
<script src="scripts/linenumber.js"></script>
119164

120165

166+
121167
</body>
122168
</html>

0 commit comments

Comments
 (0)