Skip to content

Add localization system plus alternative English dialect - #243

Merged
graeme merged 12 commits into
mainfrom
add-localization-system
Sep 22, 2026
Merged

graeme merged 12 commits into
mainfrom
add-localization-system

Conversation

@graeme

@graeme graeme commented Sep 22, 2026 •

Copy link
Copy Markdown
Collaborator

What and why

The app could not be translated. User-facing copy sat as bare literals in views, and some of it sat
below the UI layer in the core and repository packages, where no catalog would ever reach it. This
adds the machinery and moves every string into it.

Each UI target now owns a Localizable.xcstrings catalog: the Homebrew app target for the window,
sidebar and menus, plus BrewUIComponents and each BrewFeature* package. That is 238 keys, each
with a translator comment saying where it appears and what any %@ or %lld stands for. Copy
crosses layers as LocalizedStringResource so the bundle and comment travel with the key, and the
error wording that used to live in the domain layers is now worded in
BrewUIComponents/Copy/BrewErrorCopy. Homebrew's own output stays verbatim English.

Two BrewUILint rules hold the line. localized_copy requires bundle: #bundle and a comment: at
every call site, since a package string without #bundle silently never localises.
localization_layer rejects localising calls outside the UI packages.

scripts/localize wraps Xcode's own tools: sync updates the catalogs from a build, verify fails
on a catalog outside the UI layer, a key with no comment, an empty translation, or a language the app
bundle does not ship, and status reports coverage into the CI job summary. Swift Quality runs
verify, and Build and Test runs sync --check after the build so copy cannot drift from its
catalog. en-GB is seeded, 8 of 238 keys, to prove the path end to end. The README documents adding a
language, translating a string and changing English copy.

Validation

macOS 26.5, Apple silicon, Xcode 26.5, Swift 6.3.2.

  • scripts/test: pass, 1104 BrewKit tests and 30 BrewUILint tests

  • SwiftFormat lint, SwiftLint strict and BrewUILint over the production tree: clean

  • scripts/localize sync --check and scripts/localize verify: pass

  • Not run: scripts/test-ui and scripts/test-e2e

  • I followed the conventions and workflow, checked for duplicate PRs and kept this change focused.

  • I added regression coverage for bug fixes or explained why automated coverage is impractical, and reported the relevant validation above.

Screenshots

Not applicable. Every string keeps its exact English wording and en-GB matches it's meaning in alternative dialect, so
there is no before and after to show.

AI assistance

  • AI was used to generate or assist with generating this PR.

Claude Opus 5 via Claude Code wrote the extraction, the lint rules, the script and the docs,
directed and reviewed by me commit by commit. The checks above ran in that session.

ü§ñ Generated with Claude Code

graeme and others added 9 commits September 21, 2026 22:03
Empty Localizable.xcstrings per UI package and for the app target,
defaultLocalization on the package, en-GB as a known region, and the
test plans pinned to en so hosted tests never follow the runner's
language.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Xcode updates the String Catalogs after an IDE build, but xcodebuild
only emits the compiler's .stringsdata. scripts/localize sync builds
the app and runs `xcrun xcstringstool sync` over that data so the
catalogs can be kept current (and checked in CI) from the command line.
verify keeps catalogs in UI targets and rejects empty translations,
which render blank rather than falling back to English; status
summarises per-language progress.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
OperationFailure and the repositories now carry data only; the new
BrewRepositoryError names a malformed brew output, and
BrewUIComponents/Copy/BrewErrorCopy is the one place those enums are
worded. Replaces four duplicated userMessage(for:) switches.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every literal in the shared components now goes through
String(localized:bundle:comment:) or Text(verbatim:), and the target's
catalog is synced from the compiler's extraction.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds bundle: #bundle to every String(localized:) call, converts view
literals, and replaces LocalizedStringKey-typed properties with String
since a key cannot carry a bundle. Test support no longer mirrors copy.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Base copy is US English ("catalog"); the en-GB catalog will carry the
UK spelling once seeded.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
DoctorCopy.warningPreamble stays verbatim on purpose: it mirrors brew
doctor's own English output.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
SidebarItem.title is now the one String behind the sidebar rows, the
View menu and the window titles. The Debug menu uses Text(verbatim:) so
developer-only labels never reach the catalog.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@graeme
graeme requested review from MikeMcQuaid and a lite review from Copilot September 22, 2026 12:47

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

