Skip to content

i18n: add French interface translation - #144

Merged
X4Applegate merged 2 commits into
X4Applegate:mainfrom
Thibault-MTZ:french-translation
Oct 20, 2026
Merged

X4Applegate merged 2 commits into
X4Applegate:mainfrom
Thibault-MTZ:french-translation

Conversation

@Thibault-MTZ

Copy link
Copy Markdown
Contributor

What & why

Add French (fr) to CaddyUI's existing interface language picker by adding only web/i18n/fr.json.

The catalog translates all 314 keys currently in en.json, sets _meta.name to Français, and preserves all keys and interpolation placeholders. It covers the interface strings already exposed to localization; forms not yet internationalized remain unchanged.

Related issue

Closes #142.
Related to #128.

Type of change

  • Bug fix
  • New feature
  • Documentation
  • Refactor / internal (no behavior change)
  • Build / CI / tooling

Checklist

  • go build ./... compiles and go test ./... passes
  • Commits are clean (WIP squashed) and the PR is focused on one concern
  • Code matches the existing style (no new linters required)
  • User-facing change is noted in CHANGELOG.md with a version bump — not included for this catalog-only contribution; see below
  • Docs updated where relevant — not required for this catalog-only addition
  • I agree my contribution is licensed under the project's Apache License 2.0

Notes for reviewers

Following docs/TRANSLATING.md, the PR intentionally adds only the locale catalog, without changing Go code, templates, JavaScript, documentation, or the version. Missing future keys retain the normal English fallback.

Validation against main c43126a4a7dd4e934bb3df0245a3b88fefa2a49e:

  • go test ./internal/i18n/ ./internal/server/ -run 'Translation|Catalog|Language|Locale'
  • go build -p1 ./...
  • go test -p1 ./...
  • Catalog checks: 314 matching keys, no empty translations, and unchanged placeholders.

The translation was prepared with AI assistance and reviewed locally. Authenticated French profile and dashboard rendering was also checked on a local development build.

Replaces #143 after renaming the source branch to french-translation; the commit and catalog are unchanged.

@X4Applegate X4Applegate left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for this, @Thibault-MTZ! I went through it carefully.

Mechanical checks (all good)

  • Checked against current main: 314 keys in fr.json, exactly matching en.json; none missing, none extra, none empty.
  • Every {placeholder} is preserved, and there is no markup in any value.
  • go test ./internal/i18n/ ./internal/server/ -run 'Translation|Catalog|Language|Locale' passes with the file added.
  • I read all 314 pairs: the French is natural and consistent (hôtes proxy, parc, jetons, instantanés, "vous" throughout).

One thing to fix before merge: the dashboard activity verbs

The Recent activity feed builds each line as <actor> <verb> <target> (see dashboard.html, around ops-activity-row__summary), so the verbs have to read as a verb after a person's name. Right now they are nouns, which gives "alice création example.com". Past-tense verbs read correctly:

"dashboard.activity.verb.created":      "a créé",
"dashboard.activity.verb.updated":      "a modifié",
"dashboard.activity.verb.deleted":      "a supprimé",
"dashboard.activity.verb.revoked":      "a révoqué",
"dashboard.activity.verb.enabled":      "a activé",
"dashboard.activity.verb.disabled":     "a désactivé",
"dashboard.activity.verb.imported":     "a importé",
"dashboard.activity.verb.reclassified": "a reclassé"

Small suggestions (optional)

  • nav.section.developer: "Développement" reads like a development environment; "Développeur" fits a nav section better.
  • dashboard.raw.description: the French adds the word "JSON" ("routes JSON") that the English does not have; "Définitions récentes de routes brutes sur le serveur actif" stays closer to the source.
  • settings.general.word_any: the template renders "(par défaut 404 ; tout 400–599, par exemple 403, …)". "tout" reads oddly there; "toute valeur de" would be clearer.

Everything else looks good. Once the verbs are changed I'm happy to merge. Merci !

@Thibault-MTZ

Thibault-MTZ commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor Author

Thanks for the review! Fixed the eight activity verbs and applied your three wording suggestions in 4a0a89a.

