i18n for systems analyst interviews
Contents:
Why i18n shows up on SA interviews
You apply for a systems analyst role at Airbnb, Uber, or Stripe and the recruiter slips it in: "Walk me through how you'd add a third language to our checkout." It sounds soft, but it's a stress test for whether you understand the separation between code and content, the locale abstraction, and the edge cases that break naive translation — plurals, RTL, currency symbols, date formats. Get it wrong and the conversation slides into "you've never shipped a real product."
The reason multinationals push this question is operational. Adding a market is not a marketing decision, it's a backlog. The engineering cost of i18n scales with how early you wired it in, and analysts are the people who write the requirements that determine that cost. Hard-coded strings, ambiguous date formats, and English-only plural rules are the three sins that get caught in code review and the three sins interviewers love to probe.
Load-bearing trick: never separate translation from formatting. They share the same locale abstraction. If your spec talks about "translating the page" without mentioning numbers, dates, plurals, and currency, you'll fail the follow-up.
This guide covers the seven topics that come up in roughly that order: the i18n/l10n distinction, locale identifiers, translation pipelines, ICU MessageFormat, formatting APIs, RTL handling, and the pitfalls that separate juniors from people who've actually launched a non-English market.
i18n vs l10n vs g11n
The numbers in the acronyms count letters. Internationalization has 18 letters between the i and n; localization has 10; globalization has 11. They are three different jobs and on interviews you're expected to know which team owns which.
| Term | What it means | Who owns it | Output |
|---|---|---|---|
| i18n | Engineering work to make the product capable of supporting many locales | Engineering, SA | Code with no hard-coded strings, locale-aware formatting |
| l10n | Translating and culturally adapting content for a specific locale | Content, translators, l10n vendors | A new translation file, adapted images, legal copy |
| g11n | Strategic decision which markets to enter, business case, pricing, GTM | Product, business development | Roadmap of locales to ship |
A common interview gotcha is to ask "if we want to launch in Japan tomorrow, what changes do you make?" The right answer separates i18n work (already done if the system was built correctly) from l10n work (translations, JPY formatting, length adjustments) from g11n decisions (pricing in JPY, payment methods, legal terms). Don't conflate them — recruiters score the distinction.
Locale: language + region + script
A locale identifier follows BCP 47: language-Script-REGION-variant. The script tag is optional but matters for Chinese and Serbian.
en-US — English, United States
en-GB — English, United Kingdom
pt-BR — Portuguese, Brazil
pt-PT — Portuguese, Portugal
zh-Hans-CN — Chinese, Simplified, China
zh-Hant-TW — Chinese, Traditional, Taiwan
sr-Latn-RS — Serbian written in Latin
sr-Cyrl-RS — Serbian written in Cyrillic
ar-EG — Arabic, EgyptThe locale drives five orthogonal axes: translation lookup, date and time format, number and currency format, plural rules, and writing direction. Junior candidates often assume en is enough; senior ones know en-US and en-GB format dates differently and use different spelling conventions ("color" vs "colour").
Gotcha: detecting locale from the Accept-Language header alone is unreliable. Users in São Paulo connect via VPNs, browsers ship with locales different from user preference, and corporate laptops are often locked to en-US. The right pattern is "propose a locale, let the user override, persist the choice".
Translation files and pipelines
Translation files come in a small set of formats. The dominant ones are JSON for JavaScript ecosystems, .po/gettext for Python and PHP, .xliff for enterprise pipelines, and .arb for Flutter.
{
"checkout.button.pay": "Pay {amount}",
"checkout.error.declined": "Card declined. Try another card.",
"checkout.cart.items": "{count, plural, one {# item} other {# items}}"
}The pipeline interview question is "how do translations get from a translator to production?" The standard answer is a four-stage flow.
Extract: a tool scans the codebase for translatable strings and produces a source file (typically en-US.json as the source of truth). Send: the source file is uploaded to a Translation Management System — Phrase, Lokalise, Crowdin, or Smartling. Translators fill in the target locales. Pull: CI pulls translated files back into the repo, ideally on every release. Render: the runtime loads the file matching the user's locale.
The two failure modes you should be ready to name are missing keys (a new feature shipped without translations — the UI falls back to the key or to the source locale) and stale keys (the developer changed the English copy but the translators kept the old translation). Mature teams enforce both with CI checks: fail the build if coverage drops below 95% for active locales.
Plurals and interpolation with ICU
Plurals are the single most common i18n bug on production. English has two forms — one and other (1 item, 2 items). Russian has three — one, few, many. Arabic has six — zero, one, two, few, many, other. Welsh has six. Polish is famously irregular. CLDR documents the rules per language and the ICU MessageFormat is how you encode them in a single string.
{count, plural,
=0 {No items}
one {# item}
few {# items}
many {# items}
other {# items}
}The # is replaced with the formatted number. =0 is an exact-match case, evaluated before the plural category. Putting No items under =0 and # item under one gives you the "You have no notifications" / "You have 1 notification" / "You have 5 notifications" pattern users expect.
Interpolation — substituting variables into a translation — uses similar bracket syntax:
"welcome.greeting": "Hello, {name}!"
"order.confirmation": "Your order #{orderId} will arrive by {deliveryDate, date, medium}."A frequent interview trap is concatenation. Translators must see the whole sentence in context. Splitting "Welcome, " + name + "!" into three pieces produces grammatically broken output in any language with case markers or word order different from English. Always pass the variable into the translation, never the other way around.
Dates, numbers, currencies
Formatting is where naive translation breaks. The same number, date, or price renders differently in every locale.
| Locale | Date | Number | Currency |
|---|---|---|---|
en-US |
5/22/2026 | 1,234.56 | $1,234.56 |
en-GB |
22/05/2026 | 1,234.56 | £1,234.56 |
de-DE |
22.05.2026 | 1.234,56 | 1.234,56 € |
fr-FR |
22/05/2026 | 1 234,56 | 1 234,56 € |
ja-JP |
2026/05/22 | 1,234.56 | ¥1,235 |
pt-BR |
22/05/2026 | 1.234,56 | R$ 1.234,56 |
The interview answer is "never hand-roll formatting — use the platform's i18n API." In JavaScript that's Intl.NumberFormat, Intl.DateTimeFormat, Intl.PluralRules, and Intl.RelativeTimeFormat. In Java it's java.text and ICU4J. In Python it's the babel package or the pyicu bindings. All of them read from CLDR, the Unicode Common Locale Data Repository.
new Intl.NumberFormat('de-DE', {
style: 'currency',
currency: 'EUR'
}).format(1234.56)
// "1.234,56 €"Sanity check: the currency code is independent of the locale. A German user paying in dollars should see $1,234.56 formatted by de-DE rules — minus sign first, comma decimal, space, code. Never assume locale fixes currency. Stripe and Airbnb both ask this question explicitly.
For timestamps the gold rule is store in UTC, render in user's timezone, use ISO 8601 for any API contract. Sending "2026-05-22T14:30:00Z" to the backend and letting the client format it is the only pattern that survives DST transitions and travel across timezones.
Drill systems-analyst interview questions like this every day — NAILDD is launching with i18n, API design, and SQL problems modeled on real screens at Stripe, Airbnb, and Uber.
RTL languages and bidirectional UI
Arabic, Hebrew, Persian, and Urdu are written right to left. Mirroring is not a CSS afterthought — it changes the entire visual grammar of the interface.
html[dir="rtl"] .layout {
direction: rtl;
text-align: right;
}
/* Logical properties make mirroring automatic */
.card {
margin-inline-start: 16px; /* not margin-left */
padding-inline-end: 24px; /* not padding-right */
}What flips and what stays: text direction flips, layout order flips, navigation arrows flip, sliders and progress bars flip. What stays: numbers, dates, phone numbers, code blocks, brand logos with directional intent, media controls (play/pause), and graphs (time still flows left-to-right by mathematical convention in most cases — check with the local team).
The hardest case is bidirectional text — English brand names or numbers embedded in Arabic sentences. Unicode handles this via the Bidi Algorithm, but you'll see edge cases where punctuation lands on the wrong side. Test on real content, not Lorem Ipsum.
Common pitfalls
The trap that costs the most engineering time is hard-coded strings in the source language. Every English string baked into a template, a tooltip, an error message, or a backend response must be extracted before launch. Teams that defer this past MVP end up doing a months-long migration. The fix is to enforce a lint rule from day one: any user-visible literal must come from a translation key.
The second trap is assuming English fits the layout. German words are on average 30% longer, Finnish compounds run to 50 characters, and Chinese characters take less horizontal space but more vertical density. Fixed-width buttons, table columns, and badges break the moment you switch locales. Always design with the +30% rule in mind — every container should gracefully handle text 30% longer than the English original — and pseudo-localize early to catch overflow.
The third trap is handling numbers and IDs as locale-sensitive when they aren't. Order IDs, SKUs, currency codes (USD, EUR), and HTTP status codes are not translatable. Running them through Intl.NumberFormat turns 100000 into 100,000 and breaks downstream search. The discipline is to distinguish between content (translatable) and identifiers (verbatim) in the data model itself, not at render time.
The fourth trap is right-to-left as a CSS afterthought. Bolting direction: rtl onto a finished interface produces icons pointing the wrong way, sliders that count backward, and animations that move the wrong direction. RTL must be in the design system from day one, with logical CSS properties (margin-inline-start, padding-block-end) instead of physical ones (margin-left, padding-bottom).
The fifth trap is forgetting plural categories that don't exist in English. Engineers who only speak English routinely write if (count === 1) "1 item" else count + " items", which produces wrong grammar in Russian, Polish, Welsh, and Arabic. The fix is to never write your own plural logic — always use ICU MessageFormat or the platform's PluralRules API.
Related reading
- Acceptance criteria with Given/When/Then for SA
- How to calculate currency conversion in SQL
- Datetime in Python for data analysts
FAQ
What's the difference between i18n and l10n in one sentence?
i18n is the engineering work that makes a product capable of supporting many locales — no hard-coded strings, locale-aware formatting, RTL support. l10n is the content work for a specific locale — actual translations, culturally adapted images, region-specific legal copy. You do i18n once for the codebase; you do l10n once per new market.
How do I detect a user's locale?
The pragmatic order is: explicit user setting (saved in the profile or a cookie) first, Accept-Language HTTP header second, IP-based geolocation as a weak hint third. The last one is the least reliable because of VPNs and mobile roaming. The pattern that performs best in user research is to propose a locale based on these signals and surface an obvious switcher so the user can override and persist.
Should I store dates in UTC or local time?
Always store in UTC on the backend, always format in the user's timezone on the frontend. The API contract should use ISO 8601 with a timezone designator (2026-05-22T14:30:00Z or 2026-05-22T14:30:00+02:00). The only exception is floating events that have no real timezone — like a daily reminder at 9 AM regardless of where the user travels — which you store as local time plus a timezone identifier.
How do I handle pluralization for languages I don't speak?
Don't write the plural logic yourself. Use ICU MessageFormat, pass the count as a variable, and let translators choose the categories that apply to their language. The translation tool will surface the required categories per language (Russian gets one/few/many/other, Arabic gets all six). Your job is to write the message with placeholders; their job is to fill in the variants.
What's pseudo-localization and why use it?
Pseudo-localization replaces every translatable string with a mangled version of the same string — "Hello" becomes "Ĥéľľô [!! 30% longer !!]". Running the app in pseudo-locale catches three classes of bugs at once: hard-coded strings (which don't get pseudo'd and show up as plain English), layout overflow (the +30% padding triggers truncation early), and missing extraction (any string still in English wasn't routed through the translation pipeline). Most modern i18n libraries can generate it automatically.
Is i18n needed if we only ship in English?
Yes, even for English-only products. Date and number formatting still differs between en-US and en-GB, currencies differ between markets, and the moment a single non-US customer signs up you'll wish formatting was locale-aware. The cost of adding i18n later is roughly 10x the cost of building it in from the start. Spend the week up front; thank yourself in two years.