Skip to content

Add Japanese locale, chosen from shopkeepers.locale or Accept-Language - #150

Merged
dadachi merged 4 commits into
mainfrom
feature/japanese-locale
Oct 10, 2026
Merged

dadachi merged 4 commits into
mainfrom
feature/japanese-locale

Conversation

@dadachi

@dadachi dadachi commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Stored locale for signed-in shopkeepers. shopkeepers.locale (the column already existed, default en) now decides the language of every signed-in request, over Accept-Language. Sign-up stores the locale the app sends (ja-JP → ja; unsupported or missing falls back to Accept-Language, so sign-up never fails over it). PATCH /shopkeeper_auth changes it (en or ja, else 422). Confirmation and reset mails use the shopkeeper's locale; an invitation uses the invitee's if they have an account, otherwise the inviter's.
  • Locale per request for everything before authentication (sign-in, sign-up, 401s, rate limits, HTML pages). New SetLocale concern (in ApplicationController and NonApiApplicationController) picks English or Japanese from Accept-Language: quality values, header order for ties, regional tags (ja-JP), q=0 refusals; anything else gets English. It wraps process_action instead of around_action, because rescue_from handlers (404/400/422 JSON) and rate_limit 429s run outside the callback chain and came back in English otherwise.
  • Japanese translations, written by hand rather than adding rails-i18n / devise-i18n:
    • config/locales/ja.yml: every app message plus model and attribute names, so validation errors read e.g. 店舗名を入力してください.
    • rails.ja.yml: Active Model / Active Record validation messages.
    • devise.ja.yml: Devise's messages (e.g. the 401 for a missing token). devise_token_auth already ships ja.yml; app overrides mirror en.yml.
  • Hard-coded English moved to locale keys: the 404/500 HTML pages, ErrorsController JSON messages, the sample item tag a new shop gets, and the mail greetings. English output is unchanged.
  • Other mails queued during a request keep its locale (Active Job stores I18n.locale).
  • Fix: PATCH /shopkeeper_auth returned 422 unless email was sent, because devise_token_auth validated the params before name, time_zone (and now locale) were permitted. configure_permitted_parameters is now a prepend_before_action.
  • Docs: OpenAPI description and a new AGENTS.md "Internationalization" section.

Translation review

A second agent reviewed the Japanese against the English and the composed messages. Fixed in a follow-up commit, each with a test that failed first (test/i18n/japanese_messages_test.rb, two more in locale_test.rb):

  • Multiple errors were joined as A, B, and C; support.array now joins them with 、.
  • An unknown current_platform printed only は一覧にありません; it now gets the spam-protection message.
  • An existing member accepting an invitation was told the user "already exists"; now ユーザーはすでにこの組織のメンバーです.
  • An invalid reset link read Reset password tokenは不正な値です.
  • The reset password form still showed (8 characters minimum) and an English placeholder.
  • Wording: は正しくありません (invalid), はすでに使われています (taken), を選択してください for roles, 「」 quotes and サインイン in devise_token_auth messages, a clearer email format error, 個人用の組織.

English changes: an unknown current_platform now returns "Your input data is wrong." (same as blank), and a duplicate member returns "User is already a member of this organization" instead of "User has already been taken". Two existing model tests were updated for this.

Not changed: the invitation mail names app UI labels (「組織」タブ, 「参加」ボタン). Whether they match the clients' Japanese labels needs checking against the iOS/Android apps.

Client impact

To use the stored locale, the apps should send locale at sign-up and offer it on the profile screen (PATCH /shopkeeper_auth with locale). Until they do, new shopkeepers get their Accept-Language language stored at sign-up, and existing ones stay en.

iOS sends Accept-Language from the device's preferred languages automatically, so Japanese devices get Japanese with no client change. Android's OkHttp sends no Accept-Language by default; those clients keep getting English until they add the header. Clients that compare error_message strings would break for Japanese devices; the template clients only display them.

