Upgrade to Pro — share decks privately, control downloads, hide ads and more …

[BalkanRuby 2019] Getting ready for I18n, Shopify case study

[BalkanRuby 2019] Getting ready for I18n, Shopify case study

A478cd8745e3f45919cb5835b321dc6b?s=128

Edouard Chin

May 17, 2019
Tweet

Transcript

  1. Hello! Здравейте! Bonjour! ¡hola! Balkan Ruby 2019-05-17

  2. Edouard Chin https://github.com/Edouard-chin @DaroudeDudek

  3. None
  4. None
  5. 2,000,000+ LOC 120,000+ Tests Some Ruby Stats Lot of hardcoded

    strings
  6. Main location of hard-coded strings • HTML / ERB (Views)

    • Ruby (Helpers, Controllers) • Javascript
  7. ui-journey-bar::before { content: 'Next: '; }

  8. Getting ready for internationalization, Shopify's case study Balkan Ruby 2019-05-17

  9. 1) Translate your product !"#$%

  10. /“(.*)”/

  11. Detect hard-coded strings visible by the user def my_endpoint if

    params['something'] render(plain: 'Hello World!') end end
  12. Stop the bleeding

  13. None
  14. Static code analysis

  15. <html> <body> <%= link_to('Click here', 'https://example.com') %> Hello World! </body>

    </html>
  16. ui_flag ui_title ui_content_list ui_banner … …

  17. link_to('Click here', 'https://example.com') content_tag(‘div’, ‘Ask me!’)

  18. None
  19. # @example # # bad # {a:3} # # #

    good # {a: 3} class SpaceAfterColon < Cop MSG = 'Space missing after colon.' def on_pair(node) return unless node.colon? colon = node.loc.operator add_offense(…) end # ... end
  20. link_to -> LinkToCop Button -> ButtonCop … …

  21. link_to('My link', 'https://example.com') content_tag('div', 'Hello') Positional arguments

  22. ui_flag(country: 'US', label: 'Make it great again') Keyword arguments

  23. submit(_, data: { confirm: ‘Send!' }, confirm: ‘data’) Options Hash

  24. form_for @book do |f| f.check_box(:paid, {}, 'Yes', 'No') a.check_box('data') end

    Blocks
  25. ui_layout do |row| ui_data_table do |table| table.header do |section| section.row

    do |row| row.header('Hello') end row.header('data') end end end Nested blocks
  26. class UIRadioButtonHelper < Cop include BlockOutputHelperBehavior UI_HELPER_NAMES = { form_for:

    :ui_radio_button } def forbidden_kwarg?(kwarg) %w(label help_text).include?(kwarg) end def argument_check_required?(index) index > 0 end end
  27. form_for :book do |form| form.ui_radio_button( :genre, label: ‘horror', help_text: 'Scary

    books’ ) end
  28. def_node_matcher :ui_block_helper, <<-PATTERN (block { (send nil? #ui_helper_method_names ...) (send

    (lvar _) #ui_helper_method_names) } (args (arg $_)) $(...)) PATTERN RuboCop NodePattern https://rubocop.readthedocs.io/en/latest/node_pattern/
  29. <html> <body> Hello World! </body> </html>

  30. <html> <style> p { background: white; } </style> </html>

  31. ERB-Lint https://github.com/Shopify/erb-lint

  32. <%= link_to('MyLink', _) %> ERB-Lint link_to('MyLink', _)

  33. ERB-Lint https://github.com/Shopify/erb-lint

  34. None
  35. RuboCop autocorrection

  36. link_to(‘My Link', _) link_to(I18n.t('path.to.file.my_l'), _) en: path: to: file: my_l:

    'My Link'
  37. en: path: to: file: my_l: 'My Link %{here}’ link_to("My Link

    #{here}", _) link_to(I18n.t('path.to.file.my_l', here: here), _)
  38. <%= I18n.t(:translation_key_html) %> <%= t(:translation_key_html) %>

  39. bin/rubocop -only I18n .

  40. None
  41. Ruby I18n or gettext ?

  42. I18n.t('some.namespace.key') en: some: namespace: key: 'Hello!' "Hello!" Ruby-i18n

  43. "Hello!" _("Hello!") gettext .po/.mo files to store translations

  44. https://github.com/grosser/fast_gettext/blob/ master/Readme.md#comparison

  45. " #

  46. None
  47. Pseudolocalization

  48. Ŵḛḛḅḥṓṓṓṓḳ ṡααṽḛḛḍ ṡṵṵͼͼḛḛṡṡϝṵṵḽḽẏẏ Webhook saved successfully

  49. egg topper Eierschalensollbruchstellenv erursacher

  50. None
  51. https://github.com/Shopify/ pseudolocalization

  52. MySQL character set

  53. “".bytesize => 4

  54. Unicode Basic Multilingual Plane

  55. Utf8mb4

  56. None
  57. Expose your app to UTF8 characters

  58. Translation vs Localization

  59. None
  60. " % May 3, 2019 2019-05-03 03/05/2019 YYYY-MM-DD (ISO 8601)

    DD/MM/YYYY
  61. ) 2019-03-05 2019-05-03 03-05-2019

  62. 1PM VS 13:00

  63. " %

  64. Edouard Chin
 902-210 Gloucester Street
 
 Ottawa ON K2P 2K4

  65. Edouard Chin
 
 Ottawa ON K2P 2K4

  66. Edouard Chin
 
 Ottawa K2P 2K4 Canada

  67. "Miss #{last_name} #{first_name}
 #{apartment} #{street} #{postal_code} #{province}"

  68. … name: 'United States' week_start_day: 'sunday' format: edit: "{firstName}{lastName}_{company}_{address1} _{address2}_{city}_{country}{province}{zip}_{phone}"

    show: "{firstName} {lastName}_{company}_{address1} _{address2}_{city} {province} {zip}_{country}_{phone}"
  69. const address = { company: 'Shopify', firstName: '恵⼦子', lastName: '⽥田中',

    address1: '⼋八重洲1-5-3', address2: '', city: '⽬目黒区', province: 'JP-13', zip: '100-8994', country: 'JP', phone: '', }; const addressFormatter = new AddressFormatter('ja'); await addressFormatter.format(address); /* => ⽇日本 〒100-8994東京都⽬目黒区⼋八重洲1-5-3 Shopify ⽥田中恵⼦子様 */
  70. https://www.npmjs.com/package/@shopify/address

  71. Edouard Chin https://github.com/Edouard-chin @DaroudeDudek