forked from cmgriffing/angular-i18n-presentation
-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.html
393 lines (378 loc) · 19.8 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="generator" content="pandoc">
<title></title>
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, minimal-ui">
<link rel="stylesheet" href="reveal.js/css/reveal.css">
<style type="text/css">code{white-space: pre;}</style>
<style type="text/css">
div.sourceCode { overflow-x: auto; }
table.sourceCode, tr.sourceCode, td.lineNumbers, td.sourceCode {
margin: 0; padding: 0; vertical-align: baseline; border: none; }
table.sourceCode { width: 100%; line-height: 100%; }
td.lineNumbers { text-align: right; padding-right: 4px; padding-left: 4px; color: #aaaaaa; border-right: 1px solid #aaaaaa; }
td.sourceCode { padding-left: 5px; }
code > span.kw { color: #007020; font-weight: bold; } /* Keyword */
code > span.dt { color: #902000; } /* DataType */
code > span.dv { color: #40a070; } /* DecVal */
code > span.bn { color: #40a070; } /* BaseN */
code > span.fl { color: #40a070; } /* Float */
code > span.ch { color: #4070a0; } /* Char */
code > span.st { color: #4070a0; } /* String */
code > span.co { color: #60a0b0; font-style: italic; } /* Comment */
code > span.ot { color: #007020; } /* Other */
code > span.al { color: #ff0000; font-weight: bold; } /* Alert */
code > span.fu { color: #06287e; } /* Function */
code > span.er { color: #ff0000; font-weight: bold; } /* Error */
code > span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */
code > span.cn { color: #880000; } /* Constant */
code > span.sc { color: #4070a0; } /* SpecialChar */
code > span.vs { color: #4070a0; } /* VerbatimString */
code > span.ss { color: #bb6688; } /* SpecialString */
code > span.im { } /* Import */
code > span.va { color: #19177c; } /* Variable */
code > span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
code > span.op { color: #666666; } /* Operator */
code > span.bu { } /* BuiltIn */
code > span.ex { } /* Extension */
code > span.pp { color: #bc7a00; } /* Preprocessor */
code > span.at { color: #7d9029; } /* Attribute */
code > span.do { color: #ba2121; font-style: italic; } /* Documentation */
code > span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
code > span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
</style>
<link rel="stylesheet" href="reveal.js/css/theme/beige.css" id="theme">
<!-- Printing and PDF exports -->
<script>
var link = document.createElement( 'link' );
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = window.location.search.match( /print-pdf/gi ) ? 'reveal.js/css/print/pdf.css' : 'reveal.js/css/print/paper.css';
document.getElementsByTagName( 'head' )[0].appendChild( link );
</script>
<!--[if lt IE 9]>
<script src="reveal.js/lib/js/html5shiv.js"></script>
<![endif]-->
</head>
<body>
<div class="reveal">
<div class="slides">
<section id="angular-i18n" class="slide level1">
<h1>Angular i18n</h1>
</section>
<section class="slide level1">
<section id="terminology" class="level2">
<h2>Terminology</h2>
<p>i18n = Internationalization</p>
<p>l10n = Localization</p>
<p>The number in each is the amount of letters between the start and end.</p>
<aside class="notes">
<p>It seems obvious now, but at first I had no idea why they were named this way.</p>
</aside>
</section>
<section id="why-i18n" class="level2">
<h2>Why i18n</h2>
<p>Whats the point when we have Google Translate?</p>
<figure>
<img src="./assets/google-translate.jpg" />
</figure>
<aside class="notes">
<p>Chrome can even identify pages in other languages and translate them for you using Google translate under the hood.</p>
</aside>
</section>
<section id="however" class="level2">
<h2>However</h2>
<figure>
<img src="./assets/google-translate-no.jpg" />
</figure>
<aside class="notes">
<p>The results tend to range from being poorly worded to being complete gibberish.</p>
<p>You can "get by" with it many times. But that isn't a great UX.</p>
</aside>
</section>
<section id="how-i18n-generally-works" class="level2">
<h2>How i18n Generally works</h2>
<ul>
<li><p>The dev codes the app using default text values.</p></li>
<li><p>The translator receives a file that contains the default values and translates them into the target language.</p></li>
<li><p>The app then uses this edited version of the file to replace the default values with the translated ones.</p></li>
</ul>
<aside class="notes">
<p>There are some tiny steps along the way that make this all possible and I will hopefully help you understand some of them better by the end of this.</p>
</aside>
</section>
<section id="angular-specifics" class="level2">
<h2>Angular Specifics</h2>
<p>Multiple Options:</p>
<ul>
<li><p>Angular's Internal i18n - https://angular.io/guide/i18n</p></li>
<li><p>ngx-translate - http://www.ngx-translate.com/</p></li>
</ul>
<aside class="notes">
<p>There are a few other smaller libs but these are the big two options.</p>
<p>Angular has the ability to inject the values at AOT compile time when most other frameworks and solutions rely on run-time injection.</p>
<p>ngx-translate happens at run-time. This means two sets of translations could ship to the user as it swaps out the default.</p>
<p>Important: Ask about audience familiarity with AOT/JIT.</p>
</aside>
</section>
<section id="aot-vs-jit" class="level2">
<h2>AOT vs JIT</h2>
<p>JIT: Just In Time (templates and dependencies are evaluated at run time)</p>
<p>AOT: Ahead Of Time (optimized at compile time when you run a production build)</p>
<p>Why AOT:</p>
<ul>
<li>Smaller bundle size</li>
<li>Faster startup time</li>
<li>More type and error safety</li>
</ul>
<aside class="notes">
<p>For the best end-user experience you want to utilize AOT compilation.</p>
</aside>
</section>
</section>
<section class="slide level1">
<section id="getting-started" class="level2">
<h2>Getting Started</h2>
<p>We will go over:</p>
<ul>
<li>Tagging elements for translation</li>
<li>Extracting those translations into an XLIFF file</li>
<li>Editing an XLIFF file for a new target language</li>
<li>Compiling that xliff file into your AOT build</li>
</ul>
<aside class="notes">
<p>After an overview of each step, I will attempt to go through the process live.</p>
</aside>
</section>
<section id="tagging-elements" class="level2">
<h2>Tagging elements</h2>
<div class="sourceCode"><pre class="sourceCode html"><code class="sourceCode html"><span class="kw"><a</span><span class="ot"> href=</span><span class="st">"google.com"</span><span class="ot"> i18n</span><span class="kw">></span>Go To Google<span class="kw"></a></span></code></pre></div>
</section>
<section id="attributes" class="level2">
<h2>Attributes</h2>
<div class="sourceCode"><pre class="sourceCode html"><code class="sourceCode html"><span class="kw"><img</span><span class="ot"> alt=</span><span class="st">"Something"</span><span class="ot"> i18n-alt src=</span><span class="st">"..."</span> <span class="kw">/></span></code></pre></div>
</section>
<section id="tagging-elements-icu-formatting" class="level2">
<h2>Tagging elements (ICU formatting)</h2>
<div class="sourceCode"><pre class="sourceCode html"><code class="sourceCode html"><span class="kw"><span</span><span class="ot"> i18n</span><span class="kw">></span>Updated {minutes, plural, =0 {just now} =1 {one minute ago} other {{{minutes}} minutes ago}}<span class="kw"></span></span></code></pre></div>
<div class="sourceCode"><pre class="sourceCode html"><code class="sourceCode html"><span class="kw"><span</span><span class="ot"> i18n</span><span class="kw">></span>Watch out for {animalType, select, wolf {wolves} cat {kitties} other {animals}}<span class="kw"></span></span></code></pre></div>
<aside class="notes">
<p>'other' is a catch-all. I tend to think of it as the final default case of a switch statement.</p>
<p>You are even able to nest ICU syntax. The angular docs show an example of this, but its a pretty narrow use case so I won't cover it more today.</p>
</aside>
</section>
<section id="extraction" class="level2">
<h2>Extraction</h2>
<pre class="shell-session"><code>ng xi18n</code></pre>
<p>No CLI?</p>
<ul>
<li><p>ng-xi18n - a tool that is part of the <code>@angular/compiler</code> repo</p></li>
<li><p><a href="https://github.com/angular/angular-cli/tree/master/packages/%40ngtools/webpack">Webpack Plugin</a></p></li>
</ul>
<aside class="notes">
<p>You can add paramaters to change things such as where the messages.xlf file is output to.</p>
<p>By default it will just create it in the src directory.</p>
<p>You can also change the format. (<code>xmb</code> or <code>xlf</code>)</p>
<p>xlf or XLIFF seems most common. But some services don't support the xliff format that Angular provides.</p>
</aside>
</section>
<section id="editing-the-xliff-before" class="level2">
<h2>Editing the XLIFF (Before)</h2>
<figure>
<img src="./assets/messages_base_xliff.jpg" />
</figure>
</section>
<section id="editing-the-xliff-after" class="level2">
<h2>Editing the XLIFF (After)</h2>
<figure>
<img src="./assets/messages_gibberish_xliff.jpg" />
</figure>
</section>
<section id="tools-for-editing-xliff-files" class="level2">
<h2>Tools for Editing XLIFF files</h2>
<p>Online Tools:</p>
<ul>
<li>POEditor <a href="https://poeditor.com/" class="uri">https://poeditor.com/</a></li>
<li>Matecat <a href="https://www.matecat.com/" class="uri">https://www.matecat.com/</a></li>
<li>SmartCAT <a href="https://www.smartcat.ai/" class="uri">https://www.smartcat.ai/</a></li>
</ul>
<p>Applications:</p>
<ul>
<li>Swordfish <a href="https://maxprograms.com/products/swordfish.html" class="uri">https://maxprograms.com/products/swordfish.html</a></li>
<li>OmegaT <a href="http://omegat.org/" class="uri">http://omegat.org/</a></li>
<li>Transit NXT <a href="Transit%20NXT">Transit NXT</a></li>
<li>Wordfast <a href="http://www.wordfast.com/" class="uri">http://www.wordfast.com/</a></li>
</ul>
<p>Mostly found via: <a href="https://en.wikipedia.org/wiki/XLIFF#Related_tools" class="uri">https://en.wikipedia.org/wiki/XLIFF#Related_tools</a></p>
<aside class="notes">
<p>CAT = Computer Aided Translation</p>
</aside>
</section>
<section id="compilation" class="level2">
<h2>Compilation</h2>
<p>Build:</p>
<pre class="shell-session"><code>ng build --aot --locale fr --i18n-format xlf --i18n-file src/locale/messages.fr.xlf --missing-translation error</code></pre>
<pre class="shell-session"><code>--output-path dist/fr</code></pre>
<p>What if your app is served like this <code>https://myapp.com/fr/</code>?</p>
<pre class="shell-session"><code>--base-href /fr/</code></pre>
<aside class="notes">
<p>When looking at the first command, you can also use similar paramaters for ng serve.</p>
<p>That could prove to be slow and I have had "out of memory" errors before when running ng serve with aot. Your mileage may vary.</p>
<p>Improving aot enough for use during serve is a priority of the angular and angular-cli teams, so I expect we will be hearing about improvements sometime soon. (maybe at ngConf?)</p>
</aside>
</section>
<section id="end-result-language-selection" class="level2">
<h2>End result (language selection)</h2>
<p>Issue: You have a compilation for every language you support. How do you decide which to serve to your user?</p>
<p>Automatic detection is buggy based on browser/os.</p>
<p>Allow the user to choose. (drop-down in header, splash page, etc)</p>
<aside class="notes">
<p>User Accounts make this simpler</p>
<p>Anonymous users need the ability to change.</p>
</aside>
</section>
</section>
<section class="slide level1">
<section id="live-coding" class="level2">
<h2>Live coding</h2>
</section>
</section>
<section class="slide level1">
<section id="caveats" class="level2">
<h2>Caveats</h2>
</section>
<section id="icu-attribute-values" class="level2">
<h2>ICU Attribute Values</h2>
<div class="sourceCode"><pre class="sourceCode html"><code class="sourceCode html"><span class="kw"><img</span>
<span class="ot"> src=</span><span class="st">"..."</span>
<span class="ot"> alt=</span><span class="st">"A dynamic image of {{itemCount}} {itemCount, plural, =1 {item} other {items}}"</span>
<span class="ot"> i18n-alt</span>
<span class="kw">/></span></code></pre></div>
</section>
<section id="nesting" class="level2">
<h2>Nesting</h2>
<div class="sourceCode"><pre class="sourceCode html"><code class="sourceCode html"><span class="kw"><div</span><span class="ot"> i18n</span><span class="kw">></span>Follow this <span class="kw"><a</span><span class="ot"> href=</span><span class="st">"#"</span><span class="kw">></span>link<span class="kw"></a></span>.<span class="kw"></div></span></code></pre></div>
</section>
<section id="variable-from-ts-file" class="level2">
<h2>Variable from ts file</h2>
<div class="sourceCode"><pre class="sourceCode html"><code class="sourceCode html"><span class="kw"><span</span><span class="ot"> i18n</span><span class="kw">></span>{{ someValue }}<span class="kw"></span></span></code></pre></div>
</section>
</section>
<section class="slide level1">
<section id="angular-i18n-roadmap" class="level2">
<h2>Angular i18n Roadmap</h2>
<figure>
<img src="./assets/i18n_plans_for_v5_and_beyond.jpg" />
</figure>
<p><a href="https://github.com/angular/angular/issues/16477#issue-225727620">i18n Plans Github Issue</a></p>
</section>
<section id="run-time-aot-built-i18n" class="level2">
<h2>Run-time AOT-built i18n</h2>
<aside class="notes">
</aside>
</section>
</section>
<section class="slide level1">
<section id="hacks" class="level2">
<h2>Hacks</h2>
</section>
<section id="nesting-1" class="level2">
<h2>Nesting</h2>
<p>Instead of:</p>
<div class="sourceCode"><pre class="sourceCode html"><code class="sourceCode html"><span class="kw"><div</span><span class="ot"> i18n</span><span class="kw">></span>Follow this <span class="kw"><a</span><span class="ot"> href=</span><span class="st">"#"</span><span class="kw">></span>link<span class="kw"></a></span>.<span class="kw"></div></span></code></pre></div>
<p>Try:</p>
<div class="sourceCode"><pre class="sourceCode html"><code class="sourceCode html"><span class="kw"><div><span</span><span class="ot"> i18n</span><span class="kw">></span>Follow this<span class="kw"><span></span> <span class="kw"><a</span><span class="ot"> href=</span><span class="st">"#"</span><span class="ot"> i18n</span><span class="kw">></span>link<span class="kw"></a></span>.<span class="kw"></div></span></code></pre></div>
</section>
<section id="icu-formatting-in-attributes" class="level2">
<h2>ICU Formatting in attributes</h2>
<div class="sourceCode"><pre class="sourceCode html"><code class="sourceCode html"><span class="kw"><div</span><span class="ot"> hidden</span><span class="kw">></span>
<span class="co"><!-- ICU selection message marked for translation --></span>
<span class="kw"><span</span> <span class="er">#altValue</span><span class="ot"> i18n</span><span class="kw">></span>{role, select, tinker {Tinker} tailor {Tailor} soldier {Soldier} spy {Spy}} Profile Image
<span class="kw"></div></span>
<span class="kw"><img</span> <span class="er">[alt]</span><span class="ot">=</span><span class="st">"removeHtmlComments(altValue.innerHTML)"</span> <span class="kw">/></span></code></pre></div>
</section>
<section id="why-removehtmlcomments" class="level2">
<h2>Why removeHtmlComments()?</h2>
<div class="sourceCode"><pre class="sourceCode html"><code class="sourceCode html"><span class="kw"><span</span><span class="ot"> _ngcontent-c4=</span><span class="st">""</span><span class="kw">></span><span class="co"><!--bindings={</span>
<span class="co"> "ng-reflect-ng-switch": "tinker"</span>
<span class="co">}--><!--bindings={</span>
<span class="co"> "ng-reflect-ng-switch-case": "tinker"</span>
<span class="co">}--></span>Tinker<span class="co"><!--bindings={</span>
<span class="co"> "ng-reflect-ng-switch-case": "tailor"</span>
<span class="co">}--><!--bindings={</span>
<span class="co"> "ng-reflect-ng-switch-case": "soldier"</span>
<span class="co">}--><!--bindings={</span>
<span class="co"> "ng-reflect-ng-switch-case": "spy"</span>
<span class="co">}--></span> Profile Image<span class="kw"></span></span></code></pre></div>
<div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="at">removeHtmlComments</span>(html) <span class="op">{</span>
<span class="cf">return</span> <span class="va">html</span>.<span class="at">replace</span>(<span class="ss">/<!--</span><span class="sc">[\s\S]*?</span><span class="ss">-->/g</span><span class="op">,</span> <span class="st">''</span>)<span class="op">;</span>
<span class="op">}</span></code></pre></div>
</section>
<section id="dynamically-created-components" class="level2">
<h2>Dynamically Created Components</h2>
<ul>
<li><p><a href="https://angular.io/guide/dynamic-component-loader">Angular core guide</a></p></li>
<li><p><a href="http://blog.rangle.io/dynamically-creating-components-with-angular-2/">Rangle Blog post</a></p></li>
</ul>
</section>
<section id="conference-call-bingos-hackery" class="level2">
<h2>Conference Call Bingo's Hackery</h2>
<p><a href="./assets/shia-magic.gif"></a></p>
</section>
</section>
<section class="slide level1">
<section id="what-didnt-i-cover" class="level2">
<h2>What didn't I cover?</h2>
<ul>
<li><p>Per-route bundles and how that impacts the compilation process?</p></li>
<li><p>ngx-translate</p></li>
<li><p>Continuous translation workflows</p></li>
<li><p>Date/time, number, and currency formatting - Angular pipes can help you with those: <a href="https://angular.io/guide/i18n#i18n-pipes" class="uri">https://angular.io/guide/i18n#i18n-pipes</a></p></li>
</ul>
<div>
<p>Per route bundling could just work or it could have some quirks. I haven't gotten that far with it yet.</p>
<p>ngx-translate is very solid. The only gripe is that it sends the entire translation file. Per-route bundling would mean that you are getting translations for pages you aren't viewing. And the intended translations have to wait for the whole file to download.</p>
<p>I don't have any real world experience with translating an app, then adding features or content and dealing with merging the already translated xlf files with the base one that contains new fields. Maybe it just works depending on the translators editing software, I just don't know.</p>
</div>
</section>
<section id="citations" class="level2">
<h2>Citations</h2>
<ul>
<li><p>https://angular.io/guide/i18n</p></li>
<li><p>https://github.com/angular/angular-cli/wiki/stories-internationalization</p></li>
<li><p>https://github.com/cmgriffing/angular-i18n-presentation/demo</p></li>
<li><p>https://github.com/cmgriffing/conference-call-bingo</p></li>
</ul>
<style class="styling-overrides">
code {
background: white;
}
.reveal section img {
max-height: 400px;
}
</style>
</section>
</section>
</div>
</div>
<script src="reveal.js/lib/js/head.min.js"></script>
<script src="reveal.js/js/reveal.js"></script>
<script>
// Full list of configuration options available at:
// https://github.com/hakimel/reveal.js#configuration
Reveal.initialize({
// Optional reveal.js plugins
dependencies: [
{ src: 'reveal.js/lib/js/classList.js', condition: function() { return !document.body.classList; } },
{ src: 'reveal.js/plugin/zoom-js/zoom.js', async: true },
{ src: 'reveal.js/plugin/notes/notes.js', async: true }
]
});
</script>
</body>
</html>