aboutsummaryrefslogtreecommitdiff
path: root/files/zh-tw/mozilla/add-ons/webextensions/internationalization/index.html
blob: c7e493e8f4e8f4c826091511d9662d94dbd162d0 (plain)
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
394
395
396
397
398
399
400
---
title: Internationalization
slug: Mozilla/Add-ons/WebExtensions/Internationalization
translation_of: Mozilla/Add-ons/WebExtensions/Internationalization
---
<div>{{AddonSidebar}}</div>

<p><a href="/zh-TW/docs/Mozilla/Add-ons/WebExtensions">WebExtension</a> API 有個相當方便、能用於國際化的模組:<a href="/zh-TW/docs/Mozilla/Add-ons/WebExtensions/API/i18n">i18n</a>。我們將在這篇文章內探索本功能,並提供實際做動的範例。專供 extensions 組建所使用的 i18n 系統 API,用法與坊間諸如 <a href="http://i18njs.com/">i18n.js</a> 的函式庫相似。</p>

<div class="note">
<p>本例所使用的套件:<a href="https://github.com/mdn/webextensions-examples/tree/master/notify-link-clicks-i18n">notify-link-clicks-i18n</a> 能在 GitHub 找到。看各章節時,請配著原始碼觀看。</p>
</div>

<h2 id="剖析國際化的套件">剖析國際化的套件</h2>

<p>國際化套件能包含與其他套件相同的功能:<a href="/zh-TW/Add-ons/WebExtensions/Anatomy_of_a_WebExtension#Background_scripts">background scripts</a><a href="/zh-TW/docs/Mozilla/Add-ons/WebExtensions/Content_scripts">content scripts</a>……等。但它也擁有一些能允許在語言間切換的部份。目錄樹大概是這樣:</p>

<ul class="directory-tree">
 <li>extension-root-directory/
  <ul>
   <li>_locales
    <ul>
     <li>en
      <ul>
       <li>messages.json
        <ul>
         <li>English messages (strings)</li>
        </ul>
       </li>
      </ul>
     </li>
     <li>de
      <ul>
       <li>messages.json
        <ul>
         <li>German messages (strings)</li>
        </ul>
       </li>
      </ul>
     </li>
     <li>etc.</li>
    </ul>
   </li>
   <li>manifest.json
    <ul>
     <li>locale-dependent metadata</li>
    </ul>
   </li>
   <li>myJavascript.js
    <ul>
     <li>JavaScript for retrieving browser locale, locale-specific messages, etc.</li>
    </ul>
   </li>
   <li>myStyles.css
    <ul>
     <li>locale-dependent CSS</li>
    </ul>
   </li>
  </ul>
 </li>
</ul>

<p>我們將逐一探索各大新特性:章節的每個部份,都是要國際化套件時,所需遵循的步驟。</p>

<h2 id="在__locales_提供本地化的字串">在 _locales 提供本地化的字串</h2>

<div class="pull-aside">
<div class="moreinfo">You can look up language subtags using the <em>Find</em> tool on the <a href="http://r12a.github.io/apps/subtags/">Language subtag lookup page</a>. Note that you need to search for the English name of the language.</div>
</div>

<p>Every i18n system requires the provision of strings translated into all the different locales you want to support. In extensions, these are contained within a directory called <code>_locales</code>, placed inside the extension root. Each individual locale has its strings (referred to as messages) contained within a file called <code>messages.json</code>, which is placed inside a subdirectory of <code>_locales</code>, named using the language subtag for that locale's language.</p>

<p>Note that if the subtag includes a basic language plus a regional variant, then the language and variant are conventionally separated using a hyphen: for example, "en-US". However, in the directories under <code>_locales</code>, <strong>the separator must be an underscore</strong>: "en_US".</p>

<p>So <a href="https://github.com/mdn/webextensions-examples/tree/master/notify-link-clicks-i18n/_locales">for example, in our sample app</a> we have directories for "en" (English), "de" (German), "nl" (Dutch), and "ja" (Japanese). Each one of these has a <code>messages.json</code> file inside it.</p>

<p>Let's now look at the structure of one of these files (<a href="https://github.com/mdn/webextensions-examples/blob/master/notify-link-clicks-i18n/_locales/en/messages.json">_locales/en/messages.json</a>):</p>

<pre class="brush: json">{
  "extensionName": {
    "message": "Notify link clicks i18n",
    "description": "Name of the extension."
  },

  "extensionDescription": {
    "message": "Shows a notification when the user clicks on links.",
    "description": "Description of the extension."
  },

  "notificationTitle": {
    "message": "Click notification",
    "description": "Title of the click notification."
  },

  "notificationContent": {
    "message": "You clicked $URL$.",
    "description": "Tells the user which link they clicked.",
    "placeholders": {
      "url" : {
        "content" : "$1",
        "example" : "https://developer.mozilla.org"
      }
    }
  }
}</pre>

<p>This file is standard JSON — each one of its members is an object with a name, which contains a <code>message</code> and a <code>description</code>. All of these items are strings; <code>$URL$</code> is a placeholder, which is replaced with a substring at the time the <code>notificationContent</code> member is called by the extension. You'll learn how to do this in the <a href="#retrieving_message_strings_from_javascript">Retrieving message strings from JavaScript</a> section.</p>

<div class="note">
<p><strong>Note</strong>: You can find much more information about the contents of <code>messages.json</code> files in our <a href="/en-US/docs/Mozilla/Add-ons/WebExtensions/API/i18n/Locale-Specific_Message_reference">Locale-Specific Message reference</a>.</p>
</div>

<h2 id="Internationalizing_manifest.json">Internationalizing manifest.json</h2>

<p>There are a couple of different tasks to carry out to internationalize your manifest.json.</p>

<h3 id="Retrieving_localized_strings_in_manifests">Retrieving localized strings in manifests</h3>

<p>Your <a href="https://github.com/mdn/webextensions-examples/blob/master/notify-link-clicks-i18n/manifest.json">manifest.json</a> includes strings that are displayed to the user, such as the extension's name and description. If you internationalize these strings and put the appropriate translations of them in messages.json, then the correct translation of the string will be displayed to the user, based on the current locale, like so.</p>

<p>To internationalize strings, specify them like this:</p>

<pre class="brush: json">"name": "__MSG_extensionName__",
"description": "__MSG_extensionDescription__",</pre>

<p>Here, we are retrieving message strings dependant on the browser's locale, rather than just including static strings.</p>

<p>To call a message string like this, you need to specify it like this:</p>

<ol>
 <li>Two underscores, followed by</li>
 <li>The string "MSG", followed by</li>
 <li>One underscore, followed by</li>
 <li>The name of the message you want to call as defined in <code>messages.json</code>, followed by</li>
 <li>Two underscores</li>
</ol>

<pre><strong>__MSG_</strong> + <em>messageName</em> + <strong>__</strong></pre>

<h3 id="Specifying_a_default_locale">Specifying a default locale</h3>

<p>Another field you should specify in your manifest.json is <a href="/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/default_locale">default_locale</a>:</p>

<pre class="brush: json">"default_locale": "en"</pre>

<p>This specifies a default locale to use if the extension doesn't include a localized string for the browser's current locale. Any message strings that are not available in the browser locale are taken from the default locale instead. There are some more details to be aware of in terms of how the browser selects strings — see <a href="#localized_string_selection">Localized string selection</a>.</p>

<h2 id="Locale-dependent_CSS">Locale-dependent CSS</h2>

<p>Note that you can also retrieve localized strings from CSS files in the extension. For example, you might want to construct a locale-dependent CSS rule, like this:</p>

<pre class="brush: css">header {
  background-image: url(../images/__MSG_extensionName__/header.png);
}</pre>

<p>This is useful, although you might be better off handling such a situation using <a href="#predefined_messages">Predefined messages</a>.</p>

<h2 id="Retrieving_message_strings_from_JavaScript">Retrieving message strings from JavaScript</h2>

<p>So, you've got your message strings set up, and your manifest. Now you just need to start calling your message strings from JavaScript so your extension can talk the right language as much as possible. The actual <a href="/en-US/docs/Mozilla/Add-ons/WebExtensions/API/i18n">i18n API</a> is pretty simple, containing just four main methods:</p>

