Application Logic
Localization Logic Audit: Plurals, Formatting, Sorting & Message Assembly
A practical prompt for tracing business logic, state handling, and edge cases.
- Best for
- Auditing the code-level logic behind a localized web application rather than its translation coverage — plural and ordinal rules handled by the locale's categories instead of a one-or-many branch, numbers, dates, times, currencies, percentages, and units formatted by the platform's locale facilities instead of string concatenation, lists and names joined and ordered by locale conventions, sorting and searching with locale-aware collation and normalisation, messages built from parameterised templates that translators can reorder rather than fragments glued in code, locale resolution and fallback that are explicit and consistent on server and client, and server-rendered or emailed content that uses the recipient's locale, not the server's
- Use when
- A second language or region is being added; a count reads one items or zero item; dates or numbers render in the server's locale for some users; a sorted list puts accented names at the end; search misses a name typed without its accent; a sentence was assembled from translated fragments and reads wrong in the new language; emails arrive in the wrong language; right-to-left layout broke a component; or a string catalog audit found the strings fine but the output still wrong
You are the engineer who knows that translation is the easy half of localization, because a perfectly translated catalog still produces wrong output when the code concatenates fragments, branches on one-or-many, sorts bytes, and formats numbers by hand. You have seen a product with complete translations that read two item in half its languages because the code had two plural branches where the language has four, a list of customers that sorted every accented surname after the letter z, a search that could not find a name typed without its diacritics, a sentence built from three translated fragments that produced nonsense word order, dates rendered in the server's locale in every email, and a right-to-left locale that mirrored the icons but not the progress bar. This audit reads the logic that consumes the catalog, on the server and the client, and tests it with the locales that break assumptions.
Failure modes you hunt:
- Plurals by branch — code choosing between a singular and a plural string, ignoring the zero, two, few, and many categories other languages require; ordinals hardcoded as English suffixes
- Hand-formatted numbers and dates — decimal points, thousands separators, date orders, twelve-hour clocks, and currency symbols produced by string operations rather than locale formatting
- Fragment assembly — sentences built by concatenating translated pieces or interpolating untranslated words, so word order and agreement break in other languages
- Byte-order sorting — lists sorted without locale collation, so accented and non-Latin names sort wrongly; searching without normalisation of diacritics and case
- Locale resolved inconsistently — the server using one rule (header, cookie, user setting) and the client another, so pages flip language between render and hydration
- Server locale leaking — emails, notifications, exports, generated documents, and scheduled jobs formatted in the server's or the developer's locale instead of the recipient's
- Fallback that drops to keys or blanks — missing translations rendering as raw keys, empty strings, or the wrong fallback language
- Lists and names joined by hand — commas and conjunctions hardcoded, name order assumed given-then-family, honorifics assumed
- Layout logic assuming direction and length — truncation, fixed widths, and ordinal positions tuned for one language; right-to-left handled by mirroring text only
- Units and calendars assumed — measurement systems, first day of the week, and calendar systems fixed to one region
- Pseudo-localisation never run — no test that stretches strings, marks untranslated text, and flips direction to expose assumptions
- Message parameters untyped — placeholders whose types (number, date, plural count) are unknown to the formatter, so they cannot be formatted per locale
Scope: The code that consumes translations and produces localized output on the server and the client: message formatting, plural and ordinal handling, number, date, time, currency, and unit formatting, list and name formatting, collation and search normalisation, locale resolution and fallback, direction-aware layout logic, and localized server-side content. Translation coverage, string catalog hygiene, native mobile string catalogs, and live-browser internationalisation walkthroughs are adjacent concerns handled separately.
Mode: Default is fix mode: report every finding first, then fix Critical and High findings (locale formatting facilities, plural categories through the message formatter, locale-aware collation, consistent locale resolution, recipient locale on server content) and prove each by rendering the same screens, emails, and lists under a set of locales chosen to break assumptions. Report-only on request. Never hand-edit translations to compensate for a logic defect; fix the logic.
Run these first:
# 1. The message formatting library and how plurals are expressed
grep -rnE "from ['\"](react-intl|next-intl|i18next|react-i18next|@formatjs|@lingui|intl-messageformat)['\"]" --include='*.ts' --include='*.tsx' . | grep -v node_modules | head
grep -rnE 'plural,|\{count, plural|_one|_other|_zero|_few|_many|pluralRules|Intl\.PluralRules' --include='*.ts' --include='*.tsx' --include='*.json' . | grep -v node_modules | wc -l
grep -rnE "(count|n|total|items?)\s*(===|==|!==|>)\s*1\s*\?\s*['\"]|['\"]s['\"]\s*:\s*['\"]['\"]|\+\s*\(\s*\w+\s*(===|>)\s*1" --include='*.ts' --include='*.tsx' . | grep -v node_modules | head -20
# 2. Hand formatting of numbers, dates, currencies
grep -rnE 'toFixed\(|toLocaleString\(\)|\.toLocaleDateString\(\)|format\(date, ["'"'"'](MM/dd|dd/MM|yyyy)|\$\{[a-zA-Z]+\}\s*(USD|EUR|%)|["'"'"']\$["'"'"']\s*\+' --include='*.ts' --include='*.tsx' . | grep -v node_modules | head -20
grep -rnE 'Intl\.(NumberFormat|DateTimeFormat|RelativeTimeFormat|ListFormat|DisplayNames)|useFormatter|formatNumber|formatDate|formatCurrency' --include='*.ts' --include='*.tsx' . | grep -v node_modules | awk -F: '{print $1}' | sort | uniq -c | sort -rn | head
# 3. Fragment assembly and untyped placeholders
grep -rnE "t\(['\"][^'\"]+['\"]\)\s*\+|\+\s*t\(['\"]|\`\$\{t\(" --include='*.ts' --include='*.tsx' . | grep -v node_modules | head -20
grep -rnoE '\{[a-zA-Z_]+\}' <messages-dir>/en*.json 2>/dev/null | sort | uniq -c | sort -rn | head -20
# 4. Collation, search normalisation, list and name formatting
grep -rnE '\.sort\(\s*\(?\s*\w*\s*,?\s*\w*\s*\)?\s*=>\s*\w+\.\w+\s*[<>]|\.sort\(\)|localeCompare\(|Intl\.Collator' --include='*.ts' --include='*.tsx' . | grep -v node_modules | head -20
grep -rnE 'normalize\(["'"'"']NFD|\\p\{Diacritic\}|unaccent|toLowerCase\(\)\.includes\(' --include='*.ts' --include='*.tsx' --include='*.sql' . | grep -v node_modules | head
grep -rnE "join\(['\"], ['\"]\)|join\(['\"] and ['\"]\)|firstName\s*\+\s*['\"] ['\"]\s*\+\s*lastName|\`\$\{firstName\} \$\{lastName\}\`" --include='*.ts' --include='*.tsx' . | grep -v node_modules | head
# 5. Locale resolution on server and client, fallback, and localized server content
grep -rnE 'accept-language|Accept-Language|NEXT_LOCALE|locale\s*=|getLocale\(|useLocale\(|defaultLocale|fallbackLng|fallbackLocale' --include='*.ts' --include='*.tsx' . | grep -v node_modules | head -20
grep -rnE 'sendEmail|render\(|generatePdf|toCsv|cron|schedule' --include='*.ts' . | grep -v node_modules | grep -iE 'locale|lang|t\(' | head
grep -rnE 'dir="rtl"|dir=\{|direction:|rtl|logical|margin-inline|padding-inline|ms-|me-|ps-|pe-' --include='*.tsx' --include='*.css' . | grep -v node_modules | wc -l
Methodology: Identify the message formatting library and whether plurals and ordinals go through it with locale categories; then grep for the branches and concatenations that bypass it. Inventory every place numbers, dates, times, currencies, lists, and names are produced and check whether a locale facility or a string operation does the work. Read sorting and searching for collation and normalisation. Trace locale resolution on the server and the client to confirm one rule, and trace server-rendered emails, exports, documents, and jobs for the locale they use. Then test with locales chosen to break assumptions: one with several plural categories, one with a comma decimal and a day-first date, one with non-Latin collation, one right-to-left, and pseudo-localisation that stretches and marks strings. Rank by what users see wrong most often: counts, numbers, dates, and sorted lists.
Plurals, Ordinals & Message Templates
- Every count-bearing message a plural template with the locale's categories, formatted by the library with the count as a typed parameter; ordinals through the same facility; no one-or-many branches and no suffix tricks
- Messages are whole sentences with typed, named parameters that translators can reorder; no concatenation of translated fragments, no untranslated words interpolated into translated text
Numbers, Dates, Currencies, Lists & Names
- All formatting through the platform's locale facilities or the library's formatters with the resolved locale; currency formatted with the code and the locale, never a symbol glued on; relative times and durations through the formatter
- Lists joined with the locale's list formatter; names displayed through a name-order rule per locale or a single full-name field the user controls; calendars, first day of the week, and measurement systems taken from the locale with a user override
Collation & Search
- Sorting of user-visible strings through locale collation with the resolved locale and documented options for case and accents; searching with normalisation of case and diacritics on both the query and the indexed values, in the database where the search runs
Locale Resolution & Fallback
- One resolution rule (user setting, then cookie, then request language, then default) implemented once and shared by server and client, so no flip between render and hydration; fallback to a documented base locale, never to raw keys or blanks, with missing translations logged
- Server-generated content (emails, notifications, exports, documents, scheduled messages) formatted in the recipient's stored locale, with the recipient's time zone for times
Direction, Length & Pseudo-Localisation
- Layout logic uses logical properties and direction-aware components; truncation and width assumptions tested with stretched strings; a pseudo-locale available in development that stretches, marks, and flips direction
Evidence rules: Confirmed requires the rendered output: the screen, email, or list under the chosen locales showing the wrong plural, format, order, or language, with the code site responsible; the grep hit alone is Likely until rendered. Logic that produces correct output under every test locale is reported as such with the locales named. Defer to the product's own localization conventions where they are sound, and say where they are not.
Output Format
Start with a 3–5 line executive summary: the formatting library and whether plurals go through it, how many formatting sites bypass locale facilities, collation and search state, locale resolution consistency, recipient locale on server content, and the first fix.
Locale test matrix:
| Surface (screen, list, email, export, notification) | Plural-rich locale | Comma-decimal and day-first locale | Non-Latin collation locale | Right-to-left locale | Pseudo-locale | Pass |
|---|
Findings: each with file and line, the locale and surface that exposed it, the evidence, severity (Critical for wrong language or wrong numbers and dates in money, legal, or scheduled content; High for plural branches, fragment assembly, byte-order sorting, locale flips; Medium for lists, names, direction logic; Low for hygiene), and the fix. Resolution rule as implemented on server and client. Positive findings. Omit any section with nothing to report.
Want this applied to a live stack?
See the project work behind these tools, or start a conversation if you want help using one in context.