Add German localization (i18n) alongside English
Summary
Forji currently ships English-only with all user-facing strings hardcoded as literals. This issue tracks adding German as a second language using Apple's modern String Catalog (.xcstrings) workflow. The goal is a fully German-capable build that falls back to English for anything untranslated, while leaving Forgejo API data (repo names, issue titles, usernames) untouched.
Background / current state
- No localization infrastructure exists: no
.xcstrings,.strings,.stringsdict, or.lprojdirectories. project.pbxproj:developmentRegion = en,knownRegions = (en, Base). German is not declared.- ~150+ user-facing strings across 54 view files, all string literals.
- Target is iOS 18+ (uses
TabAPI + liquid glass), so String Catalogs are available and are the right tool.
Approach
Use a single Localizable.xcstrings String Catalog (not legacy .strings/.stringsdict). Most SwiftUI views take LocalizedStringKey, so existing literals like .navigationTitle("Repositories") are already valid keys and need no code change, only translation. Plain-String display logic in helpers/models needs wrapping in String(localized:).
Work items
1. Project setup
- Add a
Localizable.xcstringsString Catalog (File -> New -> String Catalog). - Add German via Project -> Info -> Localizations ->
+-> German. ConfirmknownRegionsinproject.pbxprojgainsde. - Build once so Xcode's scan auto-populates the catalog from all
LocalizedStringKeyliterals.
2. Wrap plain-String display logic in String(localized:)
These return raw String and are not auto-localized. Files confirmed to need edits:
Models/ReviewState.swift-ReviewEvent.title(Comment / Approve / Reject),ReviewState.label(approved / rejected / commented / pending).Helpers/PRStatusStyle.swift-textvalues (Merged / Draft / Open / Closed).Helpers/WorkflowStatus.swift-WorkflowRunFilter.label(All / Running / Success / Failed),WorkflowStatusStylelabels (Success / Failed / Cancelled / Skipped / Running / Waiting / Blocked / Unknown), anddisplayName("Run #..." / "Run ...").- Sweep the rest of
Models/andHelpers/for any other computed display strings missed above.
3. Fix interpolation edge cases (German word order / plurals)
Views/NotificationsOverviewView.swift-"There are no \(statusFilter) notifications": replace the interpolation with aswitchover the filter yielding distinct full keys per case, since German word order differs.Helpers/WorkflowStatus.swift-status.capitalizedon API strings won't localize; map known statuses to localized keys viaswitch, or consciously leave unknown ones English.- Error-message prefixes like
"PR created, but requesting reviewers failed: \(error.localizedDescription)"(Views/PullRequestCreateView.swift): localize the prefix; thelocalizedDescriptionis already system-localized.
4. Pluralization
Use the String Catalog's inline "Vary by Plural" for count-based strings (German has different plural rules):
"Comments (\(comments.count))"-Views/IssueDetailView.swift"Reviews (\(reviews.count))"-Views/PullRequestDetailView.swift- Audit for other
\(...count...)strings.
5. Translate
- Fill the German column for every catalog entry, mark as reviewed.
- Provide German for accessibility labels too (e.g. "Star repository" / "Unstar repository" in
RepositoryListView.swift).
Explicitly out of scope / do NOT translate
- Forgejo API data rendered via
Text(variable):repository.name,issue.title,pullReq.title,user.login, server-provided notification statuses. The existingText(variable)(non-literal) usage already prevents these from being localized; keep it that way.
Open question / needs investigation
- ForgejoKit
formatRelativeDate(_:)lives in the dependency package, not the app. If it usesRelativeDateTimeFormatter/Date.FormatStyle, relative dates localize automatically by locale. If it hand-builds strings like "2 days ago", that text will stay English and requires a change in ForgejoKit itself. Verify which, and file a follow-up against ForgejoKit if needed.
Acceptance criteria
- App runs fully in German when device language is German, with no leftover English in app-authored UI (API data excepted).
- Untranslated strings fall back to English gracefully.
- Plurals render correctly in both languages (1 Kommentar vs 2 Kommentare).
- Switching device language to English shows no regression.
just build,just test,just lint,just formatall pass.
Testing notes
- Use the Xcode scheme's App Language -> German run option (and the SwiftUI preview locale
.environment(\.locale, .init(identifier: "de"))) to spot-check screens without changing the whole simulator.