Files
social-app/docs/plans/2026-03-27-l10n-error-code-rollout.md
T

9.3 KiB

L10n Cleanup + Stable Error Code + Frontend Text Migration Implementation Plan

For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

Goal: Remove redundant l10n wrappers, introduce backend stable/mappable error codes for HTTP contracts, and continue frontend hardcoded-text localization migration to zh/en with default zh.

Architecture: Keep Flutter UI localization in lib/l10n as single source of truth, minimize cross-layer localization coupling, and use backend RFC7807 + code/params as machine-readable contract. Frontend maps code -> l10n key for user-facing messages while preserving fallback behavior.

Tech Stack: Flutter gen-l10n, FastAPI (RFC7807), Pydantic models, Dio client error mapping, existing AGENTS/rules constraints.


Task 1: Freeze and baseline current behavior

Files:

  • Modify: none (read-only task)
  • Verify: apps/lib/**, backend/src/**, docs/protocols/**

Step 1: Snapshot app localization status

Run: python scripts/count_cn_literals.py (or equivalent one-off command) Expected: baseline count and top files with Chinese literals.

Step 2: Snapshot backend detail-string usage

Run: python scripts/count_http_detail_usage.py (or equivalent one-off command) Expected: per-file count of HTTPException(detail=...) hotspots.

Step 3: Capture baseline checks

Run: cd apps && flutter analyze Expected: no new errors, only known existing warnings/infos.

Run: cd backend && uv run pytest -q (or targeted fast suite if full too slow) Expected: baseline pass/fail recorded for regression comparison.


Task 2: Refactor l10n structure to remove redundant wrapper responsibilities

Files:

  • Modify: apps/lib/app/app.dart
  • Modify: UI files currently importing apps/lib/core/l10n/l10n.dart
  • Delete/Modify: apps/lib/core/l10n/l10n.dart (depending on final outcome)
  • Verify: generated files under apps/lib/l10n/

Step 1: Define target rule

Rule:

  • UI layer uses context.l10n.
  • Non-UI layer does not depend on ad-hoc global locale state.
  • If non-UI needs localization, pass already-localized strings in from caller or inject mapper service.

Step 2: Write failing/static guard checks

Add temporary grep checks:

  • Fail when new code adds L10n.current in feature/presentation.
  • Fail when core/l10n/l10n.dart is reintroduced for convenience access.

Step 3: Replace call sites incrementally

For each file:

  1. Replace L10n.current.xxx with context.l10n.xxx where BuildContext exists.
  2. For cubit/service/form validators, inject message providers or pass messages from UI.
  3. Keep behavior unchanged.

Step 4: Remove locale global mutation path

In app.dart:

  • Remove L10n.setLocale(...) style side effects.
  • Keep Flutter-native delegates + supportedLocales + default locale logic.

Step 5: Delete redundant wrapper (if no remaining valid use case)

Delete apps/lib/core/l10n/l10n.dart only after all references are removed and non-UI strategy is in place.

Step 6: Verify

Run: cd apps && flutter gen-l10n && flutter analyze Expected: no errors.


Task 3: Define backend stable error code contract (RFC7807 extension)

Files:

  • Modify: backend/src/core/http/response.py
  • Modify: backend/src/app.py
  • Create: backend/src/core/http/errors.py
  • Modify: docs/protocols/agent/api-endpoints.md
  • (optional) Create: docs/protocols/common/error-contract.md

Step 1: Extend problem details schema

Add fields:

  • code: str | None
  • params: dict[str, str | int | float | bool] | None

Preserve RFC7807 required fields and media type.

Step 2: Introduce unified domain error type

In core/http/errors.py, create exception class carrying:

  • http status
  • stable error code (UPPER_SNAKE_CASE)
  • optional params
  • optional internal detail

Step 3: Wire global exception handlers

In app.py:

  • Convert domain exceptions to problem+json with code and params.
  • Keep fallback for unknown exceptions.

Step 4: Define code naming convention

Examples:

  • AUTH_INVALID_TOKEN
  • AUTH_TOKEN_EXPIRED
  • SCHEDULE_ITEM_NOT_FOUND
  • TODO_TITLE_REQUIRED
  • FRIENDSHIP_ALREADY_EXISTS

Task 4: Migrate backend hotspots from free-text detail to stable codes

Files:

  • Modify: backend/src/v1/friendships/service.py
  • Modify: backend/src/v1/schedule_items/service.py
  • Modify: backend/src/v1/todo/service.py
  • Modify: backend/src/v1/agent/service.py
  • Modify: backend/src/v1/users/service.py
  • Modify: backend/src/v1/memories/service.py
  • Modify: backend/src/v1/auth/gateway.py
  • Modify: backend/src/v1/agent/router.py
  • Modify: other files with HTTPException(detail=...)

Step 1: Prioritize by impact

Order:

  1. Auth
  2. Agent
  3. Todo/Schedule/Friendships
  4. Users/Memories

Step 2: Replace throw sites

For each detail-based throw:

  1. Map to stable code.
  2. Keep detail only as optional server diagnostic text.
  3. Add params when useful (e.g., max size, field, limit).

Step 3: Preserve backwards compatibility window

During transition:

  • Keep detail present.
  • Add code/params immediately.
  • Frontend prefers code, falls back to existing behavior.

Task 5: Frontend network error mapping to l10n via backend code

Files:

  • Modify: apps/lib/core/network/api_exception.dart
  • Create: apps/lib/core/network/error_code_mapper.dart
  • Modify: call sites currently displaying raw backend detail
  • Modify: apps/lib/l10n/app_zh.arb, apps/lib/l10n/app_en.arb

Step 1: Parse code and params from response payload

In ApiException.fromDioError:

  • Read RFC7807 + extension fields.
  • Keep statusCode fallback behavior.

Step 2: Map code -> localized message

Implement central mapper:

  • Input: code/status/params
  • Output: localized user-facing string key resolution

Step 3: Fallback strategy

Priority:

  1. known code mapping
  2. status-based generic mapping
  3. safe generic fallback (request failed localized)

Step 4: Replace UI direct usage of raw server detail

Audit and update places where e.toString() or backend detail is shown directly.


Task 6: Continue frontend hardcoded text migration (remaining files)

Files:

  • Modify: apps/lib/features/settings/presentation/screens/*.dart (remaining high-count files)
  • Modify: apps/lib/features/calendar/presentation/screens/*.dart
  • Modify: apps/lib/features/calendar/presentation/widgets/*.dart
  • Modify: apps/lib/l10n/app_zh.arb, apps/lib/l10n/app_en.arb

Step 1: Batch by screen group

Batch A: settings deep pages Batch B: calendar pages Batch C: shared/home leftovers

Step 2: Migrate with key hygiene

Rules:

  • key names are feature-prefixed and stable
  • dynamic texts use placeholders, not string concatenation
  • avoid duplicate semantic keys

Step 3: After each batch, run verification

Run:

  • cd apps && flutter gen-l10n
  • cd apps && flutter analyze

Track remaining hardcoded-literal count after each batch.


Task 7: Protocol docs and test updates

Files:

  • Modify: docs/protocols/agent/api-endpoints.md
  • Modify/Create: docs/protocols/common/error-contract.md
  • Modify: backend integration/unit tests asserting only detail
  • Modify: frontend tests around error display/mapping

Step 1: Document new error response shape

Example:

{
  "type": "about:blank",
  "title": "Unprocessable Entity",
  "status": 422,
  "detail": "Validation failed",
  "code": "TODO_TITLE_REQUIRED",
  "params": {"field": "title"},
  "instance": "/api/v1/todo"
}

Step 2: Update tests to assert codes first

Replace brittle text assertions with:

  • status
  • code
  • optional params

Task 8: Final verification gate

Files:

  • Verify only

Step 1: Apps verification

Run:

  • cd apps && flutter gen-l10n
  • cd apps && flutter analyze

Step 2: Backend verification

Run:

  • cd backend && uv run ruff check .
  • cd backend && uv run basedpyright
  • cd backend && uv run pytest -q

Step 3: Cross-contract smoke

Run targeted API checks ensuring error payload includes code for representative modules.


Task 9: Rollout and compatibility

Files:

  • Modify: release notes/changelog if used

Step 1: Progressive rollout strategy

  • Phase 1: backend emits both detail + code
  • Phase 2: frontend consumes code with fallback
  • Phase 3: clean up legacy detail-dependent branches

Step 2: Monitoring

  • Log unknown/unmapped error codes on frontend
  • Add backend metrics for top emitted error codes

Risks and mitigations

  • Risk: non-UI code loses localization access after wrapper removal
    • Mitigation: inject messages from UI/service boundary; avoid static locale globals.
  • Risk: backend code migration is broad (many detail throws)
    • Mitigation: staged module-by-module migration + compatibility window.
  • Risk: front/back mismatch in error code enum
    • Mitigation: shared protocol doc + CI checks for known code list.

Done criteria

  • apps/lib/core/l10n/l10n.dart removed or reduced to zero-overlap minimal utility with explicit justification.
  • Backend RFC7807 responses include stable code (and optional params) on migrated endpoints.
  • Frontend maps known codes to zh/en l10n; raw detail is no longer primary user-facing string.
  • Hardcoded visible Chinese text count in apps/lib reduced to agreed threshold or zero for targeted modules.
  • Docs and tests updated accordingly.