<ul>
 <li>You'll probably use {{WebExtAPIRef("i18n.getMessage()")}} most often — this is the method you use to retrieve a specific language string, as mentioned above. We'll see specific usage examples of this below.</li>
 <li>The {{WebExtAPIRef("i18n.getAcceptLanguages()")}} and {{WebExtAPIRef("i18n.getUILanguage()")}} methods could be used if you needed to customize the UI depending on the locale — perhaps you might want to show preferences specific to the users' preferred languages higher up in a prefs list, or display cultural information relevant only to a certain language, or format displayed dates appropriately according to the browser locale.</li>
 <li>The {{WebExtAPIRef("i18n.detectLanguage()")}} method could be used to detect the language of user-submitted content, and format it appropriately.</li>
</ul>

<p>In our <a href="https://github.com/mdn/webextensions-examples/tree/master/notify-link-clicks-i18n">notify-link-clicks-i18n</a> example, the<a href="https://github.com/mdn/webextensions-examples/blob/master/notify-link-clicks-i18n/background-script.js"> background script</a> contains the following lines:</p>

<pre class="brush: js">var title = browser.i18n.getMessage("notificationTitle");
var content = browser.i18n.getMessage("notificationContent", message.url);</pre>

<p>The first one just retrieves the <code>notificationTitle message</code> field from the available <code>messages.json</code> file most appropriate for the browser's current locale. The second one is similar, but it is being passed a URL as a second parameter. What gives? This is how you specify the content to replace the <code>$URL$</code> placeholder we see in the <code>notificationContent message</code> field:</p>

<pre class="brush: json">"notificationContent": {
  "message": "You clicked $URL$.",
  "description": "Tells the user which link they clicked.",
  "placeholders": {
    "url" : {
      "content" : "$1",
      "example" : "https://developer.mozilla.org"
    }
  }
}
</pre>

