Resolve the page UI language once and let each ExApp fill its own Phrases.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-11 11:45:25 +02:00
co-authored by Cursor
parent afbd76445d
commit 623ada5f99
14 changed files with 1051 additions and 53 deletions
+24 -3
View File
@@ -335,11 +335,13 @@ Use it when the ExApp wants Nextcloud's navigation column, theme background, and
### Call sequence
1. Fill `AppNavigation`. `Items`, `Page`, and `DefaultID` are required. `Header` and `MissingMessage` are optional.
2. `Items` returns the tree for this request. An `Item` has an id, a label, optional children, and an optional `Icon`.
2. `Items` returns the tree for this request. An `Item` has an id, a label, optional children, an optional `Icon`, and an optional `Phrase` id. The label is the English source. When `Phrase` is set, the page script replaces it for the UI language.
3. Set `Icon` to a same-origin image URL, or to a field of `NextcloudIcons` (core SVGs Nextcloud already serves, such as `NextcloudIcons.Folder`).
4. `Page` returns the HTML body for the selected id and whether that id is known.
5. `SelectKey` defaults to `item`. Other query parameters, including a Visit folder, stay on the address.
6. Mount `Handler` on the ExApp's page route.
6. Set `Cred` to the ExApp's Nextcloud credentials. The shell reads the UI language once from that. The zero value leaves the page in English. Do not pass a language.
7. Set `Phrases` to the ExApp's set. `MissingPhrase` names the Phrase for `MissingMessage`.
8. Mount `Handler` on the ExApp's page route.
A missing item renders `DefaultID`, shows `MissingMessage`, and publishes the corrected query on `data-address`. Every parent starts expanded. At 1024px and below, the tree stays hidden until the user opens it. Choosing an item, pressing Escape, or clicking outside closes it.
@@ -369,6 +371,25 @@ nav := gonexapp.AppNavigation{
http.Handle("/page", nav.Handler())
```
## UI language and Phrases
### Purpose
An ExApp page speaks the Requesting user's UI language. The Library resolves it once per page and writes its own chrome. The ExApp owns the Phrases of its pages and navigation items.
### Call sequence
1. `Credentials.UILanguage(r)` reads the Provisioning OCS user field `language` and forwards the page's `Accept-Language`. The field `locale` is ignored. `de` and `de_DE` use German. `en` and `en_GB` use English. Any other code, a missing user, or a failed read is English, and the page still renders. A later call on the same page request reuses that result.
2. Put `i18n/en.json` and `i18n/de.json` at the project root, or beside a module. Each file is one JSON object of stable dotted id → text. Embed the directory and load it with `PhrasesFromFS`. `Merge` combines sets; a repeated id uses the set added later. CheckDNS and StalwartAdmin use only the default set.
3. English is the complete set. A missing German text shows the English text. An unknown id shows the id. A hole is `{name}`. English and German name the same holes and may place them differently. A hole with no value stays as `{name}`. The value is plain text.
4. Leave the English sentence in the HTML. Name the Phrase with `data-phrase` on the text, `data-phrase-placeholder`, `data-phrase-title`, or `data-phrase-aria-label`. A hole value is JSON on `data-phrase-holes`. App navigation injects `Phrases.Script` and `FillScript`. A page the ExApp renders itself puts `Script(lang)` in the head and `FillScript()` at the end of the body.
5. A sentence the script creates calls `exappPhrase(id, holes)`. The map does not include Library chrome.
6. An error the page displays is `Phrases.Sentence(lang, id, holes)`, the finished sentence. Escape it when writing HTML. A log line the page does not show stays English.
### Errors
`UILanguage` does not return an error. A failed read is English. `PhrasesFromFS` and `ParsePhrases` return an error when a file is missing or is not a JSON object of strings.
## Dialog
### Purpose
@@ -377,7 +398,7 @@ The Dialog is a modal for a message, a confirm, or a prompt. The page calls it f
### When to use it
App navigation already includes the dialog. Insert `DialogHTML` on a page the ExApp renders itself. The buttons say OK and Cancel. The agreeing button's word may be replaced. The choice stays in the page.
App navigation already includes the dialog in the UI language. Insert `DialogHTML()` on a page the ExApp renders itself when that page stays in English, or `Credentials.DialogHTML(r)` so the same read supplies the language. OK stays OK. Cancel is Abbrechen in German. A renamed agreeing button is the exact text the caller passes. The choice stays in the page.
### Call sequence