Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,9 @@ src/arm-none-eabi
node_modules
package.json
package-lock.json

# Local simulation and container virtualization artifacts
build_lv_sim/
InfiniSim/
.podman/
.containers/
60 changes: 60 additions & 0 deletions doc/localization.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Localization

InfiniTime has a small static localization layer for user interface strings. It is designed for firmware use:

- strings are addressed by `Pinetime::Applications::Localization::StringId`
- the selected language is `Pinetime::Applications::Localization::Language`
- translations are stored in fixed `constexpr` tables in `src/displayapp/localization/Localization.cpp`
- callers use `Translate(language, stringId)` to get a `const char*`
- no dynamic allocation is used by the localization layer

The currently supported languages are English and Spanish.

Spanish uses accented characters such as `á`, `é`, `í`, `ó`, `ú`, `ñ`, `¿` and `¡`. The built-in UI font range is configured in:

```text
src/displayapp/fonts/fonts.json
```

`jetbrains_mono_bold_20` includes the Latin-1 range so normal UI labels can render Spanish text correctly on device.

## Where Strings Are Defined

String IDs and supported languages are declared in:

```text
src/displayapp/localization/Localization.h
```

English and Spanish string tables are defined in:

```text
src/displayapp/localization/Localization.cpp
```

The settings controller stores the selected language in the persisted settings data. English is the default.

## Adding a New Language

To add a language:

1. Add a value to the `Language` enum before `Count`.
2. Add a translation table in `Localization.cpp`.
3. Add that table to the `translations` array in the same enum order.
4. Add the language to the selector options in `src/displayapp/screens/settings/SettingLanguage.cpp`.
5. Add translations for every existing `StringId`.
6. Make sure any new characters required by the language are present in the active UI font ranges.

The translation tables use `std::array<const char*, StringCount>`. This makes missing entries fail at compile time when the table size no longer matches the number of string IDs.

## Settings Menu Usage

The Settings app stores menu entries as string IDs instead of hardcoded labels. `src/displayapp/screens/List.cpp` translates those IDs with the current language when the list screen is created.

The language selector is implemented in:

```text
src/displayapp/screens/settings/SettingLanguage.cpp
```