<p>The <code>"placeholders"</code> member defines all the placeholders, and where they are retrieved from. The <code>"url"</code> placeholder specifies that its content is taken from $1, which is the first value given inside the second parameter of <code>getMessage()</code>. Since the placeholder is called <code>"url"</code>, we use <code>$URL$</code> to call it inside the actual message string (so for <code>"name"</code> you'd use <code>$NAME$</code>, etc.) If you have multiple placeholders, you can provide them inside an array that is given to {{WebExtAPIRef("i18n.getMessage()")}} as the second parameter — <code>[a, b, c]</code> will be available as <code>$1</code>, <code>$2</code>, and <code>$3</code>, and so on, inside <code>messages.json</code>.</p>

<p>Let's run through an example: the original <code>notificationContent</code> message string in the <code>en/messages.json</code> file is</p>

<pre>You clicked $URL$.</pre>

<p>Let's say the link clicked on points to <code>https://developer.mozilla.org</code>. After the {{WebExtAPIRef("i18n.getMessage()")}} call, the contents of the second parameter are made available in messages.json as <code>$1</code>, which replaces the <code>$URL$</code> placeholder as defined in the <code>"url"</code> placeholder. So the final message string is</p>

<pre>You clicked https://developer.mozilla.org.</pre>

<h3 id="Direct_placeholder_usage">Direct placeholder usage</h3>

<p>It is possible to insert your variables (<code>$1</code>, <code>$2</code>, <code>$3</code>, etc.) directly into the message strings, for example we could rewrite the above <code>"notificationContent"</code> member like this:</p>

<pre class="brush: json">"notificationContent": {
  "message": "You clicked $1.",
  "description": "Tells the user which link they clicked."
}</pre>

<p>This may seem quicker and less complex, but the other way (using <code>"placeholders"</code>) is seen as best practice. This is because having the placeholder name (e.g. <code>"url"</code>) and example helps you to remember what the placeholder is for — a week after you write your code, you'll probably forget what <code>$1</code><code>$8</code> refer to, but you'll be more likely to know what your placeholder names refer to.</p>

<h3 id="Hardcoded_substitution">Hardcoded substitution</h3>

<p>It is also possible to include hardcoded strings in placeholders, so that the same value is used every time, instead of getting the value from a variable in your code. For example:</p>

<pre class="brush: json">"mdn_banner": {
  "message": "For more information on web technologies, go to $MDN$.",
  "description": "Tell the user about MDN",
  "placeholders": {
    "mdn": {
      "content": "https://developer.mozilla.org/"
    }
  }
}</pre>

<p>In this case we are just hardcoding the placeholder content, rather than getting it from a variable value like <code>$1</code>. This can sometimes be useful when your message file is very complex, and you want to split up different values to make the strings more readable in the file, plus then these values could be accessed programmatically.</p>

<p>In addition, you can use such substitutions to specify parts of the string that you don't want to be translated, such as person or business names.</p>

<h2 id="Localized_string_selection">Localized string selection</h2>

<p>Locales can be specified using only a language code, like <code>fr</code> or <code>en</code>, or they may be further qualified with a region code, like <code>en_US</code> or <code>en_GB</code>, which describes a regional variant of the same basic language. When you ask the i18n system for a string, it will select a string using the following algorithm:</p>

<ol>
 <li>if there is a <code>messages.json</code> file for the exact current locale, and it contains the string, return it.</li>
 <li>Otherwise, if the current locale is qualified with a region (e.g. <code>en_US</code>) and there is a <code>messages.json</code> file for the regionless version of that locale (e.g. <code>en</code>), and that file contains the string, return it.</li>
 <li>Otherwise, if there is a <code>messages.json</code> file for the <code>default_locale</code> defined in the <code>manifest.json</code>, and it contains the string, return it.</li>
 <li>Otherwise return an empty string.</li>
</ol>

<p>Take the following example:</p>

<ul class="directory-tree">
 <li>extension-root-directory/
  <ul>
   <li>_locales
    <ul>
     <li>en_GB
      <ul>
       <li>messages.json
        <ul>
         <li><code>{ "colorLocalised": { "message": "colour", "description": "Color." }, ... }</code></li>
        </ul>
       </li>
      </ul>
      en

      <ul>
       <li>messages.json
        <ul>
         <li><code>{ "colorLocalised": { "message": "color", "description": "Color." }, ... }</code></li>
        </ul>
       </li>
      </ul>
     </li>
     <li>fr
      <ul>
       <li>messages.json
        <ul>
         <li><code>{ "colorLocalised": { "message": "<span lang="fr">couleur</span>", "description": "Color." }, ...}</code></li>
        </ul>
       </li>
      </ul>
     </li>
    </ul>
   </li>
  </ul>
 </li>
</ul>

<p>Suppose the <code>default_locale</code> is set to <code>fr</code>, and the browser's current locale is <code>en_GB</code>:</p>

<ul>
 <li>If the extension calls <code>getMessage("colorLocalised")</code>, it will return "colour".</li>
 <li>If "colorLocalised" were not present in <code>en_GB</code>, then <code>getMessage("colorLocalised")</code>, would return "color", not "couleur".</li>
</ul>

<h2 id="Predefined_messages">Predefined messages</h2>

<p>The i18n module provides us with some predefined messages, which we can call in the same way as we saw earlier in {{anch("Calling message strings from manifests and extension CSS")}}. For example:</p>

<pre>__MSG_extensionName__</pre>

<p>Predefined messages use exactly the same syntax, except with <code>@@</code> before the message name, for example</p>

<pre>__MSG_@@ui_locale__</pre>

<p>The following table shows the different available predefined messages:</p>

<table class="standard-table">
 <thead>
  <tr>
   <th scope="col">Message name</th>
   <th scope="col">Description</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td><code>@@extension_id</code></td>
   <td>
    <p>The extension's internally-generated UUID. You might use this string to construct URLs for resources inside the extension. Even unlocalized extensions can use this message.</p>

    <p>You can't use this message in a manifest file.</p>

    <p>Also note that this ID is <em>not</em> the add-on ID returned by {{WebExtAPIRef("runtime.id")}}, and that can be set using the <a href="/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/applications">applications</a> key in manifest.json. It's the generated UUID that appears in the add-on's URL. This means that you can't use this value as the <code>extensionId</code> parameter to {{WebExtAPIRef("runtime.sendMessage()")}}, and can't use it to check against the <code>id</code> property of a {{WebExtAPIRef("runtime.MessageSender")}} object.</p>
   </td>
  </tr>
  <tr>
   <td><code>@@ui_locale</code></td>
   <td>The current locale; you might use this string to construct locale-specific URLs.</td>
  </tr>
  <tr>
   <td><code>@@bidi_dir</code></td>
   <td>The text direction for the current locale, either "ltr" for left-to-right languages such as English or "rtl" for right-to-left languages such as Arabic.</td>
  </tr>
  <tr>
   <td><code>@@bidi_reversed_dir</code></td>
   <td>If the <code>@@bidi_dir</code> is "ltr", then this is "rtl"; otherwise, it's "ltr".</td>
  </tr>
  <tr>
   <td><code>@@bidi_start_edge</code></td>
   <td>If the <code>@@bidi_dir</code> is "ltr", then this is "left"; otherwise, it's "right".</td>
  </tr>
  <tr>
   <td><code>@@bidi_end_edge</code></td>
   <td>If the <code>@@bidi_dir</code> is "ltr", then this is "right"; otherwise, it's "left".</td>
  </tr>
 </tbody>
</table>

<p>Going back to our earlier example, it would make more sense to write it like this:</p>

<pre class="brush: css">header {
  background-image: url(../images/__MSG_@@ui_locale__/header.png);
}</pre>

<p>Now we can just store our local specific images in directories that match the different locales we are supporting — en, de, etc. — which makes a lot more sense.</p>

<p>Let's look at an example of using <code>@@bidi_*</code> messages in a CSS file:</p>

<pre class="brush: css">body {
  direction: __MSG_@@bidi_dir__;
}

div#header {
  margin-bottom: 1.05em;
  overflow: hidden;
  padding-bottom: 1.5em;
  padding-__MSG_@@bidi_start_edge__: 0;
  padding-__MSG_@@bidi_end_edge__: 1.5em;
  position: relative;
}</pre>

