Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
[BalkanRuby 2019] Getting ready for I18n, Shopi...
Search
Edouard Chin
May 17, 2019
Programming
290
2
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
[BalkanRuby 2019] Getting ready for I18n, Shopify case study
Edouard Chin
May 17, 2019
More Decks by Edouard Chin
See All by Edouard Chin
[Ruby Kaigi 2026] Faster Bundler, Happier Developers
edouardchin
0
30
Journey of a complex gem upgrade
edouardchin
0
490
Upgrading Rails at scale. RailsConf 2018
edouardchin
4
1.1k
Other Decks in Programming
See All in Programming
モバイル交通系ICへのチャージ実例から考える、クロスプラットフォーム開発におけるiOS実機テスト設計とCI運用
yusuga
1
480
Starting & Sustaining Code-Based E2E Testing for Non-Coding QA Teams( #jasstniigata )
teyamagu
PRO
1
350
Family mrubyの進捗
kishima
1
120
Webの地図
yosuke_furukawa
PRO
6
4.6k
AI活用は、個人から組織へ|マルチプレイヤーエージェントハーネス「QM」の社内活用事例 / AI use is moving from individuals to orgs
rkaga
1
110
GKE で Pod の見方を変えたら、スケールアウト時の挙動を真に捉えられた話
stkk
0
130
Streamlitで実現する自然言語データアプリ開発
ayumu_yamaguchi
1
290
一人だけ、Kiroが静止する日
hideg
0
110
大喜利で理解するLLM as a Judge / Understanding LLM-as-a-Judge through Ogiri
rockname
0
120
cdk deploy JawsSonic #MARATHONしながらAWSリソースをデプロイしてみよう
akihisaikeda
2
140
AWS DevOps Agentで インシデント対応をAIに任せたい
honmarkhunt
7
3k
スマートフォンでモールス信号を送受信する 〜スマートフォンのLEDとカメラで作る光通信の設計と実装〜
atsuki_seo
0
130
Featured
See All Featured
Game over? The fight for quality and originality in the time of robots
wayneb77
1
280
A brief & incomplete history of UX Design for the World Wide Web: 1989–2019
jct
2
510
Art, The Web, and Tiny UX
lynnandtonic
304
22k
Leo the Paperboy
mayatellez
10
2.3k
HTML-Aware ERB: The Path to Reactive Rendering @ RubyCon 2026, Rimini, Italy
marcoroth
5
670
CoffeeScript is Beautiful & I Never Want to Write Plain JavaScript Again
sstephenson
162
16k
End of SEO as We Know It (SMX Advanced Version)
ipullrank
3
4.4k
ピンチをチャンスに:未来をつくるプロダクトロードマップ #pmconf2020
aki_iinuma
128
56k
Building Adaptive Systems
keathley
44
3.2k
コードの90%をAIが書く世界で何が待っているのか / What awaits us in a world where 90% of the code is written by AI
rkaga
63
46k
10 Git Anti Patterns You Should be Aware of
lemiorhan
PRO
659
62k
Money Talks: Using Revenue to Get Sh*t Done
nikkihalliwell
0
500
Transcript
Hello! Здравейте! Bonjour! ¡hola! Balkan Ruby 2019-05-17
Edouard Chin https://github.com/Edouard-chin @DaroudeDudek
None
None
2,000,000+ LOC 120,000+ Tests Some Ruby Stats Lot of hardcoded
strings
Main location of hard-coded strings • HTML / ERB (Views)
• Ruby (Helpers, Controllers) • Javascript
ui-journey-bar::before { content: 'Next: '; }
Getting ready for internationalization, Shopify's case study Balkan Ruby 2019-05-17
1) Translate your product !"#$%
/“(.*)”/
Detect hard-coded strings visible by the user def my_endpoint if
params['something'] render(plain: 'Hello World!') end end
Stop the bleeding
None
Static code analysis
<html> <body> <%= link_to('Click here', 'https://example.com') %> Hello World! </body>
</html>
ui_flag ui_title ui_content_list ui_banner … …
link_to('Click here', 'https://example.com') content_tag(‘div’, ‘Ask me!’)
None
# @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
link_to -> LinkToCop Button -> ButtonCop … …
link_to('My link', 'https://example.com') content_tag('div', 'Hello') Positional arguments
ui_flag(country: 'US', label: 'Make it great again') Keyword arguments
submit(_, data: { confirm: ‘Send!' }, confirm: ‘data’) Options Hash
form_for @book do |f| f.check_box(:paid, {}, 'Yes', 'No') a.check_box('data') end
Blocks
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
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
form_for :book do |form| form.ui_radio_button( :genre, label: ‘horror', help_text: 'Scary
books’ ) end
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/
<html> <body> Hello World! </body> </html>
<html> <style> p { background: white; } </style> </html>
ERB-Lint https://github.com/Shopify/erb-lint
<%= link_to('MyLink', _) %> ERB-Lint link_to('MyLink', _)
ERB-Lint https://github.com/Shopify/erb-lint
None
RuboCop autocorrection
link_to(‘My Link', _) link_to(I18n.t('path.to.file.my_l'), _) en: path: to: file: my_l:
'My Link'
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), _)
<%= I18n.t(:translation_key_html) %> <%= t(:translation_key_html) %>
bin/rubocop -only I18n .
None
Ruby I18n or gettext ?
I18n.t('some.namespace.key') en: some: namespace: key: 'Hello!' "Hello!" Ruby-i18n
"Hello!" _("Hello!") gettext .po/.mo files to store translations
https://github.com/grosser/fast_gettext/blob/ master/Readme.md#comparison
" #
None
Pseudolocalization
Ŵḛḛḅḥṓṓṓṓḳ ṡααṽḛḛḍ ṡṵṵͼͼḛḛṡṡϝṵṵḽḽẏẏ Webhook saved successfully
egg topper Eierschalensollbruchstellenv erursacher
None
https://github.com/Shopify/ pseudolocalization
MySQL character set
“".bytesize => 4
Unicode Basic Multilingual Plane
Utf8mb4
None
Expose your app to UTF8 characters
Translation vs Localization
None
" % May 3, 2019 2019-05-03 03/05/2019 YYYY-MM-DD (ISO 8601)
DD/MM/YYYY
) 2019-03-05 2019-05-03 03-05-2019
1PM VS 13:00
" %
Edouard Chin 902-210 Gloucester Street Ottawa ON K2P 2K4
Edouard Chin Ottawa ON K2P 2K4
Edouard Chin Ottawa K2P 2K4 Canada
"Miss #{last_name} #{first_name} #{apartment} #{street} #{postal_code} #{province}"
… 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}"
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 ⽥田中恵⼦子様 */
https://www.npmjs.com/package/@shopify/address
Edouard Chin https://github.com/Edouard-chin @DaroudeDudek