The feed now reads naturally, e.g. “Bob a créé example.com”. Only web/i18n/fr.json changed.

Validation: all 314 keys match English, placeholders are unchanged, no empty strings or HTML, and the translation/catalog/language/locale tests pass.

Ready for another look. Merci !

@X4Applegate
X4Applegate merged commit ce1922d into X4Applegate:main Oct 20, 2026
5 of 6 checks passed
@X4Applegate

Copy link
Copy Markdown
Owner

Merged — thank you, this is a genuinely well-prepared contribution.

I verified it against main before merging rather than taking the description on trust:

  • 314/314 keys, no missing and none extra versus en.json
  • Zero placeholder mismatches — every {count}, {done} and friend carried across intact
  • Valid JSON, _meta.name set to Français, picked up automatically by the ReadDir loader with no code change needed, exactly as you said
  • Built it, ran the suite, then ran the UI with locale=fr and clicked through: Hôtes proxy, Routes avancées, Opérations, Paramètres all render, and the interpolated strings fill correctly — "Préparation de l'environnement · 1 étape(s) sur 3 requises"

That last one told me you translated against current main rather than an older snapshot: the readiness counter only changed from 4 to 3 in v2.64.2, two days ago. Appreciated.

The twelve strings identical to English (Proxy, Redirections, Administration, Configuration, Notifications, Destination, Source, active…) I checked individually — they're genuine French, not untranslated leftovers.

One note for next time, and it was not your fault: CI showed nothing on this PR for a while because workflows from first-time contributors need maintainer approval. I approved them and everything passed. The claude-review check shows red, but it was cancelled rather than failed — that bot can't read repository secrets on fork PRs by design. Nothing to do with your change.

French will be in the next release. If you'd like to keep it current as new keys land, docs/TRANSLATING.md has the workflow — and you're very welcome to.

X4Applegate added a commit that referenced this pull request Oct 20, 2026
#145: the reporter's complaint was not that analytics data was
missing but that reaching it took 'a lot of back and forth' - status
mix on one page, visitors on another, user agents only underneath an
individual visitor, each drill-down answering one question and then
dead-ending.

Adds a request explorer that takes every dimension at once (host,
status class, user agent, path, client IP, time window) and lists the
matching requests. The fleet-wide status chips, which were plain text
while the per-host ones have always been links, now open it filtered.
Request-chart bars are split by response class and link to their own
bucket, so a spike drills through to exactly its own requests. Hosts
list their verbatim user agents rather than only bucketed browser
names. Response times report p50/p95/p99.

Fixes a bug found while building that: AVG(duration_ms) returns a
REAL and was scanned straight into an int64, so ResponseTimeSince
returned a scan error and all-zero stats on any real data. Verified
against a 188k-event database - zeros before, 284ms average after.
That is why latency looked absent rather than sparse, and probably
why it was asked for.

Not done, and said plainly in the changelog: splitting upstream time
from total time. Caddy's access log emits one duration for the whole
request, so there is nothing to separate.

#142/#144: French interface translation from @Thibault-MTZ, all 314
keys, verified for placeholder parity and rendering.
@Thibault-MTZ

Thibault-MTZ commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor Author

@X4Applegate Thanks for merging this and for the detailed checks! I'll do my best to keep the French translation up to date as new strings are added.

I'm also working on extending the existing i18n coverage to the rest of the interface: moving hardcoded text in templates and JavaScript into the language catalogs, including forms, settings and Analytics. The idea is to make those parts translatable for every language, not just French. I'm already using and testing this version in my own local installation, with catalog checks and rendering tests.

Would you be interested in a separate PR for that work? I can split the i18n changes from the additional French translations, or break it into smaller PRs if that is easier to review. I'd like to check the approach with you before submitting it. Merci!

@X4Applegate

Copy link
Copy Markdown
Owner

Yes, very interested — and thank you for asking before writing it, because the structure matters more than the volume here.

