Internationalization support - #1131
Open
ltfish wants to merge 15 commits into
Open
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4c96ebc348
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
Dojo, module, challenge, and resource names/descriptions can now carry translations, either inline via a `translations:` block or through a parallel `i18n/<lang>/` tree in the dojo repo. Translations live in the existing JSONB `data` column, so no schema migration is needed, and a missing translation falls back to the source text. The navbar gains a language switcher; the choice is kept in a cookie and, for logged-in users, in a new `dojo_user_preferences` table.
Adds docs/i18n.md covering the i18n/ tree, the resource locator rules, and how a language is chosen, plus an integration test dojo exercising translations at the dojo, module, resource, header, and challenge levels.
Search now matches the name and description subfields of a translation rather than the whole JSON blob, so a query cannot match on JSON key names. The language cookie inherits the request's Secure flag, and the request language is resolved lazily so requests that render no content skip the preference lookup.
An imported challenge or module that carried any translation of its own discarded every translation it would otherwise inherit from its source -- so a consolidated dojo translating just a challenge name lost the source's translated description. Translations now merge per language and per field, with the local value winning.
A repeated resource name -- common in consolidated dojos, where several source modules each contribute a "Resources" block -- silently sent every translation to the first match. Ambiguous locators now fail and point the author at `index`.
Jinja imports do not carry the render context, so the switcher macro could not see the languages, current_language, language_switch_next, and Session variables its own template supplies -- every page raised UndefinedError on 'languages'. Also returns a safe default from language_switch_next() outside a request context, where there is no request to read the current path from.
The switcher's label lives in a display:block span, so bootstrap's inline-block .dropdown-toggle::after caret wrapped onto a second row. Suppress that caret and put an explicit fa-caret-down inside the label span, matching how the navbar's Hide Navbar item already renders its caret.
base.html includes the navbar, so an undefined `languages` raised out of the switcher on every template that extends it -- error.html included, meaning the error page could not render either. That is the symptom when templates are swapped under a still-running plugin, since dojo_plugin and dojo_theme are both bind-mounted but load() only runs at process start. Skip the switcher when the context processor has not run instead of raising.
The switcher wrote each user's language to a new `dojo_user_preferences` table, which made the feature require a schema change for the sake of one string. Drop the table and the model: the `dojo_language` cookie already carries the choice, and `Accept-Language` covers a browser that has never used the switcher. The tradeoff is that the choice no longer follows an account to another browser. That is worth a table this feature does not otherwise need, and the fallback chain still lands non-English visitors in their own language on a first visit.
…slate
The loader passed `translations=... or {}` for every dojo, module, challenge, and
resource, and the import paths wrote a merged blob unconditionally, so every row grew a
`translations` key -- usually an empty one -- on the next dojo update. That is a lot of
churn for a feature most dojos do not use.
Write the key only when there is a translation to store, so an untranslated dojo's `data`
is exactly what a build without i18n produces. A dojo update that removes translations
still clears the ones already stored.
Add `zh-CN`, `zh-TW`, and `it` to the switcher, along with their translations of the `Challenges` header the platform synthesizes for modules that use the `challenges:` shorthand -- otherwise that one heading stays English inside an otherwise translated module. Chinese needs a nudge that Korean did not: browsers name it by script (`zh-Hans-CN`) or generically (`zh`) as often as by region, and truncating those from the right never reaches `zh-CN` or `zh-TW`. LANGUAGE_ALIASES maps what browsers send onto what is offered. It only steers the choice of language, not how a translation is looked up once the language is chosen, so a dojo still files its translations under the offered tags.
conftest raises a collection-time UsageError for any test whose file is missing from TEST_FILE_TIERS, so the whole file failed with "Test tier is not configured" rather than running. The tier map arrived with the test revamp on master after this branch was written, and a rebase has no way to notice that a new test file needs an entry in it. Semantic, alongside test_dojos.py and test_module_resources.py: these drive dojo pages and their JSON over HTTP.
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
…itcher The switcher's `next` field was `request.path` plus the request's own query string, and the navbar renders on every page -- error pages included. The API blueprint's exception handler logs and re-raises, so an abort() inside a flask-restx resource falls through to Flask's HTML error page rather than a JSON body, and that page then echoed back whatever the caller passed. test_workspace_api_unknown_user_is_not_an_oracle caught it: two 403s that must be byte-identical differed by the `user=` each request had asked about. Send the path alone. Switching language still returns to the page you were on, and dropping the query also drops any `lang`, which was the reason the query was being filtered in the first place.
An imported module or challenge merges its translations with the source's per language and per field, but the dojo level picked one mapping or the other outright. A dojo that imported another and translated only its own name therefore threw away the source's translated description, and the page fell back to English for it. Also stop storing an empty translated value. An empty DESCRIPTION.md in an i18n tree is a stub a translator has not filled in yet, not a request to render nothing, and storing the empty string left it ambiguous. Adds test_i18n.py coverage for dojo-level import, which had none.
Three tests read a dojo page as somebody other than an admin -- two anonymously and one as a freshly registered user -- because that is the point of a per-session language: it has to work without an account. The fixture dojo had no `type`, so it was private and dojo_accessible() 404'd for all three. They passed whenever i18n_import_dojo happened to be built first, since promoting the dojo to official makes it viewable as a side effect. Under xdist that depends on how the file is distributed. Declaring the dojo public makes it hold on its own.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR adds internationalization (multi-language) support to dojo, module, and challenge descriptions. All descriptions in another language are stored along side the dojo in
i18n/<la>/dojo/module/challengeswhere<la>is a two-letter language code, e.g.,kofor Korean.This PR does not alter the schema of the database.