It uses the existing `CheckboxList` pattern and saves the selected language through the settings controller.
5 changes: 4 additions & 1 deletion src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,7 @@ list(APPEND SOURCE_FILES
displayapp/screens/Alarm.cpp
displayapp/screens/Styles.cpp
displayapp/screens/WeatherSymbols.cpp
displayapp/localization/Localization.cpp
displayapp/Colors.cpp
displayapp/widgets/Counter.cpp
displayapp/widgets/PageIndicator.cpp
Expand All @@ -421,6 +422,7 @@ list(APPEND SOURCE_FILES
displayapp/screens/settings/SettingShakeThreshold.cpp
displayapp/screens/settings/SettingBluetooth.cpp
displayapp/screens/settings/SettingOTA.cpp
displayapp/screens/settings/SettingLanguage.cpp

## Watch faces
displayapp/screens/WatchFaceAnalog.cpp
Expand Down Expand Up @@ -611,6 +613,7 @@ set(INCLUDE_FILES
displayapp/screens/FirmwareValidation.h
displayapp/screens/ApplicationList.h
displayapp/screens/CheckboxList.h
displayapp/localization/Localization.h
displayapp/Apps.h
displayapp/screens/Notifications.h
displayapp/screens/HeartRate.h
Expand Down Expand Up @@ -671,6 +674,7 @@ set(INCLUDE_FILES
systemtask/SystemMonitor.h
systemtask/WakeLock.h
displayapp/screens/Symbols.h
displayapp/screens/settings/SettingLanguage.h
drivers/TwiMaster.h
heartratetask/HeartRateTask.h
components/heartrate/Ppg.h
Expand Down Expand Up @@ -1143,4 +1147,3 @@ endif()
if(BUILD_RESOURCES)
add_subdirectory(resources)
endif()

238 changes: 232 additions & 6 deletions src/components/datetime/DateTimeController.cpp

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions src/components/datetime/DateTimeController.h
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,11 @@ namespace Pinetime {
const char* DayOfWeekShortToString() const;
const char* DayOfWeekToString() const;
static const char* MonthShortToStringLow(Months month);
static const char* MonthShortToStringLow(Months month, Controllers::Settings::Language language);
static const char* DayOfWeekShortToStringLow(Days day);
static const char* DayOfWeekShortToStringLow(Days day, Controllers::Settings::Language language);
static const char* DayOfWeekToStringLow(Days day);
static const char* DayOfWeekToStringLow(Days day, Controllers::Settings::Language language);

std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds> CurrentDateTime();

Expand Down
4 changes: 4 additions & 0 deletions src/components/settings/Settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ void Settings::LoadSettingsFromFile() {
fs.FileClose(&settingsFile);
if (bufferSettings.version == settingsVersion) {
settings = bufferSettings;
if (static_cast<uint8_t>(settings.language) >= static_cast<uint8_t>(Language::Count)) {
settings.language = Language::English;
settingsChanged = true;
}
}
}

Expand Down
16 changes: 15 additions & 1 deletion src/components/settings/Settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "components/brightness/BrightnessController.h"
#include "components/fs/FS.h"
#include "displayapp/apps/Apps.h"
#include "displayapp/localization/Localization.h"
#include <nrf_log.h>

namespace Pinetime {
Expand Down Expand Up @@ -41,6 +42,7 @@ namespace Pinetime {
enum class PTSWeather : uint8_t { On, Off };
enum class PrideFlag : uint8_t { Gay, Trans, Bi, Lesbian };
enum class DfuAndFsMode : uint8_t { Disabled, Enabled, EnabledTillReboot };
using Language = Pinetime::Applications::Localization::Language;

struct PineTimeStyle {
Colors ColorTime = Colors::Teal;
Expand Down Expand Up @@ -351,10 +353,21 @@ namespace Pinetime {
settings.heartRateBackgroundPeriod = newIntervalInSeconds.value();
}

void SetLanguage(Language language) {
if (language != settings.language) {
settingsChanged = true;
}
settings.language = language;
}

Language GetLanguage() const {
return settings.language;
}

private:
Pinetime::Controllers::FS& fs;

static constexpr uint32_t settingsVersion = 0x000a;
static constexpr uint32_t settingsVersion = 0x000b;

struct SettingsData {
uint32_t version = settingsVersion;
Expand Down Expand Up @@ -383,6 +396,7 @@ namespace Pinetime {

bool dfuAndFsEnabledOnBoot = false;
uint16_t heartRateBackgroundPeriod = std::numeric_limits<uint16_t>::max(); // Disabled by default
Language language = Language::English;
};

SettingsData settings;
Expand Down
14 changes: 10 additions & 4 deletions src/displayapp/DisplayApp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
#include "displayapp/screens/settings/SettingShakeThreshold.h"
#include "displayapp/screens/settings/SettingBluetooth.h"
#include "displayapp/screens/settings/SettingOTA.h"
#include "displayapp/screens/settings/SettingLanguage.h"

#include "utility/Math.h"

Expand Down Expand Up @@ -549,14 +550,14 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio
settingsController.SetAppMenu(0);
} break;
case Apps::Error:
currentScreen = std::make_unique<Screens::Error>(bootError);
currentScreen = std::make_unique<Screens::Error>(bootError, settingsController);
break;

case Apps::FirmwareValidation:
currentScreen = std::make_unique<Screens::FirmwareValidation>(validator);
currentScreen = std::make_unique<Screens::FirmwareValidation>(validator, settingsController);
break;
case Apps::FirmwareUpdate:
currentScreen = std::make_unique<Screens::FirmwareUpdate>(bleController);
currentScreen = std::make_unique<Screens::FirmwareUpdate>(bleController, settingsController);
break;

case Apps::PassKey:
Expand All @@ -568,6 +569,7 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio
notificationManager,
systemTask->nimble().alertService(),
motorController,
settingsController,
*systemTask,
Screens::Notifications::Modes::Normal);
break;
Expand All @@ -576,6 +578,7 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio
notificationManager,
systemTask->nimble().alertService(),
motorController,
settingsController,
*systemTask,
Screens::Notifications::Modes::Preview);
break;
Expand Down Expand Up @@ -634,8 +637,11 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio
case Apps::SettingOTA:
currentScreen = std::make_unique<Screens::SettingOTA>(this, settingsController);
break;
case Apps::SettingLanguage:
currentScreen = std::make_unique<Screens::SettingLanguage>(this, settingsController);
break;
case Apps::BatteryInfo:
currentScreen = std::make_unique<Screens::BatteryInfo>(batteryController);
currentScreen = std::make_unique<Screens::BatteryInfo>(batteryController, settingsController);
break;
case Apps::SysInfo:
currentScreen = std::make_unique<Screens::SystemInfo>(this,
Expand Down
1 change: 1 addition & 0 deletions src/displayapp/apps/Apps.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ namespace Pinetime {
SettingShakeThreshold,
SettingBluetooth,
SettingOTA,
SettingLanguage,
Error
};

Expand Down
2 changes: 1 addition & 1 deletion src/displayapp/fonts/fonts.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"sources": [
{
"file": "JetBrainsMono-Bold.ttf",
"range": "0x20-0x7e, 0x410-0x44f, 0xB0"
"range": "0x20-0x7e, 0xA1, 0xBF, 0xC0-0xFF, 0x410-0x44f, 0xB0"
},
{
"file": "FontAwesome5-Solid+Brands+Regular.woff",
Expand Down
Loading
Loading