Skip to content

Commit bb3b45d

Browse files
committed
feat: Language locale added native Portuguese
1 parent 84433f3 commit bb3b45d

34 files changed

Lines changed: 3324 additions & 634 deletions
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"specId": "02d18244-d737-442a-b102-f45dc2c9237f", "workflowType": "requirements-first", "specType": "feature"}

.kiro/specs/i18n-localization/design.md

Lines changed: 409 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
# Requirements Document
2+
3+
## Introduction
4+
5+
This feature adds internationalisation (i18n) and localisation (l10n) support to the WeatherWidget desktop application. The goal is to make every piece of human-readable content — widget display, settings window, system tray menu, error messages, weather descriptions, date/time formatting, and validation messages — available in multiple languages. The initial supported locales are British English (en-GB) and Brazilian Portuguese (pt-BR). The architecture must allow additional locales to be added by simply dropping in a new locale file.
6+
7+
## Glossary
8+
9+
- **Locale_Manager**: The component responsible for loading, caching, and serving translated strings for the active locale.
10+
- **Locale_File**: A structured data file (JSON) containing all translated strings for a single locale, keyed by a stable message identifier.
11+
- **Active_Locale**: The locale currently selected by the user and persisted in the configuration.
12+
- **Message_Key**: A unique, stable string identifier used to look up a translated message (e.g. `settings.title`, `tray.show_widget`).
13+
- **Settings_Window**: The Fyne-based settings dialog containing Appearance, Data Provider, Locations, and About tabs.
14+
- **System_Tray_Menu**: The desktop system tray context menu with Show Widget, Hide Widget, Settings, and Quit items.
15+
- **City_Panel**: The per-city weather display panel showing city name, temperature, weather description, time, and date.
16+
- **Weather_Formatter**: The component that formats temperature, date, time, and weather description strings for display.
17+
- **Config_Service**: The component that loads, validates, and persists the application configuration file.
18+
- **Validation_Engine**: The component that validates configuration fields and produces user-facing error messages.
19+
20+
## Requirements
21+
22+
### Requirement 1: Locale File Structure and Loading
23+
24+
**User Story:** As a developer, I want locale files stored in a well-defined JSON format, so that adding a new language requires only creating a new file without code changes.
25+
26+
#### Acceptance Criteria
27+
28+
1. THE Locale_Manager SHALL load locale data from JSON Locale_Files embedded in the application binary.
29+
2. WHEN the application starts, THE Locale_Manager SHALL load the Locale_File matching the Active_Locale from the persisted configuration.
30+
3. IF a Locale_File for the Active_Locale is missing or corrupt, THEN THE Locale_Manager SHALL fall back to the en-GB Locale_File.
31+
4. THE Locale_Manager SHALL provide a lookup function that accepts a Message_Key and returns the translated string for the Active_Locale.
32+
5. IF a Message_Key is not found in the Active_Locale Locale_File, THEN THE Locale_Manager SHALL return the en-GB translation for that Message_Key.
33+
6. WHEN a new Locale_File is added to the embedded assets, THE Locale_Manager SHALL make the new locale available without any code changes beyond the file addition.
34+
35+
### Requirement 2: Locale File Content — en-GB and pt-BR
36+
37+
**User Story:** As a user, I want the application available in British English and Brazilian Portuguese, so that I can use the application in my preferred language.
38+
39+
#### Acceptance Criteria
40+
41+
1. THE Locale_File for en-GB SHALL contain translations for every Message_Key used in the application.
42+
2. THE Locale_File for pt-BR SHALL contain translations for every Message_Key used in the application.
43+
3. THE Locale_File for en-GB and pt-BR SHALL each cover the following categories of Message_Keys:
44+
- Settings_Window title and tab labels (Appearance, Data Provider, Locations, About)
45+
- Settings_Window section titles and subtitles (Widget Position, Background Transparency, Refresh Interval, Startup, Data Provider & API Key)
46+
- Settings_Window form labels (Provider, API Key, Name, Region, Latitude, Longitude, Timezone)
47+
- Settings_Window button labels (Get FREE API, Get PRO API, Add City, Search API, Searching..., Save, Remove)
48+
- Settings_Window placeholder text (API Key, City name, Region / Country, Latitude (optional), Longitude (optional), Timezone)
49+
- Settings_Window position labels (Top-Left, Top-Right, Bottom-Left, Bottom-Right)
50+
- Settings_Window opacity labels (25%, 50%, 75%, 100%)
51+
- Settings_Window note text (Free = 120 minutes refresh rate, Pro = 10 minutes refresh rate)
52+
- Settings_Window auto-start checkbox label (Launch WeatherWidget when Windows starts)
53+
- Settings_Window card titles and subtitles (Saved Cities, Add New City, Manage your tracked locations)
54+
- Settings_Window About tab content (version label, application description, Website label, Preview label)
55+
- Settings_Window dialog messages (Settings saved successfully!, Saved)
56+
- System_Tray_Menu item labels (Show Widget, Hide Widget, Settings, Quit)
57+
- City_Panel placeholder text (City, RG; --°C; --; --:--:--; --/--/----)
58+
- City_Panel stale data warning (Data may be stale)
59+
- Weather_Formatter temperature unit suffix (°C)
60+
- Validation_Engine error messages (must contain 1 to 5 cities, must not be empty, invalid latitude, etc.)
61+
- Config city management error messages (maximum of 5 cities reached, cannot remove the last city)
62+
- Settings_Window error messages (city name is required, city name is required to search, API Key is required, Region / Country is required to search via EasyWeatherWidget)
63+
- Monitor selector label (Display Monitor, Monitor N)
64+
- Refresh interval display format (N min)
65+
66+
### Requirement 3: Locale Configuration Persistence
67+
68+
**User Story:** As a user, I want my language preference saved, so that the application remembers my choice across restarts.
69+
70+
#### Acceptance Criteria
71+
72+
1. THE Config_Service SHALL store the Active_Locale as a string field named `locale` in the configuration file.
73+
2. WHEN the configuration file does not contain a `locale` field, THE Config_Service SHALL default the Active_Locale to "en-GB".
74+
3. WHEN the user selects a different locale in the Settings_Window, THE Config_Service SHALL persist the new Active_Locale value.
75+
4. THE Validation_Engine SHALL validate that the `locale` field matches one of the available Locale_Files.
76+
5. IF the `locale` field contains an unsupported value, THEN THE Validation_Engine SHALL produce a validation error and THE Config_Service SHALL fall back to "en-GB".
77+
78+
### Requirement 4: Settings Window Localisation
79+
80+
**User Story:** As a user, I want the settings window displayed in my chosen language, so that I can configure the application without language barriers.
81+
82+
#### Acceptance Criteria
83+
84+
1. WHEN the Settings_Window opens, THE Settings_Window SHALL render all labels, titles, subtitles, placeholders, button text, and tab names using translations from the Active_Locale.
85+
2. THE Settings_Window SHALL include a language selector control in the Appearance tab that lists all available locales by their display name (e.g. "English (UK)", "Português (Brasil)").
86+
3. WHEN the user selects a new locale in the language selector, THE Settings_Window SHALL update all visible text to the newly selected locale without requiring a restart.
87+
4. THE Settings_Window SHALL display the refresh interval label in the format translated for the Active_Locale (e.g. "10 min" in en-GB, "10 min" in pt-BR).
88+
5. THE Settings_Window SHALL display the auto-start checkbox label translated for the Active_Locale.
89+
6. THE Settings_Window SHALL display all dialog messages (save confirmation, error dialogs) translated for the Active_Locale.
90+
91+
### Requirement 5: System Tray Menu Localisation
92+
93+
**User Story:** As a user, I want the system tray menu in my chosen language, so that I can control the widget without switching mental context.
94+
95+
#### Acceptance Criteria
96+
97+
1. WHEN the system tray menu is created, THE System_Tray_Menu SHALL render all menu item labels using translations from the Active_Locale.
98+
2. THE System_Tray_Menu SHALL display the following items translated: "Show Widget", "Hide Widget", "Settings", "Quit".
99+
3. WHEN the Active_Locale changes, THE System_Tray_Menu SHALL update its menu item labels to the new locale on the next menu rebuild or application restart.
100+
101+
### Requirement 6: Weather Display Localisation
102+
103+
**User Story:** As a user, I want weather information displayed in my language, so that I can read conditions and times naturally.
104+
105+
#### Acceptance Criteria
106+
107+
1. THE Weather_Formatter SHALL format temperature strings using the Active_Locale temperature unit suffix (e.g. "25°C").
108+
2. THE Weather_Formatter SHALL format date strings according to the Active_Locale convention (en-GB: "DD/MM/YYYY", pt-BR: "DD/MM/YYYY").
109+
3. THE Weather_Formatter SHALL format time strings according to the Active_Locale convention (en-GB: "HH:MM:SS", pt-BR: "HH:MM:SS").
110+
4. THE City_Panel SHALL display the stale data warning message translated for the Active_Locale.
111+
5. THE City_Panel SHALL display placeholder text translated for the Active_Locale when no weather data is available.
112+
113+
### Requirement 7: Validation and Error Message Localisation
114+
115+
**User Story:** As a user, I want error messages in my language, so that I can understand and fix configuration problems.
116+
117+
#### Acceptance Criteria
118+
119+
1. WHEN a validation error occurs, THE Validation_Engine SHALL produce error messages translated for the Active_Locale.
120+
2. WHEN a city management operation fails, THE Config_Service SHALL return error messages translated for the Active_Locale (e.g. "maximum of 5 cities reached", "cannot remove the last city").
121+
3. WHEN a settings form validation fails, THE Settings_Window SHALL display error dialog text translated for the Active_Locale (e.g. "city name is required", "API Key is required in the settings above to search").
122+
4. IF a connection test fails, THEN THE Settings_Window SHALL display the failure message translated for the Active_Locale.
123+
124+
### Requirement 8: Locale File Round-Trip Integrity
125+
126+
**User Story:** As a developer, I want to verify that locale files can be serialised and deserialised without data loss, so that translations are never silently corrupted.
127+
128+
#### Acceptance Criteria
129+
130+
1. FOR ALL valid Locale_Files, serialising to JSON then deserialising SHALL produce an equivalent Locale_File (round-trip property).
131+
2. THE Locale_Manager SHALL reject Locale_Files that contain duplicate Message_Keys.
132+
3. THE Locale_Manager SHALL reject Locale_Files that are not valid JSON.
133+
134+
### Requirement 9: Locale Completeness Validation
135+
136+
**User Story:** As a developer, I want automated validation that every locale file covers all required keys, so that missing translations are caught before release.
137+
138+
#### Acceptance Criteria
139+
140+
1. THE Locale_Manager SHALL expose a function that compares a Locale_File against the en-GB reference and returns a list of missing Message_Keys.
141+
2. WHEN a Locale_File is loaded, THE Locale_Manager SHALL log a warning for each Message_Key present in en-GB but missing from the loaded Locale_File.
142+
3. FOR ALL non-default Locale_Files, the set of Message_Keys SHALL be a superset of the en-GB Message_Keys (completeness property).
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# Tasks
2+
3+
## Task 1: Create LocaleManager core and locale files
4+
5+
- [x] 1.1 Create `internal/i18n/locales/en-GB.json` with all message keys covering settings, tray, panel, weather formatting, validation, and error messages
6+
- [x] 1.2 Create `internal/i18n/locales/pt-BR.json` with Brazilian Portuguese translations for all message keys
7+
- [x] 1.3 Create `internal/i18n/embed.go` with `//go:embed locales/*.json` directive exposing `LocaleFS`
8+
- [x] 1.4 Create `internal/i18n/locale.go` implementing `LocaleManager` with `NewLocaleManager`, `SetLocale`, `T`, `TWithArgs`, `ActiveLocale`, `AvailableLocales`, `MissingKeys`, and `ValidateLocaleFile`
9+
- [x] 1.5 Write unit tests in `internal/i18n/locale_test.go` for locale loading, fallback, key lookup, missing key detection, corrupt file handling, and en-GB/pt-BR completeness
10+
11+
## Task 2: Property-based tests for LocaleManager
12+
13+
- [x] 2.1 Write property test: Lookup returns translated string for valid keys (Property 1) `[pbt]`
14+
- [x] 2.2 Write property test: Missing key fallback to en-GB (Property 2) `[pbt]`
15+
- [x] 2.3 Write property test: Locale file JSON round-trip (Property 5) `[pbt]`
16+
- [x] 2.4 Write property test: Missing keys detection returns correct set difference (Property 6) `[pbt]`
17+
18+
## Task 3: Add locale field to Config and validation
19+
20+
- [x] 3.1 Add `Locale string` field to `Config` struct in `internal/config/types.go` with JSON tag `"locale"` and update `DefaultConfig()` to set `Locale: "en-GB"`
21+
- [x] 3.2 Update `internal/config/service.go` `Load` method to default `Locale` to `"en-GB"` when the field is empty or missing
22+
- [x] 3.3 Add locale validation to `internal/config/validation.go` that checks the `locale` field against available locale codes and produces a translated validation error for invalid values
23+
- [x] 3.4 Write property test: Locale validation accepts valid and rejects invalid (Property 3) `[pbt]`
24+
- [x] 3.5 Write property test: Translated validation errors (Property 7) `[pbt]`
25+
26+
## Task 4: Localise weather formatting functions
27+
28+
- [x] 4.1 Update `internal/weather/format.go` to accept locale parameter in `FormatTemperature`, `FormatDate`, `FormatTime` functions, using locale-specific format strings from the `LocaleManager`
29+
- [x] 4.2 Update all callers of format functions (`internal/ui/panel/panel.go`, `internal/weather/service.go`) to pass locale information
30+
- [x] 4.3 Write property test: Locale-aware formatting preserves structure (Property 4) `[pbt]`
31+
- [x] 4.4 Update existing format tests and property tests in `internal/weather/format_test.go` and `internal/weather/format_property_test.go` to account for locale parameter
32+
33+
## Task 5: Localise city management error messages
34+
35+
- [x] 5.1 Update `internal/config/cities.go` `AddCity`, `RemoveCity`, and `ReorderCities` to accept a translation function parameter and use it for error messages
36+
- [x] 5.2 Update all callers of city management functions to pass the `LocaleManager.T` function
37+
- [x] 5.3 Update existing city tests in `internal/config/cities_test.go` and `internal/config/cities_property_test.go` to account for the translation function parameter
38+
39+
## Task 6: Localise validation error messages
40+
41+
- [x] 6.1 Update `internal/config/validation.go` `Validate` and helper functions to accept a translation function and use translated message keys for all `ValidationError.Message` values
42+
- [x] 6.2 Update all callers of `Validate` to pass the translation function
43+
- [x] 6.3 Update existing validation tests in `internal/config/validation_test.go` and `internal/config/validation_property_test.go` to account for the translation function parameter
44+
45+
## Task 7: Localise Settings Window
46+
47+
- [x] 7.1 Update `internal/ui/manager.go` `UIManager` to store a `*LocaleManager` reference and accept it in `NewUIManager`
48+
- [x] 7.2 Update `internal/ui/settings.go` `ShowSettings` to replace all hardcoded strings with `lm.T()` calls for labels, titles, subtitles, placeholders, button text, tab names, dialog messages, and notes
49+
- [x] 7.3 Add a language selector widget to the Appearance tab in `ShowSettings` that lists available locales and triggers live UI refresh on selection
50+
- [x] 7.4 Update `internal/ui/settings.go` `buildConfigFromUI` to include the selected locale in the returned `Config`
51+
- [x] 7.5 Update existing settings tests in `internal/ui/settings_test.go` to account for locale manager dependency
52+
53+
## Task 8: Localise System Tray Menu
54+
55+
- [x] 8.1 Update `internal/ui/tray.go` `SetupSystemTray` to use `lm.T()` for all menu item labels ("Show Widget", "Hide Widget", "Settings", "Quit")
56+
- [x] 8.2 Ensure tray menu labels update on next rebuild when locale changes
57+
58+
## Task 9: Localise City Panel
59+
60+
- [x] 9.1 Update `internal/ui/panel/panel.go` `NewCityPanel` to accept a `LocaleManager` or translation function and use it for placeholder text ("City, RG", "--°C", "--", "--:--:--", "--/--/----")
61+
- [x] 9.2 Update `internal/ui/panel/panel.go` `ShowError` to use translated stale data warning message
62+
- [x] 9.3 Update `internal/ui/panel/panel.go` `StartClock` to use locale-aware date/time formatting
63+
- [x] 9.4 Update existing panel tests in `internal/ui/panel/panel_test.go` to account for locale manager dependency
64+
65+
## Task 10: Wire LocaleManager into AppManager
66+
67+
- [x] 10.1 Update `internal/app/manager.go` `AppManager` to create `LocaleManager` at startup, load the configured locale, and pass it to `UIManager`, weather formatting, and validation components
68+
- [x] 10.2 Update `internal/app/manager.go` `onSettingsSave` to handle locale changes — call `LocaleManager.SetLocale` when the locale config changes
69+
- [x] 10.3 Update `cmd/weatherwidget/main.go` if needed to pass locale-related dependencies
70+
71+
## Task 11: Final integration verification
72+
73+
- [x] 11.1 Run all existing tests to verify no regressions from i18n changes
74+
- [x] 11.2 Run all new property-based tests and verify they pass
75+
- [x] 11.3 Verify the application builds successfully with `go build ./...`
13.8 KB
Loading
3.68 KB
Loading

