Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
634d261
feat: add UI translation system (i18n) with live language switching
foXaCe May 21, 2026
6b31da4
feat(i18n): translate in-game Frontend options (ma_config.c)
foXaCe May 21, 2026
932cbc2
feat(i18n): translate launcher root labels (Tools, Recently Played, C…
foXaCe May 21, 2026
b8f7ef5
feat(i18n): translate pak / Tools folder names via Entry_new fallthrough
foXaCe May 23, 2026
9f5dda2
i18n: stop translating third-party pak names (Aesthetics, Updater, ...)
foXaCe May 23, 2026
5ead184
feat(i18n): translate Battery tool body (labels + zoom title)
foXaCe May 23, 2026
56e0cf9
i18n(fr): shorter Battery zoom title ('Utilisation', not 'Utilisation…
foXaCe May 23, 2026
cae7c1f
i18n: also translate Files / Gallery (generic descriptive names)
foXaCe May 23, 2026
6772b05
i18n: add re-translation keys consumed by minui-presenter
foXaCe May 23, 2026
77b5cd3
i18n: add minui-presenter retranslate keys for empty/error messages
foXaCe May 23, 2026
b30c326
i18n(clock): make date field order locale-aware
foXaCe May 23, 2026
87fb002
i18n: add Pak Store keys (ps.*) for FR localisation
foXaCe May 23, 2026
366fab5
i18n: add ScrapeGoat keys (sg.*) + multi-line message support
foXaCe May 23, 2026
81d6c2a
i18n(sg): add sg.tag.* keys (queue badges)
foXaCe May 23, 2026
712ce9a
i18n: add Aesthetics (ae.*) + Pak Store download manager keys
foXaCe May 23, 2026
2961cf9
i18n(ae): add download manager + theme management keys
foXaCe May 24, 2026
db09f5c
i18n(sg): add 9 ScrapeGoat keys + harmonise FR to tutoiement
foXaCe May 24, 2026
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
1,068 changes: 1,068 additions & 0 deletions skeleton/SYSTEM/res/lang/en.lang

Large diffs are not rendered by default.

1,071 changes: 1,071 additions & 0 deletions skeleton/SYSTEM/res/lang/fr.lang

Large diffs are not rendered by default.

28 changes: 17 additions & 11 deletions workspace/all/battery/battery.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "defines.h"
#include "api.h"
#include "utils.h"
#include "i18n.h"

#include <sqlite3.h>
#include <batmondb.h>
Expand Down Expand Up @@ -108,7 +109,8 @@ static int estimation_line_size = 0;
static int begining_session_index;
static char session_duration[10];
static char current_percentage[10];
static char session_left[12] = "calculating";
// Buffer sized for translated "calculating" (e.g. FR "calcul en cours" = 15 chars).
static char session_left[32] = "calculating";
static char session_best[10];

static void sigHandler(int sig)
Expand Down Expand Up @@ -465,16 +467,16 @@ void renderPage()
drawBatteryIcon(0, (SDL_Rect){graph.layout.icon_x, graph.layout.icon4_y});

char text_line[255];
sprintf(text_line, "Since Charge: %s", session_duration);
snprintf(text_line, sizeof(text_line), T("battery.since_charge_fmt"), session_duration);
renderText(text_line, font.medium, COLOR_WHITE, &(SDL_Rect){graph.layout.label_session_x, graph.layout.label_session_y, graph.layout.label_size_x, graph.layout.label_size_y});

sprintf(text_line, "Current: %s", current_percentage);
snprintf(text_line, sizeof(text_line), T("battery.current_fmt"), current_percentage);
renderText(text_line, font.medium, COLOR_WHITE, &(SDL_Rect){graph.layout.label_current_x, graph.layout.label_current_y, graph.layout.label_size_x, graph.layout.label_size_y});

sprintf(text_line, "Remaining: %s", session_left);
snprintf(text_line, sizeof(text_line), T("battery.remaining_fmt"), session_left);
renderTextAlignRight(text_line, font.medium, COLOR_WHITE, &(SDL_Rect){graph.layout.label_left_x, graph.layout.label_left_y, graph.layout.label_size_x, graph.layout.label_size_y});

sprintf(text_line, "Longest: %s", session_best);
snprintf(text_line, sizeof(text_line), T("battery.longest_fmt"), session_best);
renderTextAlignRight(text_line, font.medium, COLOR_WHITE, &(SDL_Rect){graph.layout.label_best_x, graph.layout.label_best_y, graph.layout.label_size_x, graph.layout.label_size_y});

int half_line_width = (int)(GRAPH_LINE_WIDTH) / 2;
Expand Down Expand Up @@ -641,6 +643,10 @@ int main(int argc, char *argv[])
device_model = PLAT_getModel();

screen = GFX_init(MODE_MAIN);
// GFX_init has loaded the active language; refresh the "calculating"
// placeholder so it shows up translated until the real estimate is ready.
strncpy(session_left, T("battery.calculating"), sizeof(session_left) - 1);
session_left[sizeof(session_left) - 1] = '\0';
PAD_init();
PWR_init();

Expand Down Expand Up @@ -732,16 +738,16 @@ int main(int argc, char *argv[])
switch (current_zoom)
{
case 0:
sprintf(display_name, "Battery usage: Last %s", "16 hours");
snprintf(display_name, sizeof(display_name), T("battery.usage_fmt"), T("battery.range.16h"));
break;
case 1:
sprintf(display_name, "Battery usage: Last %s", "8 hours");
snprintf(display_name, sizeof(display_name), T("battery.usage_fmt"), T("battery.range.8h"));
break;
case 2:
sprintf(display_name, "Battery usage: Last %s", "4 hours");
snprintf(display_name, sizeof(display_name), T("battery.usage_fmt"), T("battery.range.4h"));
break;
default:
sprintf(display_name, "Battery usage: Last %s", "8 hours");
snprintf(display_name, sizeof(display_name), T("battery.usage_fmt"), T("battery.range.8h"));
break;
}

Expand All @@ -762,9 +768,9 @@ int main(int argc, char *argv[])
if (show_setting)
GFX_blitHardwareHints(screen, show_setting);
else
GFX_blitButtonGroup((char *[]){"L/R", "SCROLL", "L1/R1", "ZOOM", NULL}, 0, screen, 0);
GFX_blitButtonGroup((char *[]){T("btn.left_right"), T("btn.scroll"), "L1/R1", T("btn.zoom"), NULL}, 0, screen, 0);

GFX_blitButtonGroup((char *[]){"B", "BACK", NULL}, 1, screen, 1);
GFX_blitButtonGroup((char *[]){"B", T("btn.back"), NULL}, 1, screen, 1);

GFX_flip(screen);
dirty = 0;
Expand Down
2 changes: 1 addition & 1 deletion workspace/all/battery/makefile
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ SDL?=SDL

TARGET = battery
INCDIR = -I. -I../common/ -I../../$(PLATFORM)/platform/
SOURCE = $(TARGET).c ../common/utils.c ../common/api.c ../common/config.c ../common/scaler.c ../../$(PLATFORM)/platform/platform.c
SOURCE = $(TARGET).c ../common/utils.c ../common/api.c ../common/config.c ../common/scaler.c ../common/i18n.c ../../$(PLATFORM)/platform/platform.c

CC = $(CROSS_COMPILE)gcc
CFLAGS += $(OPT)
Expand Down
5 changes: 3 additions & 2 deletions workspace/all/bootlogo/bootlogo.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "defines.h"
#include "api.h"
#include "utils.h"
#include "i18n.h"

static bool quit = false;

Expand Down Expand Up @@ -179,8 +180,8 @@ int main(int argc, char *argv[])
SDL_BlitSurface(image, NULL, screen, &image_rect);
}

GFX_blitButtonGroup((char *[]){"L/R", "SCROLL", NULL}, 0, screen, 0);
GFX_blitButtonGroup((char *[]){"A", "SET", "B", "BACK", NULL}, 1, screen, 1);
GFX_blitButtonGroup((char *[]){T("btn.left_right"), T("btn.scroll"), NULL}, 0, screen, 0);
GFX_blitButtonGroup((char *[]){"A", T("btn.set"), "B", T("btn.back"), NULL}, 1, screen, 1);

GFX_flip(screen);
dirty = 0;
Expand Down
2 changes: 1 addition & 1 deletion workspace/all/bootlogo/makefile
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ SDL?=SDL

TARGET = bootlogo
INCDIR = -I. -I../common/ -I../../$(PLATFORM)/platform/
SOURCE = $(TARGET).c ../common/utils.c ../common/api.c ../common/config.c ../common/scaler.c ../../$(PLATFORM)/platform/platform.c
SOURCE = $(TARGET).c ../common/utils.c ../common/api.c ../common/config.c ../common/scaler.c ../common/i18n.c ../../$(PLATFORM)/platform/platform.c

CC = $(CROSS_COMPILE)gcc
CFLAGS += $(OPT)
Expand Down
45 changes: 33 additions & 12 deletions workspace/all/clock/clock.c
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
// loosely based on https://github.com/gameblabla/clock_sdl_app

#include <stdio.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <msettings.h>

#include "defines.h"
#include "api.h"
#include "utils.h"
#include "i18n.h"

enum {
CURSOR_YEAR,
Expand Down Expand Up @@ -139,6 +141,16 @@ int main(int argc , char* argv[]) {

int option_count = 7;

// Date field order is localised via the clock.date_order key.
// Values: "YMD" (default / ISO), "DMY" (FR/UK/...), "MDY" (US).
int date_order[3] = { CURSOR_YEAR, CURSOR_MONTH, CURSOR_DAY };
const char *date_order_str = T("clock.date_order");
if (strcmp(date_order_str, "DMY") == 0) {
date_order[0] = CURSOR_DAY; date_order[1] = CURSOR_MONTH; date_order[2] = CURSOR_YEAR;
} else if (strcmp(date_order_str, "MDY") == 0) {
date_order[0] = CURSOR_MONTH; date_order[1] = CURSOR_DAY; date_order[2] = CURSOR_YEAR;
}

int dirty = 1;
int show_setting = 0;
int was_online = PWR_isOnline();
Expand Down Expand Up @@ -254,9 +266,9 @@ int main(int argc , char* argv[]) {
GFX_blitHardwareGroup(screen, show_setting);

if (show_setting) GFX_blitHardwareHints(screen, show_setting);
else GFX_blitButtonGroup((char*[]){ "SELECT",show_24hour?"12 HOUR":"24 HOUR", NULL }, 0, screen, 0);
else GFX_blitButtonGroup((char*[]){ "SELECT",show_24hour?T("btn.hr_12"):T("btn.hr_24"), NULL }, 0, screen, 0);

GFX_blitButtonGroup((char*[]){ "B","CANCEL", "A","SET", NULL }, 1, screen, 1);
GFX_blitButtonGroup((char*[]){ "B",T("btn.cancel"), "A",T("btn.set"), NULL }, 1, screen, 1);

// 376 or 446 (@2x)
// 188 or 223 (@1x)
Expand All @@ -265,12 +277,15 @@ int main(int argc , char* argv[]) {
// datetime
int x = ox;
int y = SCALE1((((FIXED_HEIGHT / FIXED_SCALE)-PILL_SIZE-DIGIT_HEIGHT)/2));

x = blitNumber(year_selected, x,y);
x = blit(CHAR_SLASH, x,y);
x = blitNumber(month_selected, x,y);
x = blit(CHAR_SLASH, x,y);
x = blitNumber(day_selected, x,y);

for (int i = 0; i < 3; ++i) {
int field = date_order[i];
int value = (field == CURSOR_YEAR) ? (int)year_selected
: (field == CURSOR_MONTH) ? month_selected
: day_selected;
x = blitNumber(value, x, y);
if (i < 2) x = blit(CHAR_SLASH, x, y);
}
x += SCALE1(10); // space

am_selected = hour_selected < 12;
Expand All @@ -294,7 +309,7 @@ int main(int argc , char* argv[]) {
int ampm_w;
if (!show_24hour) {
x += SCALE1(10); // space
SDL_Surface* text = TTF_RenderUTF8_Blended(font.large, am_selected ? "AM" : "PM", COLOR_WHITE);
SDL_Surface* text = TTF_RenderUTF8_Blended(font.large, am_selected ? T("clock.am") : T("clock.pm"), COLOR_WHITE);
ampm_w = text->w + SCALE1(2);
SDL_BlitSurface(text, NULL, screen, &(SDL_Rect){x,y-SCALE1(3)});
SDL_FreeSurface(text);
Expand All @@ -303,9 +318,15 @@ int main(int argc , char* argv[]) {
// cursor
x = ox;
y += SCALE1(19);
if (select_cursor!=CURSOR_YEAR) {
x += SCALE1(50); // YYYY/
x += (select_cursor - 1) * SCALE1(30);
if (select_cursor <= CURSOR_DAY) {
int dx = 0;
for (int i = 0; i < 3; ++i) {
if (date_order[i] == select_cursor) { x += dx; break; }
dx += (date_order[i] == CURSOR_YEAR ? SCALE1(40) : SCALE1(20)) + SCALE1(10);
}
} else {
// after the date block (always 100 wide) + space
x += SCALE1(100) + SCALE1(10) + (select_cursor - CURSOR_HOUR) * SCALE1(30);
}
blitBar(x,y, (select_cursor==CURSOR_YEAR ? SCALE1(40) : (select_cursor==CURSOR_AMPM ? ampm_w : SCALE1(20))));

Expand Down
2 changes: 1 addition & 1 deletion workspace/all/clock/makefile
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ SDL?=SDL

TARGET = clock
INCDIR = -I. -I../common/ -I../../$(PLATFORM)/platform/
SOURCE = $(TARGET).c ../common/utils.c ../common/api.c ../common/config.c ../common/scaler.c ../../$(PLATFORM)/platform/platform.c
SOURCE = $(TARGET).c ../common/utils.c ../common/api.c ../common/config.c ../common/scaler.c ../common/i18n.c ../../$(PLATFORM)/platform/platform.c

CC = $(CROSS_COMPILE)gcc
CFLAGS += $(OPT)
Expand Down
11 changes: 8 additions & 3 deletions workspace/all/common/api.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

#include "utils.h"
#include "config.h"
#include "i18n.h"

#include <pthread.h>

Expand Down Expand Up @@ -351,6 +352,10 @@ SDL_Surface *GFX_init(int mode)

CFG_init(GFX_loadSystemFont, GFX_updateColors);

// i18n loads the language saved in settings; falls back to English when
// the file is missing. Strings are dispatched at runtime via T("key").
I18N_init(CFG_getLanguage());

// by default, we will clear with whatever background color the user prefers
// if MODE_MENU /e.g. minarch, clear with default black)
if(mode == MODE_MAIN)
Expand Down Expand Up @@ -2157,11 +2162,11 @@ void GFX_blitHardwareHints(SDL_Surface *dst, int show_setting)
{

if (show_setting == 1)
GFX_blitButtonGroup((char *[]){BRIGHTNESS_BUTTON_LABEL, "BRIGHTNESS", NULL}, 0, dst, 0);
GFX_blitButtonGroup((char *[]){BRIGHTNESS_BUTTON_LABEL, T("hw.brightness"), NULL}, 0, dst, 0);
else if (show_setting == 3)
GFX_blitButtonGroup((char *[]){BRIGHTNESS_BUTTON_LABEL, "COLOR TEMP", NULL}, 0, dst, 0);
GFX_blitButtonGroup((char *[]){BRIGHTNESS_BUTTON_LABEL, T("hw.color_temp"), NULL}, 0, dst, 0);
else
GFX_blitButtonGroup((char *[]){"MNU", "BRGHT", "SEL", "CLTMP", NULL}, 0, dst, 0);
GFX_blitButtonGroup((char *[]){T("hw.mnu"), T("hw.brght"), T("hw.sel"), T("hw.cltmp"), NULL}, 0, dst, 0);
}

int GFX_blitButtonGroup(char **pairs, int primary, SDL_Surface *dst, int align_right)
Expand Down
24 changes: 24 additions & 0 deletions workspace/all/common/config.c
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ void CFG_defaults(NextUISettings *cfg)
.raProgressNotificationDuration = CFG_DEFAULT_RA_PROGRESS_NOTIFICATION_DURATION,
.raAchievementSortOrder = CFG_DEFAULT_RA_ACHIEVEMENT_SORT_ORDER,

.language = "en",

};

*cfg = defaults;
Expand Down Expand Up @@ -406,6 +408,14 @@ void CFG_init(FontLoad_callback_t cb, ColorSet_callback_t ccb)
CFG_setRAAchievementSortOrder(temp_value);
continue;
}
{
char lang_buf[16];
if (sscanf(line, "language=%15s", lang_buf) == 1)
{
CFG_setLanguage(lang_buf);
continue;
}
}
}
fclose(file);
}
Expand Down Expand Up @@ -1067,6 +1077,19 @@ void CFG_setRAAchievementSortOrder(int sortOrder)
CFG_sync();
}

const char* CFG_getLanguage(void)
{
return settings.language[0] ? settings.language : "en";
}

void CFG_setLanguage(const char* code)
{
if (!code || !*code) code = "en";
strncpy(settings.language, code, sizeof(settings.language) - 1);
settings.language[sizeof(settings.language) - 1] = '\0';
CFG_sync();
}

void CFG_get(const char *key, char *value)
{
if (strcmp(key, "font") == 0)
Expand Down Expand Up @@ -1310,6 +1333,7 @@ void CFG_sync(void)
fprintf(file, "raNotificationDuration=%i\n", settings.raNotificationDuration);
fprintf(file, "raProgressNotificationDuration=%i\n", settings.raProgressNotificationDuration);
fprintf(file, "raAchievementSortOrder=%i\n", settings.raAchievementSortOrder);
fprintf(file, "language=%s\n", CFG_getLanguage());

fclose(file);
}
Expand Down
7 changes: 7 additions & 0 deletions workspace/all/common/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,9 @@ typedef struct
int raProgressNotificationDuration; // Duration for progress notifications (0-5 seconds, 0 = disabled)
int raAchievementSortOrder; // Sort order for achievements list (RA_SORT_* enum)

// Language
char language[8]; // ISO-like code, e.g. "en", "fr"

} NextUISettings;

// Transition mode constants
Expand Down Expand Up @@ -400,6 +403,10 @@ void CFG_setRAProgressNotificationDuration(int seconds);
int CFG_getRAAchievementSortOrder(void);
void CFG_setRAAchievementSortOrder(int sortOrder);

// Language (i18n)
const char* CFG_getLanguage(void);
void CFG_setLanguage(const char* code);

void CFG_sync(void);
void CFG_quit(void);

Expand Down
Loading