Skip to content

Add Danish (da_DK) language support (#279)#283

Open
fabiodalez-dev wants to merge 6 commits into
mainfrom
feat/danish-locale-279
Open

Add Danish (da_DK) language support (#279)#283
fabiodalez-dev wants to merge 6 commits into
mainfrom
feat/danish-locale-279

Conversation

@fabiodalez-dev

@fabiodalez-dev fabiodalez-dev commented Jul 22, 2026

Copy link
Copy Markdown
Owner

Closes #279. Adds Danish as a full, selectable language, built from @HansUwe52's translation file.

UI strings — locale/da_DK.json (6579 keys, exact parity + order with it_IT)

  • Repaired Uwe's file: 10 entries had their key text corrupted by his translation tool (e.g. questoeste, stripped <style>, (hk)(hb), altered markdown links) — re-keyed to the canonical Italian, keeping his good Danish values.
  • Added the 8 keys missing from his file — including the 4 contributor-help strings he flagged (illustrator/translator/curator/colorist, new in 0.7.40, which didn't exist when he translated).
  • Restored inline <code>/<strong> markup and a <zip-release> placeholder that the machine translation had stripped from ~10 strings that are rendered raw in views (they'd have lost bold/code styling).

Fresh-install seed — installer/database/data_da_DK.sql

Structural clone of data_it_IT.sql: 181 genres, 22 email templates (subjects + HTML bodies, {{placeholders}}/markup preserved), 2 CMS pages, home_content and settings prose in Danish; locale columns → da_DK; a languages row for da_DK. Installer.php requires a per-locale seed (it throws otherwise), so this is what makes a fresh Danish install work.

Wiring

  • Danish added to the installer language step (step0.php + normalizeInstallerLocale), the I18n available-locales fallback, and the languages INSERT in every data_*.sql.
  • migrate_0.7.41.sql upserts the da_DK languages row on existing installs (Uwe just updates → Danish appears).
  • locale/routes_da_DK.json added (RouteTranslator already falls back to English if absent).
  • Emails fall back to en_US when a da_DK template is missing.

Verified

  • da_DK.jsonit_IT key parity 6579 / 0 missing / 0 extra; all inline tags restored; placeholders intact.
  • routes_da_DK.json parity (33 keys); data_da_DK.sql structure matches data_it_IT per table.
  • php -l clean; PHPStan clean on the app change (I18n.php).
  • Real load test: schema.sql + data_da_DK.sql into a fresh DB loads with zero errors — 5 languages incl. da_DK, 22 Danish emails, 181 Danish genres.

Built with a translate → assemble → verify workflow; the machine translation was reviewed and its tag-stripping defects fixed. The Danish prose would still benefit from @HansUwe52's native review, especially the 22 email templates and CMS pages.

Summary by CodeRabbit

  • Nuove funzionalità
    • Aggiunto il supporto completo alla lingua danese (Dansk) in tutta l’app.
    • L’installer consente di selezionare il danese e lo configura correttamente durante installazioni e aggiornamenti.
    • Tradotte interfacce, contenuti principali e percorsi dell’app in danese.
    • Aggiornati i template delle notifiche via email e la disponibilità della lingua nelle aree di accesso.
  • Manutenzione
    • L’aggiornamento preserva correttamente i file della lingua danese durante la pulizia degli orfani.

Adds Danish as a full, selectable language, from HansUwe52's translation.

UI strings — locale/da_DK.json (6579 keys, exact parity + order with it_IT):
- Repaired Uwe's file: 10 keys had their KEY text corrupted by his translation
  tool (e.g. "questo"→"este", stripped <style>, "(hk)"→"(hb)", altered markdown)
  — re-keyed to the canonical Italian, preserving the good Danish values.
- Added the 8 keys missing from his file — including the 4 contributor-help
  strings he flagged (illustratore/traduttore/curatore/colorista, new in 0.7.40).
- Restored inline <code>/<strong> markup and the <zip-release> placeholder that
  the machine translation had stripped from ~10 strings rendered raw in views.

Fresh-install seed — installer/database/data_da_DK.sql (structural clone of
data_it_IT.sql): 181 genres, 22 email templates (subjects + HTML bodies,
placeholders/markup preserved), 2 CMS pages, home_content and settings prose in
Danish; locale columns da_DK; languages row for da_DK. Installer.php requires a
per-locale seed (it throws otherwise), so this is what lets a fresh Danish
install work at all.

Wiring: da_DK added to the installer language step (step0.php + normalizeInstaller
Locale), the I18n available-locales fallback, and the languages INSERT in every
data_*.sql; migrate_0.7.41.sql upserts the languages row on existing installs.
locale/routes_da_DK.json added (RouteTranslator already falls back to English if
absent). Emails fall back to en_US when a da_DK template is missing.

Verified: da_DK.json↔it_IT key parity 6579/0/0, all inline tags restored,
placeholders intact; routes parity (33); data_da_DK.sql structure matches
data_it_IT per-table; php -l clean, PHPStan clean on the app change (I18n);
REAL load test — schema + data_da_DK.sql into a fresh DB loads with zero errors,
yielding 5 languages incl. da_DK, 22 Danish emails, 181 Danish genres.
@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@fabiodalez-dev, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 29 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: be1092e6-193c-42de-86ca-18bbea3f2c40

📥 Commits

Reviewing files that changed from the base of the PR and between c192d01 and 3d7310f.

📒 Files selected for processing (17)
  • .github/workflows/ci-e2e.yml
  • .github/workflows/ci-quality.yml
  • .github/workflows/ci-upgrade-smoke.yml
  • README.md
  • config/default_texts.php
  • installer/database/data_da_DK.sql
  • installer/database/migrations/migrate_0.7.41.sql
  • locale/da_DK.json
  • locale/de_DE.json
  • locale/en_US.json
  • locale/fr_FR.json
  • locale/it_IT.json
  • storage/plugins/archives/ArchivesPlugin.php
  • tests/code-quality.spec.js
  • tests/danish-locale-279.unit.php
  • tests/migration-0.7.41.unit.php
  • version.json
📝 Walkthrough

Walkthrough

La localizzazione danese da_DK viene aggiunta alle risorse, alle route, all’installer, ai template email, al fallback applicativo e ai seed o migrazioni del database.

Changes

Localizzazione danese

Layer / File(s) Summary
Risorse, fallback e integrazioni applicative
locale/da_DK.json, locale/routes_da_DK.json, app/Support/*, app/Middleware/*
Aggiunte traduzioni, route e template email danesi; da_DK è riconosciuta dal fallback, dal middleware e dall’updater.
Selezione e normalizzazione nell’installer
installer/index.php, installer/steps/step0.php, installer/classes/Installer.php, tests/full-test.spec.js
L’installer riconosce e normalizza da/da_DK, valida la locale, mostra Dansk e usa Fortsæt; aggiornato il testo della fase di test.
Seed dei dati danesi
installer/database/data_da_DK.sql
Inizializzati impostazioni, generi, contenuti homepage, temi, pagine CMS e template email danesi, oltre ai dati delle lingue.
Registrazione della lingua nel database
installer/database/data_*.sql, installer/database/migrations/migrate_0.7.41.sql
Aggiunta da_DK ai seed esistenti e alla migrazione, con metadati, metriche di traduzione e upsert idempotenti.

Estimated code review effort: 3 (Moderate) | ~20 minuti

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Il titolo è chiaro, conciso e descrive correttamente l'aggiunta del supporto danese.
Linked Issues check ✅ Passed La locale da_DK.json è stata aggiunta e completata, coprendo le 4 chiavi mancanti indicate nell'issue.
Out of Scope Changes check ✅ Passed Le modifiche sono tutte correlate al supporto danese e non emergono cambiamenti estranei all'obiettivo.
Docstring Coverage ✅ Passed Docstring coverage is 83.33% which is sufficient. The required threshold is 60.00%.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/danish-locale-279

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@installer/database/data_da_DK.sql`:
- Around line 305-310: Update the privacy cookie-banner settings in the database
seed so cookie_banner_language uses “da” and cookie_banner_country uses “dk”,
matching the Danish installation configuration and avoiding Italian defaults.
- Around line 215-226: Update the Danish home_content seed entries for the hero
and cta sections to use the Danish routes defined in routes_da_DK.json: replace
the catalog link with /katalog and the registration link with /registrer,
leaving the remaining seeded content unchanged.

In `@installer/database/migrations/migrate_0.7.41.sql`:
- Around line 1-17: Align the migration version with the current release so it
is executed when upgrading to 0.7.40: rename migrate_0.7.41.sql to the 0.7.40
migration or update version.json to 0.7.41. Preserve the Danish language
registration and ensure the migration filename version remains less than or
equal to the release version.

In `@locale/da_DK.json`:
- Line 2120: Preserve the source HTML markup in the affected Danish translations
at the entries around the “Il codice verrà inserito…” key and the additional
affected entries. Restore each corresponding <style>, <a>, and <code> tag with
all attributes, while keeping the translated text, placeholders, and URLs
unchanged.
- Line 2314: Correggi le traduzioni danesi nelle voci di locale indicate: nella
voce con il messaggio sul prestito sostituisci il termine tedesco “Verfügbar”
con l’equivalente danese corretto; aggiorna inoltre la stringa alla riga 4105
correggendo l’errore grammaticale e sostituendo il testo corrotto
“analyseculies”, senza modificare le chiavi originali.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: e03dabef-4ddf-47b1-88d0-02fb32e70e36

📥 Commits

Reviewing files that changed from the base of the PR and between aae6353 and 3c1ac60.

📒 Files selected for processing (12)
  • app/Support/I18n.php
  • installer/database/data_da_DK.sql
  • installer/database/data_de_DE.sql
  • installer/database/data_en_US.sql
  • installer/database/data_fr_FR.sql
  • installer/database/data_it_IT.sql
  • installer/database/migrations/migrate_0.7.41.sql
  • installer/index.php
  • installer/steps/step0.php
  • locale/da_DK.json
  • locale/routes_da_DK.json
  • tests/full-test.spec.js

Comment thread installer/database/data_da_DK.sql Outdated
Comment thread installer/database/data_da_DK.sql Outdated
Comment thread installer/database/migrations/migrate_0.7.41.sql
Comment thread locale/da_DK.json Outdated
Comment thread locale/da_DK.json Outdated
Selecting Danish in the install wizard produced an Italian catalogue: the UI
localized correctly (step0/index + I18n already knew da_DK), but four locale
maps in the backend Installer class — and two runtime maps in the app — still
listed only it/en/de/fr, so da_DK silently fell through to it_IT. Found by
running a real Danish install through the browser (the SQL-only load test
couldn't catch it — the installer picks the seed file itself).

Installer (installer/classes/Installer.php):
- createEnvFile(): da/da_dk -> da_DK, and da_DK added to the fallback whitelist
  — otherwise .env is written APP_LOCALE=it_IT for a Danish install.
- importInitialData(): da/da_dk -> da_DK in the seed-file map — this is the bug
  that loaded data_it_IT.sql (181 Italian genres + 22 it_IT emails) instead of
  data_da_DK.sql. Now seeds 181 Danish genres + 22 da_DK emails.
- admin-user locale map: new admin row gets locale da_DK, so the admin lands on
  a Danish dashboard instead of reverting to Italian on first login (cf. #112).
- getInstallerLocale(): da/da_dk -> da_DK.

Runtime:
- PrivateModeMiddleware::LOCALES: add da_DK so the Danish login/register route
  variants (registered dynamically in web.php from I18n::getAvailableLocales())
  are recognized as public in private mode instead of being blocked.
- Updater::isCustomLocalePath(): treat da_DK as a bundled locale, so future
  releases keep updating locale/da_DK.json instead of preserving it as a
  user-custom catalogue that never receives translation fixes.

Verified with a full browser install on a disposable Apache vhost + fresh DB:
Danish wizard end to end, .env APP_LOCALE=da_DK, 65 tables, 181 Danish genres
("Prosa"/"Poesi"/"Skønlitteratur"), 22 da_DK email templates
("👤 Ny registreringsanmodning"), public site and admin dashboard both Danish,
admin login works, zero console errors.
@fabiodalez-dev

Copy link
Copy Markdown
Owner Author

Follow-up: fixed a real installer bug, found by installing Danish for real

I ran the full Danish install through a browser (disposable Apache vhost + fresh DB), not just an SQL load test — and it surfaced a bug the SQL test structurally couldn't: selecting Danish produced an Italian catalogue.

The wizard looked fully Danish (UI, steps, connection test), but four locale maps in installer/classes/Installer.php still listed only it/en/de/fr, so da_DK fell through to the ?? 'it_IT' default:

  • createEnvFile() → wrote APP_LOCALE=it_IT (whole app would boot Italian)
  • importInitialData() → seeded data_it_IT.sql (181 Italian genres + 22 it_IT emails) instead of data_da_DK.sql
  • admin-user locale → admin dashboard reverted to Italian on first login (cf. Language problem on first login #112)
  • getInstallerLocale()

Plus two runtime maps the language branch hadn't touched:

  • PrivateModeMiddleware::LOCALES → Danish login/register routes would be blocked in private mode
  • Updater::isCustomLocalePath()da_DK treated as a user-custom locale, so future releases would never ship translation fixes to it

Fixed in 3881535. Re-verified end to end on a fresh Danish install:

Check Result
.env APP_LOCALE=da_DK
Tables 65
Genres 181 Danish (Prosa, Poesi, Skønlitteratur)
Emails 22 da_DK (👤 Ny registreringsanmodning)
Public site Danish (Dit digitale bibliotek)
Admin dashboard Danish (Biblioteksadministrationssystem), login works
Console errors none

Takeaway for the next added language: it's not enough to add the locale files + the installer UI selector — every hardcoded it/en/de/fr map in Installer.php, PrivateModeMiddleware, and Updater has to include it too.

…a_DK.php)

de/en/fr each ship a mail_templates/<locale>.php that overrides the 22 email
subjects+bodies over the in-code Italian base; da_DK had none, so any template
not seeded in the DB fell back to Italian for Danish installs — concretely
`user_registration_verification` (sent by NotificationService for the
registration-verification email) would go out in Italian to Danish users.

- app/Support/mail_templates/da_DK.php: 22 templates, exact key parity with
  en_US.php. 21 are generated from the Danish rows already seeded in
  data_da_DK.sql (so the override layer and the install seed stay identical);
  `user_registration_verification` — which lives only in the code base/override,
  not in the DB seed — is translated to Danish here.
- SettingsMailTemplates::localeOverrides(): map da/da_DK -> da_DK so the file is
  actually loaded (it was previously unreachable).
@fabiodalez-dev

Copy link
Copy Markdown
Owner Author

Self-review — multi-lens pass

Ran a structured review over the diff. The core wiring checks out: every installer locale map, PrivateModeMiddleware, Updater::isCustomLocalePath, SettingsMailTemplates, and the seed files include da_DK, and seed parity holds (181 genres, 22 identically-named email templates, 2 CMS pages, 6579 JSON keys matching it_IT; route registration is dynamic via getAvailableLocales()). No incomplete-sweep bug in the critical paths. Findings below are two worth fixing plus polish — each verified against the code.

Worth fixing before merge

1. 🟠 Danish loan_pickup_ready email drops content present in every other locale.
The Danish template is missing the {{giorni_prestito}} (loan-duration) placeholder and the amber "⏰ pickup deadline … or the loan is automatically canceled" warning box that en_US/de_DE/fr_FR all carry — it keeps only a bare deadline line. The same truncation is in both app/Support/mail_templates/da_DK.php and installer/database/data_da_DK.sql (~line 262), so seeded installs and the override layer both ship the loss. Danish borrowers lose the duration and the auto-cancellation notice. → restore both to match the sibling locales.

2. 🟡 Migration/version coordination — migrate_0.7.41.sql vs version.json at 0.7.40.
The updater runs a migration only when version_compare(migrationVersion, toVersion, '<=') is true. With version.json still 0.7.40, version_compare('0.7.41','0.7.40','<=') is false, so on upgrade the migration is silently skipped and da_DK never lands in the languages table for upgraded installs (fresh installs get it via data_*.sql). This is fine iff the release is cut at 0.7.41 (which bumps version.json); flagging so the release version and the migration name stay coordinated per the migration-version rule.

3. 🟡 I18n::$availableLocales fallback map is missing fr_FR.
The no-DB fallback map lists it_IT/en_US/de_DE/da_DK but not fr_FR, so in an early-bootstrap / DB-unavailable state setLocale('fr_FR') returns false while da_DK works. Pre-existing, but this PR edits that exact map — adding fr_FR is a one-line consistency fix worth folding in.

Polish (low)

4. installer/steps/step0.php — the language-select intro heading/description carries one line per language (IT/EN/DE/FR) but no Danish line, despite the new Danish radio option; breaks the one-line-per-language pattern on that screen.

5. Stale comments that enumerate supported locales without da_DK: step0.php (it_IT, en_US, de_DE, fr_FR), Installer.php (supports it, it_IT, …, fr_FR), and installer/index.php ((it_IT, en_US, de_DE) — already omitted fr_FR pre-PR).

Dismissed (checked, not issues)

  • archives and archive_item both mapping to /arkiv — that same-path pattern holds in every locale (/archivio, /archive, /archiv, /archives); da_DK mirrors the convention correctly.
  • user_registration_verification present in da_DK.php but not in the SQL seed — by design: it's a code/override-only template (distinct from the DB-seeded user_activation_with_verification), matching the en/de/fr override files.
  • Security: no path-traversal or stored-XSS regression — the locale value is whitelist-resolved before any file-path use, and the Danish seed HTML introduces no script/handler content beyond the base locale.

… comments)

Follow-up to the multi-lens review of this PR.

- app/Support/mail_templates/da_DK.php + installer/database/data_da_DK.sql:
  restore the loan_pickup_ready content the Danish translation had dropped —
  the {{giorni_prestito}} loan-duration line and the amber "⏰ pickup deadline …
  or the loan is automatically canceled" warning box that en_US/de_DE/fr_FR all
  carry. Both the override file and the DB seed now match the sibling locales.
- app/Support/I18n.php: add fr_FR to the $availableLocales no-DB fallback map.
  It listed it_IT/en_US/de_DE (and now da_DK) but omitted fr_FR, so in an
  early-bootstrap / DB-unavailable state setLocale('fr_FR') returned false while
  the other bundled locales worked. Pre-existing gap, fixed while touching the map.
- Refresh stale locale enumerations in comments to include da_DK: step0.php
  (validation list + the multilingual language-select heading/description now
  include a Danish line), installer/index.php, installer/classes/Installer.php.

Not addressed here (needs release coordination, not a code fix): migrate_0.7.41.sql
is named for 0.7.41 while version.json is still 0.7.40 — the release must be cut
at >= 0.7.41 (create-release.sh bumps version.json) or the migration is skipped
on upgrade per the migration-version rule.
…ntime

The Code Quality workflow's migration-version guard failed because
migrate_0.7.41.sql (0.7.41) was higher than version.json (0.7.40): the updater
only runs migrations with version_compare(v, target, '<=') true, so as shipped
the da_DK migration would be skipped on upgrade. This is the correct next
release version for the Danish feature, so:

- version.json 0.7.40 -> 0.7.41; README "What's New in v0.7.41" section.
- tests/migration-0.7.41.unit.php: behavioural test that runs the REAL
  migration against a sandbox languages table and asserts da_DK is inserted
  with the canonical metadata, pre-existing locales are untouched, the upsert
  is idempotent on a second run, and a deactivated da_DK is re-activated. This
  also satisfies issue-237-regression's "release migration has a matching
  behavioural unit test" meta-check, which was the other latent CI failure
  waiting behind the version guard.

- CI Node runtime: the deprecation warning flagged actions/checkout@v4 and
  actions/setup-node@v4 (Node 20 runtime, being retired). Bump ci-quality to
  actions/checkout@v6 + actions/setup-node@v5 + node 22, matching the versions
  codeql.yml/test-migrations.yml already use, and bump checkout@v4 -> v6 in
  ci-e2e.yml and ci-upgrade-smoke.yml.

Verified locally against a real MySQL: migration guard passes, all
tests/*.unit.php pass (incl. the new migration test + issue-237 32/32),
route/tailwind/plugin-schema/autoloader static checks pass, PHPStan clean.
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.

Danish language file

1 participant