Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
[BalkanRuby 2019] Getting ready for I18n, Shopi...
Search
Sponsored
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
Edouard Chin
May 17, 2019
Programming
280
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
13
Journey of a complex gem upgrade
edouardchin
0
470
Upgrading Rails at scale. RailsConf 2018
edouardchin
4
1.1k
Other Decks in Programming
See All in Programming
ADKを使って簡単にAIエージェントを作ってみよう
k1mu21
0
250
運用エージェントは "作る" から "育てる" へ - 記憶と自己進化の3層設計パターン / self-evolving-agents-three-layer-agent-design
gawa
12
3.6k
正しくソフトウェアを作る、前提を疑うための認知の視点 / doubt-premise
minodriven
20
6.4k
LLMによるContent Moderationの本番運用の裏側と品質担保への挑戦
suikabar
2
530
キャリア迷子上等 ─ "ない道"は自分で作ればいい
16bitidol
3
1.9k
Hunting Vulnerabilities in Symfony with LLMs
vinceamstoutz
0
540
スマートグラスで並列バイブコーディング
hyshu
0
120
Webフレームワークの ベンチマークについて
yusukebe
0
160
AI時代の仕事技芸論 — ソフトウェア開発で「遊ぶように働く」職人的熟達のすすめ
kuranuki
1
650
Skillsは効率化、Agentsは"自分の拡張"——Builder時代のエージェント編成(CC Night 2026)
wemra
1
120
AIチームを指揮するOSS「TAKT」活用術 / How to Use “TAKT,” an OSS Tool for Orchestrating AI Teams
nrslib
6
880
Agentic UI
manfredsteyer
PRO
0
130
Featured
See All Featured
Scaling GitHub
holman
464
140k
Building AI with AI
inesmontani
PRO
1
1.1k
The Organizational Zoo: Understanding Human Behavior Agility Through Metaphoric Constructive Conversations (based on the works of Arthur Shelley, Ph.D)
kimpetersen
PRO
0
360
Documentation Writing (for coders)
carmenintech
77
5.4k
Into the Great Unknown - MozCon
thekraken
41
2.6k
Typedesign – Prime Four
hannesfritz
42
3.1k
Understanding Cognitive Biases in Performance Measurement
bluesmoon
32
2.9k
Navigating the moral maze — ethical principles for Al-driven product design
skipperchong
2
390
Responsive Adventures: Dirty Tricks From The Dark Corners of Front-End
smashingmag
254
22k
Connecting the Dots Between Site Speed, User Experience & Your Business [WebExpo 2025]
tammyeverts
11
940
Amusing Abliteration
ianozsvald
1
200
Breaking role norms: Why Content Design is so much more than writing copy - Taylor Woolridge
uxyall
0
310
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