diff --git a/assets/locale/en_US.po b/assets/locale/en_US.po index 8928341d..302010d9 100644 --- a/assets/locale/en_US.po +++ b/assets/locale/en_US.po @@ -60,6 +60,58 @@ msgstr "Settings" msgid "ui.game_menu.save" msgstr "Save" +#: src/dusk/ui/frame/game/uigamemenu.c +msgid "ui.game_menu.save_success" +msgstr "Game saved." + +#: src/dusk/ui/frame/game/uigamemenu.c +msgid "ui.game_menu.save_cancelled" +msgstr "Save cancelled." + +#: src/dusk/ui/frame/game/uigamemenu.c +msgid "ui.game_menu.save_unavailable" +msgstr "Can't save - no save device found." + +#: src/dusk/ui/frame/game/uigamemenu.c +msgid "ui.game_menu.save_temporary" +msgstr "This session is temporary - no save device was found, so saving is disabled." + +#: src/dusk/ui/frame/game/uigamemenu.c +msgid "ui.game_menu.save_create_confirm" +msgstr "No save data found. Create a new save?" + +#: src/dusk/ui/frame/game/uigamemenu.c +msgid "ui.game_menu.save_failed_format" +msgstr "Save failed: %s" + +#: src/dusk/ui/frame/game/uigamemenu.c +msgid "ui.game_menu.save_check_failed_format" +msgstr "Can't save: %s" + +#: src/dusk/ui/frame/initial/uiinitialnocard.c +msgid "ui.initial.no_card.message" +msgstr "No save device found. You can continue, but\nprogress will not be saved." + +#: src/dusk/ui/frame/initial/uiinitialnocard.c +msgid "ui.initial.no_card.retry" +msgstr "Retry" + +#: src/dusk/ui/frame/initial/uiinitialnocard.c +msgid "ui.initial.no_card.continue" +msgstr "Continue Anyway" + +#: src/dusk/ui/frame/initial/uiinitialcreatesave.c +msgid "ui.initial.create_save.message" +msgstr "No save data found. Create a new save?" + +#: src/dusk/ui/frame/initial/uiinitialcreatesave.c +msgid "ui.initial.create_save.yes" +msgstr "Yes" + +#: src/dusk/ui/frame/initial/uiinitialcreatesave.c +msgid "ui.initial.create_save.no" +msgstr "No" + msgid "item.potion.name" msgstr "Potion" diff --git a/assets/locale/es_MX.po b/assets/locale/es_MX.po index 6376afaf..ef02bd19 100644 --- a/assets/locale/es_MX.po +++ b/assets/locale/es_MX.po @@ -61,6 +61,58 @@ msgstr "Configuración" msgid "ui.game_menu.save" msgstr "Guardar" +#: src/dusk/ui/frame/game/uigamemenu.c +msgid "ui.game_menu.save_success" +msgstr "Partida guardada." + +#: src/dusk/ui/frame/game/uigamemenu.c +msgid "ui.game_menu.save_cancelled" +msgstr "Guardado cancelado." + +#: src/dusk/ui/frame/game/uigamemenu.c +msgid "ui.game_menu.save_unavailable" +msgstr "No se puede guardar: no se encontró ningún dispositivo de guardado." + +#: src/dusk/ui/frame/game/uigamemenu.c +msgid "ui.game_menu.save_temporary" +msgstr "Esta sesión es temporal - no se encontró ningún dispositivo de guardado, por lo que guardar está deshabilitado." + +#: src/dusk/ui/frame/game/uigamemenu.c +msgid "ui.game_menu.save_create_confirm" +msgstr "No se encontraron datos guardados. ¿Crear una partida nueva?" + +#: src/dusk/ui/frame/game/uigamemenu.c +msgid "ui.game_menu.save_failed_format" +msgstr "Error al guardar: %s" + +#: src/dusk/ui/frame/game/uigamemenu.c +msgid "ui.game_menu.save_check_failed_format" +msgstr "No se puede guardar: %s" + +#: src/dusk/ui/frame/initial/uiinitialnocard.c +msgid "ui.initial.no_card.message" +msgstr "No se encontró ningún dispositivo de guardado. Puedes continuar, pero\nel progreso no se guardará." + +#: src/dusk/ui/frame/initial/uiinitialnocard.c +msgid "ui.initial.no_card.retry" +msgstr "Reintentar" + +#: src/dusk/ui/frame/initial/uiinitialnocard.c +msgid "ui.initial.no_card.continue" +msgstr "Continuar de todos modos" + +#: src/dusk/ui/frame/initial/uiinitialcreatesave.c +msgid "ui.initial.create_save.message" +msgstr "No se encontraron datos guardados. ¿Crear una partida nueva?" + +#: src/dusk/ui/frame/initial/uiinitialcreatesave.c +msgid "ui.initial.create_save.yes" +msgstr "Sí" + +#: src/dusk/ui/frame/initial/uiinitialcreatesave.c +msgid "ui.initial.create_save.no" +msgstr "No" + #: src/dusk/rpg/item/item.json msgid "item.potion.name" msgstr "Poción" diff --git a/assets/locale/jp_JP.po b/assets/locale/jp_JP.po index ae9b9ad1..21f2c8b8 100644 --- a/assets/locale/jp_JP.po +++ b/assets/locale/jp_JP.po @@ -61,6 +61,58 @@ msgstr "設定" msgid "ui.game_menu.save" msgstr "セーブ" +#: src/dusk/ui/frame/game/uigamemenu.c +msgid "ui.game_menu.save_success" +msgstr "セーブしました。" + +#: src/dusk/ui/frame/game/uigamemenu.c +msgid "ui.game_menu.save_cancelled" +msgstr "セーブをキャンセルしました。" + +#: src/dusk/ui/frame/game/uigamemenu.c +msgid "ui.game_menu.save_unavailable" +msgstr "セーブできません - セーブデバイスが見つかりません。" + +#: src/dusk/ui/frame/game/uigamemenu.c +msgid "ui.game_menu.save_temporary" +msgstr "このセッションは一時的です - セーブデバイスが見つからなかったため、セーブは無効になっています。" + +#: src/dusk/ui/frame/game/uigamemenu.c +msgid "ui.game_menu.save_create_confirm" +msgstr "セーブデータが見つかりません。新しいセーブを作成しますか?" + +#: src/dusk/ui/frame/game/uigamemenu.c +msgid "ui.game_menu.save_failed_format" +msgstr "セーブに失敗しました: %s" + +#: src/dusk/ui/frame/game/uigamemenu.c +msgid "ui.game_menu.save_check_failed_format" +msgstr "セーブできません: %s" + +#: src/dusk/ui/frame/initial/uiinitialnocard.c +msgid "ui.initial.no_card.message" +msgstr "セーブデバイスが見つかりません。続行できますが、\n進行状況は保存されません。" + +#: src/dusk/ui/frame/initial/uiinitialnocard.c +msgid "ui.initial.no_card.retry" +msgstr "再試行" + +#: src/dusk/ui/frame/initial/uiinitialnocard.c +msgid "ui.initial.no_card.continue" +msgstr "続行する" + +#: src/dusk/ui/frame/initial/uiinitialcreatesave.c +msgid "ui.initial.create_save.message" +msgstr "セーブデータが見つかりません。新しいセーブを作成しますか?" + +#: src/dusk/ui/frame/initial/uiinitialcreatesave.c +msgid "ui.initial.create_save.yes" +msgstr "はい" + +#: src/dusk/ui/frame/initial/uiinitialcreatesave.c +msgid "ui.initial.create_save.no" +msgstr "いいえ" + #: src/dusk/rpg/item/item.json msgid "item.potion.name" msgstr "ポーション" diff --git a/src/dusk/locale/localemanager.c b/src/dusk/locale/localemanager.c index e13095b3..cef38a0d 100644 --- a/src/dusk/locale/localemanager.c +++ b/src/dusk/locale/localemanager.c @@ -7,16 +7,42 @@ #include "localemanager.h" #include "util/memory.h" +#include "util/string.h" #include "assert/assert.h" +#include "save/save.h" localemanager_t LOCALE; +const localeinfo_t * const LOCALE_LIST[LOCALE_LIST_COUNT] = { + &LOCALE_EN_US, + &LOCALE_JP_JP, + &LOCALE_ES_MX +}; + errorret_t localeManagerInit() { memoryZero(&LOCALE, sizeof(localemanager_t)); - errorChain(localeManagerSetLocale(&LOCALE_EN_US)); + // saveInit() runs before this (see engine.c) and, on most platforms, + // has already loaded a persisted language choice into savemeta_t by + // this point - see localeManagerGetByIndex(). + errorChain(localeManagerSetLocale( + localeManagerGetByIndex(saveGetMeta()->language) + )); errorOk(); } +uint8_t localeManagerGetIndex(const localeinfo_t *locale) { + assertNotNull(locale, "Locale cannot be NULL"); + for(uint8_t i = 0; i < LOCALE_LIST_COUNT; i++) { + if(stringCompare(LOCALE_LIST[i]->file, locale->file) == 0) return i; + } + return 0; +} + +const localeinfo_t * localeManagerGetByIndex(const uint8_t index) { + if(index >= LOCALE_LIST_COUNT) return LOCALE_LIST[0]; + return LOCALE_LIST[index]; +} + errorret_t localeManagerSetLocale(const localeinfo_t *locale) { assertNotNull(locale, "Locale cannot be NULL"); diff --git a/src/dusk/locale/localemanager.h b/src/dusk/locale/localemanager.h index ca1a10a3..836d3372 100644 --- a/src/dusk/locale/localemanager.h +++ b/src/dusk/locale/localemanager.h @@ -18,21 +18,51 @@ typedef struct { extern localemanager_t LOCALE; +/** + * Every locale the game supports, in a fixed, stable order - index into + * this array is what gets persisted as savemeta_t.language (see + * save/savemeta.h), so the order here must never change once shipped + * (only append new locales at the end). + */ +#define LOCALE_LIST_COUNT 3 +extern const localeinfo_t * const LOCALE_LIST[LOCALE_LIST_COUNT]; + /** * Initialize the locale system. - * + * * @return An error code if a failure occurs. */ errorret_t localeManagerInit(); /** * Set the current locale. - * + * * @param locale The locale to set. * @return An error code if a failure occurs. */ errorret_t localeManagerSetLocale(const localeinfo_t *locale); +/** + * Finds the LOCALE_LIST index matching the given locale's file, by + * content rather than pointer identity (localeinfo_t instances are + * declared `static const` in a header, so the same locale gets a + * distinct pointer in every translation unit that references it). + * + * @param locale The locale to find. + * @return The matching index in LOCALE_LIST, or 0 if not found. + */ +uint8_t localeManagerGetIndex(const localeinfo_t *locale); + +/** + * Gets the locale at the given LOCALE_LIST index, clamping to index 0 + * (LOCALE_EN_US) if out of range - e.g. a save file's persisted language + * index from a future build with more locales than this one supports. + * + * @param index The index to look up. + * @return The locale at that index, or LOCALE_LIST[0] if out of range. + */ +const localeinfo_t * localeManagerGetByIndex(const uint8_t index); + /** * Get a localized string for the given message ID. * diff --git a/src/dusk/save/save.c b/src/dusk/save/save.c index a236f377..202579ff 100644 --- a/src/dusk/save/save.c +++ b/src/dusk/save/save.c @@ -20,6 +20,7 @@ static void _saveEagerLoadComplete(errorret_t result, void *user) { errorret_t saveInit(void) { memoryZero(&SAVE, sizeof(save_t)); SAVE.meta.deadzone = SAVE_META_DEADZONE_DEFAULT; + SAVE.meta.language = SAVE_META_LANGUAGE_DEFAULT; #ifdef saveInitPlatform // A missing/unreachable save medium is expected, recoverable state, diff --git a/src/dusk/save/savemeta.h b/src/dusk/save/savemeta.h index 82ecef5d..895e4fdb 100644 --- a/src/dusk/save/savemeta.h +++ b/src/dusk/save/savemeta.h @@ -25,6 +25,13 @@ */ #define SAVE_META_DEADZONE_DEFAULT 0.1f +/** + * Default language index for meta that's never actually been loaded from + * disk yet - index 0 into LOCALE_LIST (see locale/localemanager.h), i.e. + * LOCALE_EN_US. + */ +#define SAVE_META_LANGUAGE_DEFAULT 0 + /** * Device/user-wide preferences, independent of any individual game save * slot (see saveslot.h) - there's exactly one of these, not one per slot, @@ -44,4 +51,13 @@ typedef struct { * rather than caching it anywhere else. */ float_t deadzone; + /** + * Index into LOCALE_LIST (see locale/localemanager.h) for the player's + * preferred UI language - the save meta is the only place this lives; + * read it directly via saveGetMeta()->language rather than caching it + * anywhere else. Never reorder/remove entries from LOCALE_LIST, only + * append, since this index must keep meaning the same locale across + * versions. + */ + uint8_t language; } savemeta_t; diff --git a/src/dusk/save/savestream.c b/src/dusk/save/savestream.c index 6f652e2d..2da1b070 100644 --- a/src/dusk/save/savestream.c +++ b/src/dusk/save/savestream.c @@ -340,6 +340,7 @@ errorret_t saveMetaSerializeRead(savestream_t *stream, savemeta_t *meta) { ); saveFileReadVersion(stream, &meta->version); saveFileReadFloat(stream, &meta->deadzone); + saveFileReadUInt8(stream, &meta->language); errorChain(saveStreamVerifyChecksumImpl(stream, "Save meta")); meta->exists = true; errorOk(); @@ -354,6 +355,7 @@ errorret_t saveMetaSerializeWrite(savestream_t *stream, savemeta_t *meta) { saveFileWriteHeader(stream, meta->header, SAVE_META_HEADER_SIZE); saveFileWriteVersion(stream, &meta->version); saveFileWriteFloat(stream, &meta->deadzone); + saveFileWriteUInt8(stream, &meta->language); errorChain(saveStreamFinalizeWriteImpl( stream, headerPosition, SAVE_META_HEADER_SIZE )); diff --git a/src/dusk/ui/frame/game/uigamemenu.c b/src/dusk/ui/frame/game/uigamemenu.c index 795f9a3d..e587461c 100644 --- a/src/dusk/ui/frame/game/uigamemenu.c +++ b/src/dusk/ui/frame/game/uigamemenu.c @@ -33,19 +33,19 @@ static void uiGameMenuSaveWriteComplete(errorret_t result, void *user) { // the longest platform save-error message plus this prefix. char_t msg[256]; stringFormat( - msg, sizeof(msg), "Save failed: %s", result.state->message + msg, sizeof(msg), UI_GAME_MENU.saveFailedFormat, result.state->message ); errorCatch(result); uiTextboxMainSetText(msg); return; } - uiTextboxMainSetText("Game saved."); + uiTextboxMainSetText(UI_GAME_MENU.saveSuccessText); } static void uiGameMenuSaveCreateConfirmed(const bool_t confirmed, void *user) { if(!confirmed) { - uiTextboxMainSetText("Save cancelled."); + uiTextboxMainSetText(UI_GAME_MENU.saveCancelledText); return; } @@ -62,7 +62,10 @@ static void uiGameMenuSaveCreateConfirmed(const bool_t confirmed, void *user) { static void uiGameMenuSaveCheckComplete(errorret_t result, void *user) { if(errorIsNotOk(result)) { char_t msg[256]; - stringFormat(msg, sizeof(msg), "Can't save: %s", result.state->message); + stringFormat( + msg, sizeof(msg), UI_GAME_MENU.saveCheckFailedFormat, + result.state->message + ); errorCatch(result); uiTextboxMainSetText(msg); return; @@ -74,7 +77,7 @@ static void uiGameMenuSaveCheckComplete(errorret_t result, void *user) { } uiConfirmOpen( - "No save data found. Create a new save?", + UI_GAME_MENU.saveCreateConfirmText, uiGameMenuSaveCreateConfirmed, NULL ); @@ -82,14 +85,11 @@ static void uiGameMenuSaveCheckComplete(errorret_t result, void *user) { static void uiGameMenuSave(void) { if(saveIsTemporary()) { - uiTextboxMainSetText( - "This session is temporary - no save device was found, so saving " - "is disabled." - ); + uiTextboxMainSetText(UI_GAME_MENU.saveTemporaryText); return; } if(!saveIsAvailable()) { - uiTextboxMainSetText("Can't save - no save device found."); + uiTextboxMainSetText(UI_GAME_MENU.saveUnavailableText); return; } if(saveIsBusy()) return;// A save/load dialog (e.g. on PSP) is already up. @@ -140,6 +140,55 @@ errorret_t uiGameMenuInit(void) { UI_GAME_MENU.saveLabel, UI_GAME_MENU_LABEL_MAX )); + errorChain(assetLocaleGetString( + &LOCALE.entry->data.locale, + "ui.game_menu.save_success", + 0, + UI_GAME_MENU.saveSuccessText, + UI_GAME_MENU_MESSAGE_MAX + )); + errorChain(assetLocaleGetString( + &LOCALE.entry->data.locale, + "ui.game_menu.save_cancelled", + 0, + UI_GAME_MENU.saveCancelledText, + UI_GAME_MENU_MESSAGE_MAX + )); + errorChain(assetLocaleGetString( + &LOCALE.entry->data.locale, + "ui.game_menu.save_unavailable", + 0, + UI_GAME_MENU.saveUnavailableText, + UI_GAME_MENU_MESSAGE_MAX + )); + errorChain(assetLocaleGetString( + &LOCALE.entry->data.locale, + "ui.game_menu.save_temporary", + 0, + UI_GAME_MENU.saveTemporaryText, + UI_GAME_MENU_MESSAGE_MAX + )); + errorChain(assetLocaleGetString( + &LOCALE.entry->data.locale, + "ui.game_menu.save_create_confirm", + 0, + UI_GAME_MENU.saveCreateConfirmText, + UI_GAME_MENU_MESSAGE_MAX + )); + errorChain(assetLocaleGetString( + &LOCALE.entry->data.locale, + "ui.game_menu.save_failed_format", + 0, + UI_GAME_MENU.saveFailedFormat, + UI_GAME_MENU_MESSAGE_MAX + )); + errorChain(assetLocaleGetString( + &LOCALE.entry->data.locale, + "ui.game_menu.save_check_failed_format", + 0, + UI_GAME_MENU.saveCheckFailedFormat, + UI_GAME_MENU_MESSAGE_MAX + )); MENU_BEGIN( &UI_GAME_MENU.menu, UI_GAME_MENU.items, uiGameMenuSelected, NULL, NULL diff --git a/src/dusk/ui/frame/game/uigamemenu.h b/src/dusk/ui/frame/game/uigamemenu.h index 171fa967..87ec2a4e 100644 --- a/src/dusk/ui/frame/game/uigamemenu.h +++ b/src/dusk/ui/frame/game/uigamemenu.h @@ -12,6 +12,7 @@ #define UI_GAME_MENU_ITEM_COUNT 4 #define UI_GAME_MENU_WIDTH 150.0f #define UI_GAME_MENU_LABEL_MAX 32 +#define UI_GAME_MENU_MESSAGE_MAX 192 typedef struct { uimenu_t menu; @@ -20,6 +21,13 @@ typedef struct { char_t itemsLabel[UI_GAME_MENU_LABEL_MAX]; char_t settingsLabel[UI_GAME_MENU_LABEL_MAX]; char_t saveLabel[UI_GAME_MENU_LABEL_MAX]; + char_t saveSuccessText[UI_GAME_MENU_MESSAGE_MAX]; + char_t saveCancelledText[UI_GAME_MENU_MESSAGE_MAX]; + char_t saveUnavailableText[UI_GAME_MENU_MESSAGE_MAX]; + char_t saveTemporaryText[UI_GAME_MENU_MESSAGE_MAX]; + char_t saveCreateConfirmText[UI_GAME_MENU_MESSAGE_MAX]; + char_t saveFailedFormat[UI_GAME_MENU_MESSAGE_MAX]; + char_t saveCheckFailedFormat[UI_GAME_MENU_MESSAGE_MAX]; } uigamemenu_t; extern uigamemenu_t UI_GAME_MENU; diff --git a/src/dusk/ui/frame/initial/uiinitialcreatesave.c b/src/dusk/ui/frame/initial/uiinitialcreatesave.c index 814b59ff..ade0fa29 100644 --- a/src/dusk/ui/frame/initial/uiinitialcreatesave.c +++ b/src/dusk/ui/frame/initial/uiinitialcreatesave.c @@ -16,9 +16,10 @@ #include "display/spritebatch/spritebatch.h" #include "display/texture/texture.h" #include "display/shader/shaderunlit.h" +#include "locale/localemanager.h" +#include "asset/loader/locale/assetlocaleloader.h" #define UI_INITIAL_CREATE_SAVE_BACKDROP_COLOR color4b(0, 0, 0, 160) -#define UI_INITIAL_CREATE_SAVE_TEXT "No save data found. Create a new save?" uiinitialcreatesave_t UI_INITIAL_CREATE_SAVE; @@ -42,12 +43,34 @@ static void uiInitialCreateSaveClosed(const uimenu_t *menu) { errorret_t uiInitialCreateSaveInit(void) { memoryZero(&UI_INITIAL_CREATE_SAVE, sizeof(uiinitialcreatesave_t)); + errorChain(assetLocaleGetString( + &LOCALE.entry->data.locale, + "ui.initial.create_save.message", + 0, + UI_INITIAL_CREATE_SAVE.text, + UI_INITIAL_CREATE_SAVE_TEXT_MAX + )); + errorChain(assetLocaleGetString( + &LOCALE.entry->data.locale, + "ui.initial.create_save.yes", + 0, + UI_INITIAL_CREATE_SAVE.yesLabel, + UI_INITIAL_CREATE_SAVE_LABEL_MAX + )); + errorChain(assetLocaleGetString( + &LOCALE.entry->data.locale, + "ui.initial.create_save.no", + 0, + UI_INITIAL_CREATE_SAVE.noLabel, + UI_INITIAL_CREATE_SAVE_LABEL_MAX + )); + MENU_BEGIN( &UI_INITIAL_CREATE_SAVE.menu, UI_INITIAL_CREATE_SAVE.items, uiInitialCreateSaveSelected, uiInitialCreateSaveClosed, NULL ); - MENU_BUTTON("Yes"); - MENU_BUTTON("No"); + MENU_BUTTON(UI_INITIAL_CREATE_SAVE.yesLabel); + MENU_BUTTON(UI_INITIAL_CREATE_SAVE.noLabel); MENU_END(UI_INITIAL_CREATE_SAVE.items, menuIndex); @@ -75,7 +98,7 @@ errorret_t uiInitialCreateSaveDraw(void) { errorChain(spriteBatchFlush()); int32_t textW, textH; - textMeasure(UI_INITIAL_CREATE_SAVE_TEXT, &FONT_DEFAULT, &textW, &textH); + textMeasure(UI_INITIAL_CREATE_SAVE.text, &FONT_DEFAULT, &textW, &textH); float_t rowHeight = (float_t)FONT_DEFAULT.tileset->tileHeight; float_t width = mathMax( @@ -93,7 +116,7 @@ errorret_t uiInitialCreateSaveDraw(void) { float_t contentWidth = width - (UI_FRAME_START_X * 2); errorChain(textDraw( - contentX, contentY, UI_INITIAL_CREATE_SAVE_TEXT, COLOR_WHITE, &FONT_DEFAULT + contentX, contentY, UI_INITIAL_CREATE_SAVE.text, COLOR_WHITE, &FONT_DEFAULT )); float_t buttonsY = contentY + rowHeight + UI_FRAME_PADDING_Y; diff --git a/src/dusk/ui/frame/initial/uiinitialcreatesave.h b/src/dusk/ui/frame/initial/uiinitialcreatesave.h index 62eece1f..8eb3a04b 100644 --- a/src/dusk/ui/frame/initial/uiinitialcreatesave.h +++ b/src/dusk/ui/frame/initial/uiinitialcreatesave.h @@ -13,6 +13,8 @@ #define UI_INITIAL_CREATE_SAVE_INDEX_NO 1 #define UI_INITIAL_CREATE_SAVE_ITEM_COUNT 2 #define UI_INITIAL_CREATE_SAVE_MIN_WIDTH 160.0f +#define UI_INITIAL_CREATE_SAVE_TEXT_MAX 256 +#define UI_INITIAL_CREATE_SAVE_LABEL_MAX 32 /** * Callback invoked once the create-save modal is dismissed. @@ -29,6 +31,9 @@ typedef struct { uiinitialcreatesavecallback_t callback; void *user; bool_t create; + char_t text[UI_INITIAL_CREATE_SAVE_TEXT_MAX]; + char_t yesLabel[UI_INITIAL_CREATE_SAVE_LABEL_MAX]; + char_t noLabel[UI_INITIAL_CREATE_SAVE_LABEL_MAX]; } uiinitialcreatesave_t; extern uiinitialcreatesave_t UI_INITIAL_CREATE_SAVE; diff --git a/src/dusk/ui/frame/initial/uiinitialnocard.c b/src/dusk/ui/frame/initial/uiinitialnocard.c index 83a60e70..35ebd9dc 100644 --- a/src/dusk/ui/frame/initial/uiinitialnocard.c +++ b/src/dusk/ui/frame/initial/uiinitialnocard.c @@ -16,10 +16,10 @@ #include "display/spritebatch/spritebatch.h" #include "display/texture/texture.h" #include "display/shader/shaderunlit.h" +#include "locale/localemanager.h" +#include "asset/loader/locale/assetlocaleloader.h" #define UI_INITIAL_NO_CARD_BACKDROP_COLOR color4b(0, 0, 0, 160) -#define UI_INITIAL_NO_CARD_TEXT \ - "No save device found. You can continue, but\nprogress will not be saved." uiinitialnocard_t UI_INITIAL_NO_CARD; @@ -41,12 +41,34 @@ static void uiInitialNoCardClosed(const uimenu_t *menu) { errorret_t uiInitialNoCardInit(void) { memoryZero(&UI_INITIAL_NO_CARD, sizeof(uiinitialnocard_t)); + errorChain(assetLocaleGetString( + &LOCALE.entry->data.locale, + "ui.initial.no_card.message", + 0, + UI_INITIAL_NO_CARD.text, + UI_INITIAL_NO_CARD_TEXT_MAX + )); + errorChain(assetLocaleGetString( + &LOCALE.entry->data.locale, + "ui.initial.no_card.retry", + 0, + UI_INITIAL_NO_CARD.retryLabel, + UI_INITIAL_NO_CARD_LABEL_MAX + )); + errorChain(assetLocaleGetString( + &LOCALE.entry->data.locale, + "ui.initial.no_card.continue", + 0, + UI_INITIAL_NO_CARD.continueLabel, + UI_INITIAL_NO_CARD_LABEL_MAX + )); + MENU_BEGIN( &UI_INITIAL_NO_CARD.menu, UI_INITIAL_NO_CARD.items, uiInitialNoCardSelected, uiInitialNoCardClosed, NULL ); - MENU_BUTTON("Retry"); - MENU_BUTTON("Continue Anyway"); + MENU_BUTTON(UI_INITIAL_NO_CARD.retryLabel); + MENU_BUTTON(UI_INITIAL_NO_CARD.continueLabel); MENU_END(UI_INITIAL_NO_CARD.items, menuIndex); @@ -74,7 +96,7 @@ errorret_t uiInitialNoCardDraw(void) { errorChain(spriteBatchFlush()); int32_t textW, textH; - textMeasure(UI_INITIAL_NO_CARD_TEXT, &FONT_DEFAULT, &textW, &textH); + textMeasure(UI_INITIAL_NO_CARD.text, &FONT_DEFAULT, &textW, &textH); float_t rowHeight = (float_t)FONT_DEFAULT.tileset->tileHeight; float_t width = mathMax( @@ -92,7 +114,7 @@ errorret_t uiInitialNoCardDraw(void) { float_t contentWidth = width - (UI_FRAME_START_X * 2); errorChain(textDraw( - contentX, contentY, UI_INITIAL_NO_CARD_TEXT, COLOR_WHITE, &FONT_DEFAULT + contentX, contentY, UI_INITIAL_NO_CARD.text, COLOR_WHITE, &FONT_DEFAULT )); float_t buttonsY = contentY + (float_t)textH + UI_FRAME_PADDING_Y; diff --git a/src/dusk/ui/frame/initial/uiinitialnocard.h b/src/dusk/ui/frame/initial/uiinitialnocard.h index e9c442a2..3b09c142 100644 --- a/src/dusk/ui/frame/initial/uiinitialnocard.h +++ b/src/dusk/ui/frame/initial/uiinitialnocard.h @@ -13,6 +13,8 @@ #define UI_INITIAL_NO_CARD_INDEX_CONTINUE 1 #define UI_INITIAL_NO_CARD_ITEM_COUNT 2 #define UI_INITIAL_NO_CARD_MIN_WIDTH 220.0f +#define UI_INITIAL_NO_CARD_TEXT_MAX 256 +#define UI_INITIAL_NO_CARD_LABEL_MAX 32 /** * Callback invoked once the no-save-device modal is dismissed. @@ -28,6 +30,9 @@ typedef struct { uiinitialnocardcallback_t callback; void *user; bool_t retry; + char_t text[UI_INITIAL_NO_CARD_TEXT_MAX]; + char_t retryLabel[UI_INITIAL_NO_CARD_LABEL_MAX]; + char_t continueLabel[UI_INITIAL_NO_CARD_LABEL_MAX]; } uiinitialnocard_t; extern uiinitialnocard_t UI_INITIAL_NO_CARD; diff --git a/src/dusk/ui/frame/settings/uisettingsdata.h b/src/dusk/ui/frame/settings/uisettingsdata.h index 511b0f04..b5278408 100644 --- a/src/dusk/ui/frame/settings/uisettingsdata.h +++ b/src/dusk/ui/frame/settings/uisettingsdata.h @@ -8,19 +8,19 @@ #pragma once #include "error/error.h" #include "ui/widget/uimenu.h" +#include "locale/localemanager.h" #define UI_SETTINGS_GENERAL_ITEM_COUNT 3 #define UI_SETTINGS_GENERAL_INDEX_LANGUAGE 0 #define UI_SETTINGS_GENERAL_INDEX_LANGUAGE_DETAIL 1 #define UI_SETTINGS_GENERAL_INDEX_APPLY 2 -#define UI_SETTINGS_GENERAL_LOCALE_COUNT 3 #define UI_SETTINGS_GENERAL_LABEL_MAX 32 -#define UI_SETTINGS_GENERAL_INFO_MAX 64 +#define UI_SETTINGS_GENERAL_INFO_MAX 96 typedef struct { uimenu_t menu; uimenuitem_t items[UI_SETTINGS_GENERAL_ITEM_COUNT]; - const char_t *localeNames[UI_SETTINGS_GENERAL_LOCALE_COUNT]; + const char_t *localeNames[LOCALE_LIST_COUNT]; char_t languageLabel[UI_SETTINGS_GENERAL_LABEL_MAX]; char_t labelInfo[UI_SETTINGS_GENERAL_INFO_MAX]; } uisettingsgeneral_t; diff --git a/src/dusk/ui/frame/settings/uisettingsgeneral.c b/src/dusk/ui/frame/settings/uisettingsgeneral.c index d4c44594..865b0db7 100644 --- a/src/dusk/ui/frame/settings/uisettingsgeneral.c +++ b/src/dusk/ui/frame/settings/uisettingsgeneral.c @@ -9,18 +9,15 @@ #include "uisettings.h" #include "assert/assert.h" #include "util/memory.h" -#include "util/string.h" #include "locale/localemanager.h" #include "locale/localeinfo.h" #include "asset/loader/locale/assetlocaleloader.h" +#include "save/save.h" +#include "error/error.h" -static const localeinfo_t *const UI_SETTINGS_GENERAL_LOCALES[ - UI_SETTINGS_GENERAL_LOCALE_COUNT -] = { - &LOCALE_EN_US, - &LOCALE_JP_JP, - &LOCALE_ES_MX -}; +static void uiSettingsGeneralSaveComplete(errorret_t result, void *user) { + if(errorIsNotOk(result)) errorCatch(errorPrint(result)); +} void uiSettingsGeneralSelected( const uimenu_t *menu, @@ -41,8 +38,8 @@ errorret_t uiSettingsGeneralInit(uisettingsdata_t *data) { uisettingsgeneral_t *general = &data->general; memoryZero(general, sizeof(uisettingsgeneral_t)); - for(uint8_t i = 0; i < UI_SETTINGS_GENERAL_LOCALE_COUNT; i++) { - general->localeNames[i] = UI_SETTINGS_GENERAL_LOCALES[i]->name; + for(uint8_t i = 0; i < LOCALE_LIST_COUNT; i++) { + general->localeNames[i] = LOCALE_LIST[i]->name; } errorChain(assetLocaleGetString( @@ -67,7 +64,7 @@ errorret_t uiSettingsGeneralInit(uisettingsdata_t *data) { MENU_DROPDOWN( general->languageLabel, general->localeNames, - UI_SETTINGS_GENERAL_LOCALE_COUNT, 0 + LOCALE_LIST_COUNT, 0 ); MENU_LABEL(general->labelInfo); @@ -80,14 +77,7 @@ errorret_t uiSettingsGeneralInit(uisettingsdata_t *data) { void uiSettingsGeneralLoad(void) { uisettingsgeneral_t *general = &UI_SETTINGS.data.general; - uint8_t index = 0; - for(uint8_t i = 0; i < UI_SETTINGS_GENERAL_LOCALE_COUNT; i++) { - if(stringCompare(LOCALE.locale->file, UI_SETTINGS_GENERAL_LOCALES[i]->file) != 0) { - continue; - } - index = i; - break; - } + uint8_t index = localeManagerGetIndex(LOCALE.locale); uiDropdownSetSelectedIndex( &general->items[UI_SETTINGS_GENERAL_INDEX_LANGUAGE].dropdown, index @@ -100,7 +90,10 @@ void uiSettingsGeneralApply(void) { uint8_t index = uiDropdownGetSelectedIndex( &general->items[UI_SETTINGS_GENERAL_INDEX_LANGUAGE].dropdown ); - errorCatch(localeManagerSetLocale(UI_SETTINGS_GENERAL_LOCALES[index])); + errorCatch(localeManagerSetLocale(LOCALE_LIST[index])); + + saveGetMeta()->language = index; + saveWriteMeta(uiSettingsGeneralSaveComplete, NULL); uiMenuClose(&general->menu); } @@ -111,7 +104,5 @@ bool_t uiSettingsGeneralHasChanges(void) { uint8_t index = uiDropdownGetSelectedIndex( &general->items[UI_SETTINGS_GENERAL_INDEX_LANGUAGE].dropdown ); - return stringCompare( - LOCALE.locale->file, UI_SETTINGS_GENERAL_LOCALES[index]->file - ) != 0; + return localeManagerGetIndex(LOCALE.locale) != index; } diff --git a/src/dusklinux/save/savejsonlinux.c b/src/dusklinux/save/savejsonlinux.c index 080b24a3..55f20773 100644 --- a/src/dusklinux/save/savejsonlinux.c +++ b/src/dusklinux/save/savejsonlinux.c @@ -31,6 +31,12 @@ void saveJsonWriterAddFloatLinux( yyjson_mut_obj_add_real(writer->doc, writer->root, key, (double)value); } +void saveJsonWriterAddUInt8Linux( + savejsonwriterlinux_t *writer, const char_t *key, const uint8_t value +) { + yyjson_mut_obj_add_uint(writer->doc, writer->root, key, (uint64_t)value); +} + void saveJsonWriterAddStringLinux( savejsonwriterlinux_t *writer, const char_t *key, const char_t *value ) { @@ -122,6 +128,14 @@ float_t saveJsonReadFloatLinux( return (float_t)yyjson_get_num(val); } +uint8_t saveJsonReadUInt8Linux( + yyjson_val *root, const char_t *key, const uint8_t defaultValue +) { + yyjson_val *val = yyjson_obj_get(root, key); + if(!val || !yyjson_is_num(val)) return defaultValue; + return (uint8_t)yyjson_get_uint(val); +} + void saveJsonReadStringLinux( yyjson_val *root, const char_t *key, char_t *out, const size_t maxLen, const char_t *defaultValue diff --git a/src/dusklinux/save/savejsonlinux.h b/src/dusklinux/save/savejsonlinux.h index 01a08180..e8122d42 100644 --- a/src/dusklinux/save/savejsonlinux.h +++ b/src/dusklinux/save/savejsonlinux.h @@ -42,6 +42,13 @@ void saveJsonWriterAddFloatLinux( savejsonwriterlinux_t *writer, const char_t *key, const float_t value ); +/** + * Adds a "key": field to the root object. + */ +void saveJsonWriterAddUInt8Linux( + savejsonwriterlinux_t *writer, const char_t *key, const uint8_t value +); + /** * Adds a string field to the root object. The value is copied into the * document, so the caller's buffer doesn't need to outlive the call. @@ -120,6 +127,14 @@ float_t saveJsonReadFloatLinux( yyjson_val *root, const char_t *key, const float_t defaultValue ); +/** + * Reads a uint8 field, falling back to defaultValue if the key is + * missing or not a number. + */ +uint8_t saveJsonReadUInt8Linux( + yyjson_val *root, const char_t *key, const uint8_t defaultValue +); + /** * Reads a string field into out, falling back to defaultValue if the key * is missing or not a string. Always null-terminates. diff --git a/src/dusklinux/save/savelinux.c b/src/dusklinux/save/savelinux.c index 465bdf94..53fccfb7 100644 --- a/src/dusklinux/save/savelinux.c +++ b/src/dusklinux/save/savelinux.c @@ -120,6 +120,9 @@ errorret_t saveMetaLoadLinux(savemeta_t *out) { out->deadzone = saveJsonReadFloatLinux( root, "deadzone", SAVE_META_DEADZONE_DEFAULT ); + out->language = saveJsonReadUInt8Linux( + root, "language", SAVE_META_LANGUAGE_DEFAULT + ); out->exists = true; yyjson_doc_free(doc); @@ -136,6 +139,7 @@ errorret_t saveMetaWriteLinux(savemeta_t *meta) { errorChain(saveJsonWriterInitLinux(&writer)); saveJsonWriterAddUInt32Linux(&writer, "version", meta->version); saveJsonWriterAddFloatLinux(&writer, "deadzone", meta->deadzone); + saveJsonWriterAddUInt8Linux(&writer, "language", meta->language); errorret_t ret = saveJsonWriterSaveLinux(&writer, path); saveJsonWriterDisposeLinux(&writer);