<p>For left-to-right languages such as English, the CSS declarations involving the predefined messages above would translate to the following final code lines:</p>

<pre class="brush: css">direction: ltr;
padding-left: 0;
padding-right: 1.5em;
</pre>

<p>For a right-to-left language like Arabic, you'd get:</p>

<pre class="brush: css">direction: rtl;
padding-right: 0;
padding-left: 1.5em;</pre>

<h2 id="Testing_out_your_extension">Testing out your extension</h2>

<p>Starting in Firefox 45, you can install extensions temporarily from disk — see <a href="https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Packaging_and_installation#Loading_from_disk">Loading from disk</a>. Do this, and then try testing out our <a href="https://github.com/mdn/webextensions-examples/tree/master/notify-link-clicks-i18n">notify-link-clicks-i18n</a> extension. Go to one of your favourite websites and click a link to see if a notification appears reporting the URL of the clicked link.</p>

<p>Next, change Firefox's locale to one supported in the extension that you want to test.</p>

<ol>
 <li>Open "about:config" in Firefox, and search for the <code>general.useragent.locale</code> preference.</li>
 <li>Double click on the preference (or press Return/Enter) to select it, enter the language code for the locale you want to test, then click "OK" (or press Return/Enter). For example in our example extension, "en" (English), "de" (German), "nl" (Dutch), and "ja" (Japanese) are supported.</li>
 <li>Search for <code>intl.locale.matchOS</code> and double click the preference so that it is set to <code>false</code>.</li>
 <li>Restart your browser to complete the change.</li>
</ol>

<div class="note">
<p><strong>Note</strong>: This works to change the browser's locale, even if you haven't got the <a href="https://addons.mozilla.org/en-US/firefox/language-tools/">language pack</a> installed for that language. You'll just get the browser UI in your default language if this is the case.</p>
</div>

<ol>
</ol>

<p>Load the extension temporarily from disk again, then test your new locale:</p>

<ul>
 <li>Visit "about:addons" again — you should now see the extension listed, with its icon, plus name and description in the chosen language.</li>
 <li>Test your extension again. In our example, you'd go to another website and click a link, to see if the notification now appears in the chosen language.</li>
</ul>

<p>{{EmbedYouTube("R7--fp5pPGg")}}</p>