Tests

  • Stored locale (locale_test.rb, shopkeeper_test.rb, registrations_controller_test.rb): it wins over Accept-Language both ways; a failed sign-in still uses the header; sign-up with ja, ja-JP, none, and unsupported fr; profile update to ja and rejection of fr (message in the stored language); confirmation, reset, and both invitation mails; model validation; a time_zone-only profile update. Each was seen failing by removing the matching line of the implementation.
  • Dev server: sign-up with locale: ja-JP stored ja; a signed-in 404 with Accept-Language: en answered 見つかりませんでした。; PATCH locale=fr gave 言語は一覧から選択してください; after PATCH locale=en, a 404 with Accept-Language: ja answered Not found.
  • test/integration/locale_test.rb (18 tests): header parsing cases, no locale leak between requests, Japanese validation message, Devise 401, rescue_from 404, unknown-route 404 JSON and HTML page, 429, invitation mail, sample item tag.
  • test/i18n/locale_files_test.rb: fails on an English key with no Japanese translation (app keys plus the Rails, Devise and devise_token_auth scopes) or a Japanese-only %{variable}.
  • Each guard was seen failing: dropping an app key or a Rails key, adding a Japanese-only variable, ignoring quality order, accepting q=0, not stripping the region, and not switching the locale all turned the suite red.
  • bin/rails test: 583 runs, 0 failures (was 568 before the stored-locale commit)
  • bin/ci passed: 568 runs, 0 failures; RuboCop, erb_lint, Brakeman, bundler-audit clean.
  • On a running dev server with Accept-Language: ja: sign-in error, blank shop name, missing token and the reset password page were Japanese; without the header, English. A password reset mail queued in Japanese stored locale: ja and was delivered with the subject パスワード再設定のご案内.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WyUzNEPWyhjjWrBcSaXSW4

dadachi and others added 4 commits October 10, 2026 21:43
Responses, validation errors, HTML pages and emails are now in English or
Japanese. SetLocale picks the locale per request from Accept-Language
(quality values, regional tags such as ja-JP), falling back to English. It
wraps process_action rather than using around_action, because rescue_from
handlers and rate_limit responses run outside the callback chain. Mails
queued during a request keep its locale.

Japanese lives in config/locales/ja.yml (app messages and attribute names),
rails.ja.yml (Active Model/Record validation messages) and devise.ja.yml,
written by hand instead of adding rails-i18n or devise-i18n. The error
pages, ErrorsController messages, the sample item tag and the mail
greetings moved from hard-coded English to locale keys; English output is
unchanged.

test/i18n/locale_files_test.rb fails on any English key without a Japanese
translation (app keys plus the Rails, Devise and devise_token_auth scopes)
or a Japanese-only %{variable}.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WyUzNEPWyhjjWrBcSaXSW4
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WyUzNEPWyhjjWrBcSaXSW4
Broken output:
- Several errors were joined as "A, B, and C": add support.array (、).
- An unknown current_platform printed only "は一覧にありません", since the
  attribute prints just its message; it now gets the spam-protection text
  (also in English, which printed only "is not included in the list").
- Accepting an invitation as an existing member said the user "already
  exists"; it now says they are already a member (also in English).
- An invalid reset link read "Reset password tokenは不正な値です".
- The reset password form still had "(8 characters minimum)" and an
  English placeholder.

Wording: "は正しくありません" for invalid, "はすでに使われています" for taken,
"を選択してください" for a missing role, 「」 instead of '...' in
devise_token_auth messages, サインイン instead of the gem's ログイン, a
clearer email format error, 個人用の組織, a full-width colon after the
invitation's instructions heading, and a thank-you greeting in the
confirmation mail.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WyUzNEPWyhjjWrBcSaXSW4
Sign-up stores the locale the app sends (regional tags reduced to the
language; unsupported or missing falls back to Accept-Language), and
PATCH /shopkeeper_auth can change it (en or ja, else 422). Signed-in
requests answer in that language; requests before authentication keep
using Accept-Language. Confirmation, reset and invitation mails use the
recipient's locale, an invitation to a new email the inviter's.

Also fix PATCH /shopkeeper_auth returning 422 unless email was sent:
devise_token_auth validated the params before name, time_zone and
locale were permitted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WyUzNEPWyhjjWrBcSaXSW4
@dadachi dadachi changed the title Add Japanese locale, chosen from Accept-Language Add Japanese locale, chosen from shopkeepers.locale or Accept-Language Oct 10, 2026
@dadachi
dadachi merged commit 6ee37f3 into main Oct 10, 2026
3 checks passed
@dadachi
dadachi deleted the feature/japanese-locale branch October 10, 2026 21:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant