mirror of
https://github.com/discourse/discourse.git
synced 2026-08-04 10:39:43 +08:00
Related: https://meta.discourse.org/t/multi-language-preferences-for-displaying-localized-content/381116 Allow anon and logged in users to set more than one language that they understand, when content localization is enabled. ### User preferences In user preferences, we now include a Content Languages section when the site has Content Localization enabled. ### Topic menu In topics where there are any languages that's not the user's language, the option will appear to set their preferred content languages. This appears for anon and logged in users, but anon users will be asked to logged in to set the languages. __________ These preferences apply only to topic titles and posts. They do not affect navigational features like categories, tags, or sidebar content. The per-post option to show the original remains independent of the user's preferences. This PR temporarily retains `show_original_content` as a compatibility field computed from `automatically_translate`, keeping cached clients and rolling deployments safe. A follow-up PR will remove the legacy API and frontend compatibility code, then retire the old database column through the staged column-removal process.
5.9 KiB
Vendored
5.9 KiB
Vendored
| name | description |
|---|---|
| discourse-content-localization | 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_localizationmust receive records with:localizationspreloaded.
Core Model Pattern
- Add
include Localizableto the source model and a nullablelocalecolumn with limit 20. - Add
<Model>Localizationwithinclude LocaleMatchable,belongs_to :model, alocalestring limit 20, translated fields with source-model length limits, and a unique(model_id, locale)index. - Use
get_localizationfor exact, normalized, and optional default-locale fallback behavior. Do not reimplement locale fallback in serializers. - Add
ContentLocalization.show_translated_<model>?usingSiteSetting.content_localization_enabled, sourcelocale.present?, and!model.in_user_locale?. Preserve existing model-specific presentation gates; do not copy them to unrelated model types without explicit product intent. - Preload in every list, boot, site JSON, admin list, and controller path that serializes localized fields. Use
includes(:localizations)orincludes(:localizations, children: :localizations)as needed. - Serializer methods should return
localization.field.presence || original_fieldand 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
localizationsarrays 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
localepresent. 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. - When changing model-specific presentation gates, test authenticated and anonymous behavior and verify unrelated localizable model types remain unaffected.
- 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-aifor core models and skips source-locale/self translations. - Turning off the feature via
SiteSetting.content_localization_enableddisables all localization behavior, including backfill and detection