review.rst (12645B)
1 .. role:: bash(code) 2 :language: bash 3 4 .. role:: js(code) 5 :language: javascript 6 7 =============================== 8 Guidelines for Fluent Reviewers 9 =============================== 10 11 This document is intended as a guideline for developers and reviewers when 12 working with FTL (Fluent) files. As such, it’s not meant to replace the 13 `existing extensive documentation`__ about Fluent. 14 15 __ ./tutorial.html 16 17 `Herald`_ is used to set the group `fluent-reviewers`_ as blocking reviewer for 18 any patch modifying FTL files committed to Phabricator. The person from this 19 group performing the review will have to manually set other reviewers as 20 blocking, if the original developer didn’t originally do it. 21 22 23 .. hint:: 24 25 In case of doubt, you should always reach out to the l10n team for 26 clarifications. 27 28 29 Message Identifiers 30 =================== 31 32 While in Fluent it’s possible to use both lowercase and uppercase characters in 33 message identifiers, the naming convention in Gecko is to use lowercase and 34 hyphens (*kebab-case*), avoiding CamelCase and underscores. For example, 35 :js:`allow-button` should be preferred to :js:`allow_button` or 36 :js:`allowButton`, unless there are technically constraints – like identifiers 37 generated at run-time from external sources – that make this impractical. 38 39 When importing multiple FTL files, all messages share the same scope in the 40 Fluent bundle. For that reason, it’s suggested to add scope to the message 41 identifier itself: using :js:`cancel` as an identifier increases the chances of 42 having a conflict, :js:`save-dialog-cancel-button` would make it less likely. 43 44 Message identifiers are also used as the ultimate fall back in case of run-time 45 errors. Having a descriptive message ID would make such fall back more useful 46 for the user. 47 48 Avoid Generating Identifiers at Run-time 49 ---------------------------------------- 50 51 Generating identifiers at run-time relying on variables should be avoided unless 52 the number of strings to manage is particularly large. 53 54 Consider for example these Fluent strings and associated JavaScript code: 55 56 .. code-block:: fluent 57 58 bar-tooltip-social = Social Media Trackers 59 bar-tooltip-cookie = Cross-Site Tracking Cookies 60 bar-tooltip-tracker = Tracking Content 61 bar-tooltip-fingerprinter = Fingerprinters 62 bar-tooltip-cryptominer = Cryptominers 63 64 65 .. code-block:: javascript 66 67 document.l10n.setAttributes(div, `bar-tooltip-${type}`); 68 69 70 From a developer perspective, this code is clean and intuitive, but it creates 71 two distinct problems: 72 73 - Updating one of the strings in the group requires either updating all of them, 74 forcing retranslation, or updating the code logic to support exceptions. 75 - Finding unused strings in the codebase becomes impossible, as the message 76 identifier is never spelled out. 77 78 Potentially, the code above can also lead to generating a missing identifiers, 79 while that's harder with an explicit list. 80 81 An explicit map or object is the preferred approach to deal with these cases: 82 83 84 .. code-block:: javascript 85 86 const messageIDs = { 87 social: "bar-tooltip-social", 88 cookie: "bar-tooltip-cookie", 89 tracker: "bar-tooltip-tracker", 90 cryptominer: "bar-tooltip-cryptominer", 91 fingerprinter: "bar-tooltip-fingerprinter", 92 }; 93 document.l10n.setAttributes(div, messageIDs[type]); 94 95 96 Comments 97 ======== 98 99 When a message includes placeables (variables), there should always be a 100 comment explaining the format of the variable, and what kind of content it will 101 be replaced with. This is the format suggested for such comments: 102 103 104 .. code-block:: fluent 105 106 # This string is used on a new line below the add-on name 107 # Variables: 108 # $name (String) - Add-on author name 109 cfr-doorhanger-extension-author = by { $name } 110 111 112 By default, a comment is bound to the message immediately following it. Fluent 113 supports both `file-level and group-level comments`__. Be aware that a group 114 comment will apply to all messages following that comment until the end of the 115 file. If that shouldn’t be the case, you’ll need to “reset” the group comment, 116 by adding an empty one (:js:`##`), or moving the section of messages at the end 117 of the file. 118 119 __ https://projectfluent.org/fluent/guide/comments.html 120 121 Comments are fundamental for localizers, since they don’t see the file as a 122 whole, or changes as a fragment of a larger patch. Their work happens on a 123 message at a time, and the context is only provided by comments. 124 125 License headers are standalone comments, that is, a single :js:`#` as prefix, 126 and the comment is followed by at least one empty line. 127 128 Changes to Existing Messages 129 ============================ 130 131 You must update the message identifier if: 132 133 - The meaning of the sentence has changed. 134 - You’re changing the morphology of the message, by adding or removing attributes. 135 136 Messages are identified in the entire localization toolchain by their ID. For 137 this reason, there’s no need to change attribute names. 138 139 If your changes are relevant only for English — for example, to correct a 140 typographical error or to make letter case consistent — then there is generally 141 no need to update the message identifier. 142 143 There is a grey area between needing a new ID or not. In some cases, it will be 144 necessary to look at all the existing translations to determine if a new ID 145 would be beneficial. You should always reach out to the l10n team in case of 146 doubt. 147 148 Changing the message ID will invalidate the existing translation, the new 149 message will be reported as missing in all tools, and localizers will have to 150 retranslate it. This is the only reliable method to ensure that localizers 151 update existing localizations, and run-time stop using obsolete translations. 152 153 You must also update all instances where that message identifier is used in the 154 source code, including localization comments. 155 156 Non-text Elements in Messages 157 ============================= 158 159 When a message includes non text-elements – like anchors or images – make sure 160 that they have a :js:`data-l10n-name` associated to them. Additional 161 attributes, like the URL for an anchor or CSS classes, should not be exposed 162 for localization in the FTL file. More details can be found in `this page`__ 163 dedicated to DOM overlays. 164 165 __ https://github.com/projectfluent/fluent.js/wiki/DOM-Overlays#text-level-elements 166 167 This information is not relevant if your code is using `fluent-react`_, where 168 DOM overlays `work differently`__. 169 170 __ https://github.com/projectfluent/fluent.js/wiki/React-Overlays 171 172 Message References 173 ================== 174 175 Consider the following example: 176 177 178 .. code-block:: fluent 179 180 newtab-search-box-search-the-web-text = Search the Web 181 newtab-search-box-search-the-web-input = 182 .placeholder = { newtab-search-box-search-the-web-text } 183 .title = { newtab-search-box-search-the-web-text } 184 185 186 This might seem to reduce the work for localizers, but it actually doesn’t 187 help: 188 189 - A change to the referenced message (:js:`newtab-search-box-search-the-web-text`) 190 would require a new ID also for all messages referencing it. 191 - Translation memory can help with matching text, not with message references. 192 193 On the other hand, this approach is helpful if, for example, you want to 194 reference another element of the UI in your message: 195 196 197 .. code-block:: fluent 198 199 help-button = Help 200 help-explanation = Click the { help-button } to access support 201 202 203 This enforces consistency and, if :js:`help-button` changes, all other messages 204 will need to be updated anyway. 205 206 Terms 207 ===== 208 209 Fluent supports a specific type of message, called `term`_. Terms are similar 210 to regular messages but they can only be used as references in other messages. 211 They are best used to define vocabulary and glossary items which can be used 212 consistently across the localization of the entire product. 213 214 Terms are typically used for brand names, like :js:`Firefox` or :js:`Mozilla`: 215 it allows to have them in one easily identifiable place, and raise warnings 216 when a localization is not using them. It helps enforcing consistency and brand 217 protection. If you simply need to reference a message from another message, you 218 don’t need a term: cross references between messages are allowed, but they 219 should not be abused, as already described. 220 221 Variants and plurals 222 ==================== 223 224 Consider the following example: 225 226 227 .. code-block:: fluent 228 229 items-selected = 230 { $num -> 231 [0] Select items. 232 [one] One item selected. 233 *[other] { $num } items selected. 234 } 235 236 237 In this example, there’s no guarantee that all localizations will have this 238 variant covered, since variants are private by design. The correct approach for 239 the example would be to have a separate message for the :js:`0` case: 240 241 242 .. code-block:: fluent 243 244 # Separate messages which serve different purposes. 245 items-select = Select items 246 # The default variant works for all values of the selector. 247 items-selected = 248 { $num -> 249 [one] One item selected. 250 *[other] { $num } items selected. 251 } 252 253 254 As a rule of thumb: 255 256 - Use variants only if the default variant makes sense for all possible values 257 of the selector. 258 - The code shouldn’t depend on the availability of a specific variant. 259 260 More examples about selector and variant abuses can be found in `this wiki`__. 261 262 __ https://github.com/projectfluent/fluent/wiki/Good-Practices-for-Developers#prefer-separate-messages-over-variants-for-ui-logic 263 264 In general, also avoid putting a selector in the middle of a sentence, like in 265 the example below: 266 267 268 .. code-block:: fluent 269 270 items-selected = 271 { $num -> 272 [one] One item. 273 *[other] { $num } items 274 } selected. 275 276 277 :js:`1` should only be used in case you want to cover the literal number. If 278 it’s a standard plural, you should use the :js:`one` category for singular. 279 Also make sure to always pass the variable to these messages as a number, not 280 as a string. 281 282 Access Keys 283 =========== 284 285 The following is a simple potential example of an access key: 286 287 .. code-block:: fluent 288 289 example-menu-item = 290 .label = Menu Item 291 .accesskey = M 292 293 Access keys are used in menus in order to help provide easy keyboard shortcut access. They 294 are useful for both power users, and for users who have accessibility needs. It is 295 helpful to first read the `Access keys`__ guide in the Windows Developer documentation, 296 as it outlines the best practices for Windows applications. 297 298 __ https://docs.microsoft.com/en-us/windows/uwp/design/input/access-keys 299 300 There are some differences between operating systems. Linux mostly follows the same 301 practices as Windows. However, macOS in general does not have good support for accesskeys, 302 especially in menus. 303 304 When choosing an access key, it's important that it's unique relative to the current level 305 of UI. It's preferable to avoid letters with descending parts, such as :code:`g`, 306 :code:`j`, :code:`p`, and :code:`q` as these will not be underlined nicely in Windows or 307 Linux. Other problematic characters are ones which are narrow, such as :code:`l`, 308 :code:`i` and :code:`I`. The underline may not be as visible as other letters in 309 sans-serif fonts. 310 311 Linter 312 ====== 313 314 :bash:`mach lint` includes a :ref:`l10n linter <L10n>`, called :bash:`moz-l10n-lint`. It 315 can be run locally by developers but also runs on Treeherder: in the Build 316 Status section of the diff on Phabricator, open the Treeherder Jobs link and 317 look for the :js:`l1nt` job. 318 319 Besides displaying errors and warnings due to syntax errors, it’s particularly 320 important because it also checks for message changes without new IDs, and 321 conflicts with the firefox-l10n-source repository used to ship localized versions of 322 Firefox. 323 324 325 .. warning:: 326 327 Currently, there’s an `issue`__ preventing warnings to be displayed in 328 Phabricator. Checks can be run locally using :bash:`./mach lint -l l10n -W`. 329 330 __ https://github.com/mozilla/code-review/issues/32 331 332 333 Migrating Strings From Legacy or Fluent Files 334 ============================================= 335 336 If a patch is moving legacy strings (.properties, .DTD) to Fluent, it should 337 also include a recipe to migrate existing strings to FTL messages. The same is 338 applicable if a patch moves existing Fluent messages to a different file, or 339 changes the morphology of existing messages without actual changes to the 340 content. 341 342 Documentation on how to write and test migration recipes is available in `this 343 page`__. 344 345 __ ./fluent_migrations.html 346 347 348 .. _Herald: https://phabricator.services.mozilla.com/herald/ 349 .. _fluent-reviewers: https://phabricator.services.mozilla.com/tag/fluent-reviewers/ 350 .. _fluent-react: https://github.com/projectfluent/fluent.js/wiki/React-Bindings 351 .. _term: https://projectfluent.org/fluent/guide/terms.html