This is also already the plan rather than a detour. docs/TRANSLATING.md says "more of the interface is moved onto keys release by release — the other Settings pages, then the list pages and forms, the large proxy-host form last". You'd be picking up exactly that, which is the part that has been going slowly.

Please split it

Extraction separate from translation. One PR that moves hardcoded strings onto keys and adds them to en.json, and a separate one that fills in fr.json. They need different kinds of review: the first is a mechanical diff where I'm checking nothing was missed or mangled, the second is a language judgement I can't make. Mixed together, both get worse.

Then one PR per area, not one big one. Suggested order, roughly the documented one and easiest-to-hardest:

  1. The remaining Settings pages (Security, Notifications, DNS, Analytics, Integrations, AI, Backup)
  2. The list pages — proxy hosts, redirections, advanced routes, certificates
  3. Analytics — see the warning below
  4. Forms, smallest first
  5. The proxy-host form last. It is ~3,700 lines and by far the biggest; it deserves its own PR and probably its own week

Small PRs are genuinely easier here, so split further if a page is large. I'd rather review six focused diffs than one that touches forty templates.

Two things that will make this safer than it looks

Adding English keys cannot break the other translations. TestEveryCatalogMatchesEnglishKeysAndPlaceholders walks each catalog's own keys and checks English defines them — it does not require a catalog to have every English key. So new keys simply fall back to English in ru.json and zh-CN.json until their maintainers catch up. No coordination needed, and nothing goes red on them.

The reverse is caught automatically. TestEveryTranslationKeyUsedExistsInEnglish scans every .html and .js in the embedded filesystem for {{t $ "..."}} and fails if en.json lacks the key. So a typo'd or forgotten key fails CI rather than silently rendering the raw key in production. Run go test ./internal/server/ -run I18n as you go.

One warning before you start on Analytics

Analytics changed substantially in v2.66.0, yesterday. There's a new analytics_requests.html page, and analytics.html and analytics_host.html both gained markup. Those templates had zero {{t}} coverage, so I added plain English there knowing it was consistent with the rest of the page — which means there's now more to extract than when you looked. Please rebase on current main before touching that area, or leave it until later in the sequence.

Conventions worth following

  • Keys are stable dotted names grouped by area: nav.*, dashboard.*, profile.*, settings.*, page.*, status.*. Script-written text uses a js. prefix (js.dashboard.*).
  • status.* exists for shared words — Online, Enabled, Disabled — so please reuse it rather than minting proxyhosts.enabled and friends.
  • Named placeholders ({count}, {done}) rather than positional, and keep them identical to the English.

Ping me on each PR and I'll review promptly. If you'd rather sanity-check the approach on one small page first — Settings › Security, say — that's a good way to confirm the shape before committing to the long tail.

Merci beaucoup — this is the contribution the i18n work most needed.

@Thibault-MTZ Thibault-MTZ mentioned this pull request Oct 20, 2026
4 of 11 tasks
@Thibault-MTZ
Thibault-MTZ deleted the french-translation branch October 20, 2026 21:25
@Thibault-MTZ

Copy link
Copy Markdown
Contributor Author

Thanks again for the advice! The first extraction batch is in #147, covering Security settings and sign-in, with English keys and tests. Full tests and builds pass; the description lists the browser checks and remaining limits. French is ready separately and will follow once the English keys land. I left this as a draft for feedback on the scope and release-version entry.

@X4Applegate

Copy link
Copy Markdown
Owner

Saw it — I've reviewed and approved #147 over there, so you're unblocked on both questions you left open.

Short answers so you don't have to dig through the review:

  • Scope is right. Including sign-in was a good call, not scope creep — it's adjacent and you found it while testing the page it belongs to. No need to split.
  • Version is v2.67.0. Interface-coverage batches have taken a minor bump before.
  • Don't write the changelog entry — I'll add it on merge so it matches the house style. Just mark the PR ready.

The review has the detail, including how I verified it: I built main and your branch side by side and diffed the rendered English rather than reading the diff, which is the only way to be sure an extraction didn't quietly drop a sentence. It didn't.

French can follow as its own PR once the English keys land, as you planned.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add French (fr) interface translation

2 participants