The lint rules have bypasses and a new test imports an undeclared direct module dependency.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 1 High severity · 2 Medium severity · 1 Low severity

Open (4)
What changed in this PR

Adds String Catalog localisation across the app and Swift packages, including seeded en-GB translations.

Changes:

  • Adds localisation lint rules, catalog verification and CI integration.
  • Moves UI copy and error presentation into UI layers.
  • Updates views, view models, tests and documentation for localized resources.

Reported checks pass; UI and E2E tests were not run.

File Description
Tools/​BrewUILint/​Tests/​BrewUILintTests/​TestSupport/​LintHarness.swift Adds lint harnesses.
Tools/​BrewUILint/​Tests/​BrewUILintTests/​LocalizedCopyRuleTests.swift Tests localized-copy linting.
Tools/​BrewUILint/​Tests/​BrewUILintTests/​LocalizationLayerRuleTests.swift Tests layer restrictions.
Tools/​BrewUILint/​Sources/​BrewUILint/​Rules/​LocalizedCopyRule.swift Adds copy lint rule.
Tools/​BrewUILint/​Sources/​BrewUILint/​Rules/​LocalizationLayerRule.swift Adds layer lint rule.
Tools/​BrewUILint/​Sources/​BrewUILint/​Core/​Runner.swift Collects copy parameters.
Tools/​BrewUILint/​Sources/​BrewUILint/​Core/​RuleRegistry.swift Registers new rules.
Tools/​BrewUILint/​Sources/​BrewUILint/​Core/​RuleContext.swift Shares lint context.
Tools/​BrewUILint/​Sources/​BrewUILint/​Core/​CopyParameterCollector.swift Finds localized parameters.
Tests/​BrewUIComponentsTests/​BrewErrorCopyTests.swift Tests error copy mapping.
Tests/​BrewUIComponentsTests/​BrewActionButtonAppearanceTests.swift Resolves localized button titles.
Tests/​BrewFeatureInstalledTests/​UpgradesViewModelScopeTests.swift Updates localized expectations.
Tests/​BrewFeatureInstalledTests/​InstalledViewModelTests.swift Updates error expectations.
Tests/​BrewFeatureInstalledTests/​InstalledViewModelScopeTests.swift Updates localized expectations.
Tests/​BrewFeatureInstalledTests/​InstalledViewModelPresentationTests.swift Updates presentation expectations.
Tests/​BrewFeatureInstalledTests/​InstalledUpgradeBusyPresentationTests.swift Updates failure construction.
Tests/​BrewFeatureInstalledTests/​InstalledUninstallBusyPresentationTests.swift Updates failure construction.
Tests/​BrewFeatureDoctorTests/​DoctorViewModelTests.swift Updates localized expectations.
Tests/​BrewFeatureDiscoverTests/​DiscoverViewModelTests.swift Updates localized expectations.
Tests/​BrewFeatureConfigTests/​ConfigViewModelTests.swift Resolves localized section titles.
Tests/​BrewCoreTests/​OperationFailureTests.swift Updates failure model tests.
Tests/​BrewCLITests/​SerialBrewCommandCenterTests.swift Updates failure assertions.
Tests/​BrewCLITests/​SerialBrewCommandCenterFailureTests.swift Updates failure extraction.
Sources/​BrewUIComponents/​Views/​PreviewCopy.swift Adds preview localization helper.
Sources/​BrewUIComponents/​Views/​PackageDetailSubviews.swift Accepts localized headings.
Sources/​BrewUIComponents/​Views/​NoteCallout.swift Supports localized and verbatim text.
Sources/​BrewUIComponents/​Views/​LastUpdatedLabel.swift Localizes relative time.
Sources/​BrewUIComponents/​Views/​CommandBlockView.swift Localizes command-block copy.
Sources/​BrewUIComponents/​Views/​BrewActionButton.swift Localizes button metadata.
Sources/​BrewUIComponents/​Views/​AsyncContentView.swift Localizes retry action.
Sources/​BrewUIComponents/​Resources/​Localizable.xcstrings Adds component catalog.
Sources/​BrewUIComponents/​Copy/​BrewErrorCopy.swift Centralizes UI error wording.
Sources/​BrewUIComponents/​Commands/​SearchCommands.swift Localizes Find command.
Sources/​BrewUIComponents/​Commands/​RefreshCommands.swift Localizes Refresh command.
Sources/​BrewServicesTestSupport/​InstalledPackagesTestSupport.swift Removes duplicated copy helpers.
Sources/​BrewRepositoryInterfaces/​RepositoryError.swift Adds typed repository error.
Sources/​BrewRepositories/​BrewInstalledPackagesRepository.swift Reports malformed output explicitly.
Sources/​BrewFeatureSelfUpgrade/​Views/​SelfUpgradeBanner.swift Localizes banner copy.
Sources/​BrewFeatureSelfUpgrade/​ViewModels/​SelfUpgradePresentation.swift Localizes upgrade presentation.
Sources/​BrewFeatureSelfUpgrade/​Resources/​Localizable.xcstrings Adds self-upgrade catalog.
Sources/​BrewFeatureInstalled/​Views/​UpgradesSidebarBadge.swift Localizes upgrade badge.
Sources/​BrewFeatureInstalled/​Views/​UpgradesPackagesView.swift Localizes upgrade states.
Sources/​BrewFeatureInstalled/​Views/​UpgradesHeaderView.swift Localizes upgrade controls.
Sources/​BrewFeatureInstalled/​Views/​InstalledUpgradesColumns.swift Localizes search prompts.
Sources/​BrewFeatureInstalled/​Views/​InstalledPackagesView.swift Localizes installed-package UI.
Sources/​BrewFeatureInstalled/​Views/​InstalledPackageDetailView.swift Localizes package details.
Sources/​BrewFeatureInstalled/​Views/​InstalledPackageDetailSubviewSections.swift Localizes metadata sections.
Sources/​BrewFeatureInstalled/​Views/​InstalledOutdatedBadge.swift Localizes outdated badge.
Sources/​BrewFeatureInstalled/​Views/​InstalledListRowView.swift Marks technical text verbatim.
Sources/​BrewFeatureInstalled/​Views/​InstalledDeprecatedBadge.swift Localizes deprecated badge.
Sources/​BrewFeatureInstalled/​ViewModels/​UpgradesViewModel.swift Carries localized upgrade resources.
Sources/​BrewFeatureInstalled/​ViewModels/​UpgradesUpToDateCopy.swift Localizes up-to-date copy.
Sources/​BrewFeatureInstalled/​ViewModels/​UpgradePackageItem.swift Localizes upgrade titles.
Sources/​BrewFeatureInstalled/​ViewModels/​UninstallPackageItem.swift Localizes uninstall copy.
Sources/​BrewFeatureInstalled/​ViewModels/​PackageDetailMetadataItem.swift Localizes metadata values.
Sources/​BrewFeatureInstalled/​ViewModels/​InstalledViewModel.swift Carries localized package counts.
Sources/​BrewFeatureInstalled/​ViewModels/​InstalledPackageDetailViewModel.swift Uses shared error copy.
Sources/​BrewFeatureInstalled/​ViewModels/​InstalledListRowViewModel.swift Localizes accessibility state.
Sources/​BrewFeatureInstalled/​Resources/​Localizable.xcstrings Adds installed-feature catalog.
Sources/​BrewFeatureDoctor/​Views/​DoctorView.swift Localizes Doctor UI.
Sources/​BrewFeatureDoctor/​Views/​DoctorSeverityStyle.swift Localizes severity names.
Sources/​BrewFeatureDoctor/​Views/​DoctorIssueRowView.swift Localizes fix badge.
Sources/​BrewFeatureDoctor/​Views/​DoctorIssueDetailView.swift Localizes issue details.
Sources/​BrewFeatureDoctor/​Views/​DoctorCopy.swift Documents verbatim brew output.
Sources/​BrewFeatureDoctor/​ViewModels/​DoctorViewModel.swift Uses shared error copy.
Sources/​BrewFeatureDoctor/​ViewModels/​DoctorIssueItem.swift Localizes accessibility annotation.
Sources/​BrewFeatureDoctor/​Resources/​Localizable.xcstrings Adds Doctor catalog.
Sources/​BrewFeatureDiscover/​Views/​DiscoverPackagesView.swift Localizes Discover UI.
Sources/​BrewFeatureDiscover/​Views/​DiscoverPackageDetailView.swift Localizes package details.
Sources/​BrewFeatureDiscover/​Views/​DiscoverListRowView.swift Localizes install metrics.
Sources/​BrewFeatureDiscover/​Views/​DiscoverInstalledBadge.swift Localizes installed badge.
Sources/​BrewFeatureDiscover/​ViewModels/​DiscoverViewModel.swift Carries localized headings.
Sources/​BrewFeatureDiscover/​ViewModels/​DiscoverPackageDetailViewModel.swift Uses shared error copy.
Sources/​BrewFeatureDiscover/​ViewModels/​DiscoverListRowViewModel.swift Localizes accessibility state.
Sources/​BrewFeatureDiscover/​Resources/​Localizable.xcstrings Adds Discover catalog.
Sources/​BrewFeatureConsole/​Views/​ConsoleToolbar.swift Localizes console controls.
Sources/​BrewFeatureConsole/​Views/​ConsoleStatusBar.swift Localizes console status.
Sources/​BrewFeatureConsole/​Views/​ConsoleCommands.swift Localizes console commands.
Sources/​BrewFeatureConsole/​Views/​ConsoleBody.swift Localizes empty state.
Sources/​BrewFeatureConsole/​ViewModels/​ConsoleStatusPresentation.swift Localizes phase labels.
Sources/​BrewFeatureConsole/​Resources/​Localizable.xcstrings Adds console catalog.
Sources/​BrewFeatureConfig/​Views/​ConfigView.swift Localizes configuration UI.
Sources/​BrewFeatureConfig/​ViewModels/​ConfigViewModel+Sections.swift Carries localized section titles.
Sources/​BrewFeatureConfig/​ViewModels/​ConfigViewModel.swift Uses shared error copy.
Sources/​BrewFeatureConfig/​ViewModels/​ConfigSectionItem.swift Stores localized titles.
Sources/​BrewFeatureConfig/​Resources/​Localizable.xcstrings Adds configuration catalog.
Sources/​BrewCore/​Operations/​OperationFailure.swift Removes UI copy from core errors.
scripts/​localize Adds catalog synchronization and verification.
README.md Documents translations.
Package.swift Adds package localization resources and dependencies.
Homebrew/​Views/​SidebarItem.swift Localizes sidebar titles.
Homebrew/​Views/​MainSidebarView.swift Uses localized sidebar titles.
Homebrew/​SelfUpgrade/​HelperSelfUpgradeHandoff.swift Localizes handoff errors.
Homebrew/​Localizable.xcstrings Adds app catalog and en-GB seed.
Homebrew/​Features/​MainWindow/​Views/​MainWindowView.swift Localizes window navigation copy.
Homebrew/​Features/​CrashReporting/​CrashReportDialog.swift Localizes crash dialog.
Homebrew/​Debug/​DebugMenuCommands.swift Marks debug copy verbatim.
Homebrew/​BrewApp.swift Localizes help menu.
Homebrew.xcodeproj/​project.pbxproj Registers en-GB region.
Brewfile Adds jq tooling.
Brew-Unit.xctestplan Fixes unit-test locale.
Brew-UI.xctestplan Fixes UI-test locale.
ARCHITECTURE.md Documents localization architecture.
AGENTS.md Documents localization workflow.
.github/​workflows/​swift_quality.yml Verifies and reports catalogs.
.github/​workflows/​pr_build_test.yml Checks catalog synchronization.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread Tests/BrewUIComponentsTests/BrewErrorCopyTests.swift
Comment thread Tools/BrewUILint/Sources/BrewUILint/Rules/LocalizedCopyRule.swift Outdated
Comment thread Sources/BrewFeatureDiscover/Resources/Localizable.xcstrings

@MikeMcQuaid MikeMcQuaid left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks great, nice work!

graeme and others added 3 commits September 22, 2026 23:24
BrewUILint gains localized_copy (every literal is Text(bundle:comment:),
String(localized:bundle:comment:) or Text(verbatim:)) and
localization_layer (no localisation outside BrewUIComponents,
BrewFeature* and the app). The build workflow checks the catalogs
against the code after its Xcode build, the Swift Quality job runs
scripts/localize verify and writes the translation status to the step
summary. Also points the lint and build workflows at Homebrew/ — the
app folder was renamed from Brew/ and the workflows never followed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds the first translations (Licence, catalogue, and the sidebar titles
in the app catalog so macOS lists English (UK) for the app) and
documents the workflow for translators and contributors.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@graeme
graeme force-pushed the add-localization-system branch from e6ad468 to 7ab192c Compare September 22, 2026 13:26
@graeme
graeme merged commit c45d8a5 into main Sep 22, 2026
9 checks passed
@graeme
graeme deleted the add-localization-system branch September 22, 2026 13:43
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.

3 participants