internal/app/manager.go

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212

1313
"weatherwidget/internal/config"
1414
"weatherwidget/internal/guard"
15+
"weatherwidget/internal/i18n"
1516
"weatherwidget/internal/scheduler"
1617
"weatherwidget/internal/ui"
1718
"weatherwidget/internal/weather"
@@ -31,6 +32,7 @@ type AppManager struct {
3132
guard *guard.SingleInstanceGuard
3233
cfg *config.Config // current loaded config
3334
dbAdapter *database.DatabaseAdapter
35+
localeMgr *i18n.LocaleManager
3436
}
3537

3638
// NewAppManager creates an AppManager with the given Fyne app and data directory.
@@ -61,8 +63,19 @@ func (a *AppManager) Run() error {
6163
}
6264
a.cfg = cfg
6365

66+
// 2b. Create locale manager and load the configured locale.
67+
lm, err := i18n.NewLocaleManager(i18n.LocaleFS)
68+
if err != nil {
69+
log.Printf("warning: failed to create locale manager: %v", err)
70+
} else {
71+
if cfg.Locale != "" {
72+
_ = lm.SetLocale(cfg.Locale)
73+
}
74+
a.localeMgr = lm
75+
}
76+
6477
// 3. Create UI manager.
65-
a.ui = ui.NewUIManager(a.app)
78+
a.ui = ui.NewUIManager(a.app, a.localeMgr)
6679

6780
// 4. Setup system tray.
6881
a.ui.SetupSystemTray(
@@ -192,6 +205,11 @@ func (a *AppManager) onSettingsSave(newCfg *config.Config) error {
192205

193206
a.cfg = newCfg
194207

208+
// Update locale if it changed.
209+
if oldCfg.Locale != newCfg.Locale && a.localeMgr != nil {
210+
_ = a.localeMgr.SetLocale(newCfg.Locale)
211+
}
212+
195213
// Switch weather provider if data source / credentials changed.
196214
if providerChanged {
197215
// Close old database adapter if switching away from database.

0 commit comments

Comments
 (0)