0
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-08-06 01:10:43 +08:00
discourse/.skills/discourse-content-localization/SKILL.md
Natalie Tay 32d7754466
DEV: Add skill for localizing models (#41823)
As part of https://github.com/discourse/discourse/pull/41795, add a
skill for concerns when it comes to localizing models for presentation.

Important bits covered in checklist.

/299871/7
2026-07-20 15:54:12 +08:00

60 lines
5.6 KiB
Markdown
Vendored

---
name: discourse-content-localization
description: Use when adding, modifying, or reviewing Discourse content localization for core models or plugin models. Covers Localizable models, localization tables, source locale handling, hot-route serializer preloading, fallback behavior, manual editing UI, discourse-ai backfill/detection boundaries, plugin dependency constraints, and tests.
---
# Discourse Content Localization
Use this before localizing dynamic Discourse content such as Sidebar sections, Groups, Docs, Events, or other models listed in content-localization status trackers.
## First Decisions
- Core models: put the data model, serializers, Guardian checks, API behavior, and edit UI in core. Put automatic AI detection/backfill/localizer jobs in `plugins/discourse-ai`.
- Plugin models: do not create plugin-to-plugin dependencies casually. Create proper plugin APIs and plugin outlets / hooks.
- Manual editing localizations UI is part of calling a model “localizable”. Flag it upfront. Pick either an existing edit page/modal or a new route before implementing the backend.
- Hot-route safety is mandatory, e.g. on any discovery routes. Any serializer that may call `get_localization` must receive records with `:localizations` preloaded.
## Core Model Pattern
- Add `include Localizable` to the source model and a nullable `locale` column with limit 20.
- Add `<Model>Localization` with `include LocaleMatchable`, `belongs_to :model`, a `locale` string limit 20, translated fields with source-model length limits, and a unique `(model_id, locale)` index.
- Use `get_localization` for exact, normalized, and optional default-locale fallback behavior. Do not reimplement locale fallback in serializers.
- Add `ContentLocalization.show_translated_<model>?` using `SiteSetting.content_localization_enabled`, source `locale.present?`, and `!model.in_user_locale?`. Match Post/Topic only when show-original behavior is relevant.
- Preload in every list, boot, site JSON, admin list, and controller path that serializes localized fields. Use `includes(:localizations)` or `includes(:localizations, children: :localizations)` as needed.
- Serializer methods should return `localization.field.presence || original_field` and expose localization rows only to users who can edit them.
## Editing UI And API
- Reuse an existing edit surface when it is the natural place users already manage the content. Otherwise add a dedicated route.
- Only users passing a Guardian localization check can edit localizations. For admin-owned/global content, prefer admin-only checks unless product intent says otherwise.
- Server-side params must accept localization rows and enforce the same max lengths as the model. UI validation is not sufficient.
- Existing source fields should remain editable independently from translations. Destroying a source record must destroy its localizations.
- Edit/settings surfaces must load source/default values, not localized display values. For example, a Japanese admin opening the "Support" category settings should see source name "Support", not its localized name "サポート".
- Preserve existing API response shape where possible; add `localizations` arrays only for authorized editors.
## discourse-ai Backfill Pattern
- Add model-specific `Candidate`, `Localizer`, regular localize job, and scheduled backfill jobs in discourse-ai only.
- Candidate scopes should include only content intended for automatic translation. For global/admin content, avoid user-private rows unless explicitly requested.
- Localize only records with source `locale` present. Delete localizations whose locale matches the source locale. Skip target locales that normalize to the source locale.
- an "en" post does not need "en" translation nor "en_GB" translation
- Respect `DiscourseAi::Translation.enabled?`, `backfill_enabled?`, configured agents, hourly rate, credits, and error logging patterns used by existing tag/category jobs.
## Tests
- Model specs: validations, uniqueness, dependency cleanup, fallback via `get_localization`.
- Serializer/request specs: localized value when enabled, original value when disabled/source locale missing/same locale, editor-only `localizations`, and no N+1 on hot routes.
- UI/system or QUnit specs: authorized editor can view/add/remove localization rows on the chosen edit surface; unauthorized users cannot.
- discourse-ai specs: detector text, localizer writes, scheduled job enqueue/credit gates, regular job limits/skips/errors, and candidate completion if the model appears in translation-progress UI.
## Checklist
- [ ] Localization model has `include LocaleMatchable`, field length limits matching the source, and a unique `(model_id, locale)` index.
- [ ] Serializers preload localizations on hot/list path and fall back to source values without N+1s.
- [ ] Regular presentation payloads do not include localization rows for admins or non-admins.
- [ ] Admins can edit localizations for the model via a UI
- [ ] Edit/localization payloads give authorized admins default values plus localization rows. A Japanese admin opening the "Support" category settings should see source name "Support", not its localized name "サポート".
- [ ] Manual editing UI, API params, and tests cover adding, updating, and removing localization rows. Do not accept empty strings for fields, use frontend validations.
- [ ] Localization editing is authorized server-side, with admin-only checks for admin-owned/global content.
- [ ] discourse-ai detection/backfill stays in `plugins/discourse-ai` for core models and skips source-locale/self translations.
- [ ] Turning off the feature via `SiteSetting.content_localization_enabled` disables all localization behavior, including backfill and detection