Changed bundled resources to use static base name instead.

Added options menu with selectable language
This commit is contained in:
lieght
2025-10-03 03:19:38 +02:00
parent 1dd345a290
commit 547ea55300
13 changed files with 228 additions and 33 deletions

View File

@@ -8,7 +8,7 @@ import java.util.Locale;
public final class CreditsMenu extends Menu { public final class CreditsMenu extends Menu {
private Locale currentLocale = AppContext.getLocale(); private Locale currentLocale = AppContext.getLocale();
private LocalizationAsset loc = ResourceManager.get("localization.properties"); private LocalizationAsset loc = ResourceManager.get("localization_en_us.properties");
public CreditsMenu() { public CreditsMenu() {
} }
} }

View File

@@ -1,14 +1,84 @@
package org.toop.app.menu; package org.toop.app.menu;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.Label;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import org.toop.app.App;
import org.toop.framework.asset.ResourceManager; import org.toop.framework.asset.ResourceManager;
import org.toop.framework.asset.resources.LocalizationAsset; import org.toop.framework.asset.resources.LocalizationAsset;
import org.toop.local.AppContext; import org.toop.local.AppContext;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.util.Locale; import java.util.Locale;
public final class OptionsMenu extends Menu { public final class OptionsMenu extends Menu {
private Locale currentLocale = AppContext.getLocale(); private Locale currentLocale = AppContext.getLocale();
private LocalizationAsset loc = ResourceManager.get("localization.properties"); private LocalizationAsset loc = ResourceManager.get("localization");
public OptionsMenu() { public OptionsMenu() {
} final Region background = createBackground();
GraphicsDevice currentScreen = GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices()[0];
LocalizationAsset locFiles = ResourceManager.get(LocalizationAsset.class, "localization");
final Label selectLanguageLabel = new Label(loc.getString("optionsMenuLabelSelectLanguage", currentLocale));
final ChoiceBox<Locale> selectLanguage = new ChoiceBox<>();
selectLanguage.setValue(currentLocale);
for (Locale locFile : locFiles.getAvailableLocales()) {
selectLanguage.getItems().add(locFile);
}
selectLanguage.setOnAction((event) -> {
Locale selectedLocale = selectLanguage.getSelectionModel().getSelectedItem();
AppContext.setLocale(selectedLocale);
App.pop();
App.push(new OptionsMenu());
});
// GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
// GraphicsDevice[] devices = ge.getScreenDevices();
// final ChoiceBox<GraphicsDevice> selectScreen = new ChoiceBox<>();
// for (GraphicsDevice screen : devices) {
// selectScreen.getItems().add(screen);
// }
//
// selectScreen.setOnAction((event) -> {
// int selectedIndex = selectScreen.getSelectionModel().getSelectedIndex();
// Object selectedItem = selectScreen.getSelectionModel().getSelectedItem();
//
// System.out.println("Selection made: [" + selectedIndex + "] " + selectedItem);
// System.out.println(" ChoiceBox.getValue(): " + selectScreen.getValue());
// });
//
// final ChoiceBox<DisplayMode> selectWindowSize = new ChoiceBox<>();
// for (DisplayMode displayMode : currentScreen.getDisplayModes()) {
// selectWindowSize.getItems().add(displayMode);
// }
//
//// if (currentScreen.isFullScreenSupported()) {}
// final CheckBox setFullscreen = new CheckBox("Fullscreen");
final VBox optionsBox = new VBox(10, selectLanguageLabel, selectLanguage);
optionsBox.setAlignment(Pos.CENTER);
optionsBox.setPickOnBounds(false);
optionsBox.setTranslateY(50);
optionsBox.setTranslateX(25);
final Button credits = createButton("Credits", () -> { App.push(new CreditsMenu()); });
final Button options = createButton("Exit Options", () -> { App.push(new MainMenu()); });
final Button quit = createButton("Quit", () -> { App.quitPopup(); });
final VBox controlBox = new VBox(10, credits, options, quit);
controlBox.setAlignment(Pos.BOTTOM_LEFT);
controlBox.setPickOnBounds(false);
controlBox.setTranslateY(-50);
controlBox.setTranslateX(25);
pane = new StackPane(background, optionsBox, controlBox);
}
} }

View File

@@ -5,9 +5,14 @@ import java.util.Locale;
public class AppContext { public class AppContext {
private static Locale currentLocale = Locale.getDefault(); private static Locale currentLocale = Locale.getDefault();
public static void setLocale(Locale locale) {
currentLocale = locale;
}
public static void setCurrentLocale(Locale locale) { public static void setCurrentLocale(Locale locale) {
currentLocale = locale; currentLocale = locale;
} }
public static Locale getLocale() { public static Locale getLocale() {
return currentLocale; return currentLocale;
} }

View File

@@ -14,4 +14,7 @@ mainMenuSelectQuit=Beenden
# Quit Menu text and buttons # Quit Menu text and buttons
quitMenuTextSure=Bist du sicher? quitMenuTextSure=Bist du sicher?
quitMenuButtonYes=Ja quitMenuButtonYes=Ja
quitMenuButtonNo=Nein quitMenuButtonNo=Nein
# Options menu
optionsMenuLabelSelectLanguage=Sprache:

View File

@@ -2,8 +2,8 @@
windowTitle=ISY Games Selector windowTitle=ISY Games Selector
# Main Menu buttons # Main Menu buttons
mainMenuSelectTicTacToe=Tic Tac Toe\u5426 mainMenuSelectTicTacToe=Tic Tac Toe
mainMenuSelectReversi=Reversi\u5426 mainMenuSelectReversi=Reversi
mainMenuSelectSudoku=Sudoku mainMenuSelectSudoku=Sudoku
mainMenuSelectBattleship=Battleship mainMenuSelectBattleship=Battleship
mainMenuSelectOther=Other mainMenuSelectOther=Other
@@ -14,4 +14,7 @@ mainMenuSelectQuit=Quit
# Quit Menu text and buttons # Quit Menu text and buttons
quitMenuTextSure=Are you sure? quitMenuTextSure=Are you sure?
quitMenuButtonYes=Yes quitMenuButtonYes=Yes
quitMenuButtonNo=No quitMenuButtonNo=No
# Options menu
optionsMenuLabelSelectLanguage=Language:

View File

@@ -14,4 +14,7 @@ mainMenuSelectQuit=Salir
# Quit Menu text and buttons # Quit Menu text and buttons
quitMenuTextSure=\u00BFEst\u00E1s seguro? quitMenuTextSure=\u00BFEst\u00E1s seguro?
quitMenuButtonYes=S\u00ED quitMenuButtonYes=S\u00ED
quitMenuButtonNo=No quitMenuButtonNo=No
# Options menu
optionsMenuLabelSelectLanguage=Idioma:

View File

@@ -14,4 +14,7 @@ mainMenuSelectQuit=Quitter
# Quit Menu text and buttons # Quit Menu text and buttons
quitMenuTextSure=\u00CAtes-vous s\u00FBr? quitMenuTextSure=\u00CAtes-vous s\u00FBr?
quitMenuButtonYes=Oui quitMenuButtonYes=Oui
quitMenuButtonNo=Non quitMenuButtonNo=Non
# Options menu
optionsMenuLabelSelectLanguage=Langue:

View File

@@ -14,4 +14,7 @@ mainMenuSelectQuit=Esci
# Quit Menu text and buttons # Quit Menu text and buttons
quitMenuTextSure=Sei sicuro? quitMenuTextSure=Sei sicuro?
quitMenuButtonYes=S\u00EC quitMenuButtonYes=S\u00EC
quitMenuButtonNo=No quitMenuButtonNo=No
# Options menu
optionsMenuLabelSelectLanguage=Lingua:

View File

@@ -14,4 +14,7 @@ mainMenuSelectQuit=Afsluiten
# Quit Menu text and buttons # Quit Menu text and buttons
quitMenuTextSure=Weet je het zeker? quitMenuTextSure=Weet je het zeker?
quitMenuButtonYes=Ja quitMenuButtonYes=Ja
quitMenuButtonNo=Nee quitMenuButtonNo=Nee
# Options menu
optionsMenuLabelSelectLanguage=Taal:

View File

@@ -27,4 +27,7 @@ quitMenuTextSure=\u4F60\u786E\u5B9A\u5417\uFF1F
quitMenuButtonYes=\u662F quitMenuButtonYes=\u662F
# ? # ?
quitMenuButtonNo=\u5426 quitMenuButtonNo=\u5426
# ? # ?
# Options menu
optionsMenuLabelSelectLanguage=\u8BED\u8A00:

View File

@@ -158,6 +158,7 @@ public class ResourceLoader {
Map<String, BundledResource> bundledResources = new HashMap<>(); Map<String, BundledResource> bundledResources = new HashMap<>();
for (File file : files) { for (File file : files) {
boolean skipAdd = false;
BaseResource resource = resourceMapper(file, BaseResource.class); BaseResource resource = resourceMapper(file, BaseResource.class);
switch (resource) { switch (resource) {
case null -> { case null -> {
@@ -165,13 +166,11 @@ public class ResourceLoader {
} }
case BundledResource br -> { case BundledResource br -> {
String key = resource.getClass().getName() + ":" + br.getBaseName(); String key = resource.getClass().getName() + ":" + br.getBaseName();
if (bundledResources.containsKey(key)) { if (!bundledResources.containsKey(key)) {bundledResources.put(key, br);}
bundledResources.get(key).loadFile(file); bundledResources.get(key).loadFile(file);
resource = (BaseResource) bundledResources.get(key); resource = (BaseResource) bundledResources.get(key);
} else { assets.add(new ResourceMeta<>(br.getBaseName(), resource));
br.loadFile(file); skipAdd = true;
bundledResources.put(key, br);
}
} }
case PreloadResource pr -> pr.load(); case PreloadResource pr -> pr.load();
default -> { default -> {
@@ -181,7 +180,7 @@ public class ResourceLoader {
BaseResource finalResource = resource; BaseResource finalResource = resource;
boolean alreadyAdded = assets.stream() boolean alreadyAdded = assets.stream()
.anyMatch(a -> a.getResource() == finalResource); .anyMatch(a -> a.getResource() == finalResource);
if (!alreadyAdded) { if (!alreadyAdded && !skipAdd) {
assets.add(new ResourceMeta<>(file.getName(), resource)); assets.add(new ResourceMeta<>(file.getName(), resource));
} }

View File

@@ -8,33 +8,90 @@ import java.io.*;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.*; import java.util.*;
/**
* Represents a localization resource asset that loads and manages property files
* containing key-value pairs for different locales.
* <p>
* This class implements {@link LoadableResource} to support loading/unloading
* and {@link BundledResource} to represent resources that can contain multiple
* localized bundles.
* </p>
* <p>
* Files handled by this class must have the {@code .properties} extension,
* optionally with a locale suffix, e.g., {@code messages_en_US.properties}.
* </p>
*
* <p>
* Example usage:
* <pre>{@code
* LocalizationAsset asset = new LocalizationAsset(new File("messages_en_US.properties"));
* asset.load();
* String greeting = asset.getString("hello", Locale.US);
* }</pre>
* </p>
*/
@FileExtension({"properties"}) @FileExtension({"properties"})
public class LocalizationAsset extends BaseResource implements LoadableResource, BundledResource { public class LocalizationAsset extends BaseResource implements LoadableResource, BundledResource {
private final Map<Locale, ResourceBundle> bundles = new HashMap<>();
private boolean isLoaded = false;
private final Locale fallback = Locale.forLanguageTag("");
/** Map of loaded resource bundles, keyed by locale. */
private final Map<Locale, ResourceBundle> bundles = new HashMap<>();
/** Flag indicating whether this asset has been loaded. */
private boolean isLoaded = false;
/** Basename of the given asset */
private final String baseName = "localization";
/** Fallback locale used when no matching locale is found. */
private final Locale fallback = Locale.forLanguageTag("en_US");
/**
* Constructs a new LocalizationAsset for the specified file.
*
* @param file the property file to load
*/
public LocalizationAsset(File file) { public LocalizationAsset(File file) {
super(file); super(file);
} }
/**
* Loads the resource file into memory and prepares localized bundles.
*/
@Override @Override
public void load() { public void load() {
loadFile(getFile()); loadFile(getFile());
isLoaded = true; isLoaded = true;
} }
/**
* Unloads all loaded resource bundles, freeing memory.
*/
@Override @Override
public void unload() { public void unload() {
bundles.clear(); bundles.clear();
isLoaded = false; isLoaded = false;
} }
/**
* Checks whether this asset has been loaded.
*
* @return {@code true} if the asset is loaded, {@code false} otherwise
*/
@Override @Override
public boolean isLoaded() { public boolean isLoaded() {
return isLoaded; return isLoaded;
} }
/**
* Retrieves a localized string for the given key and locale.
* If an exact match for the locale is not found, a fallback
* matching the language or the default locale will be used.
*
* @param key the key of the string
* @param locale the desired locale
* @return the localized string
* @throws MissingResourceException if no resource bundle is available for the locale
*/
public String getString(String key, Locale locale) { public String getString(String key, Locale locale) {
Locale target = findBestLocale(locale); Locale target = findBestLocale(locale);
ResourceBundle bundle = bundles.get(target); ResourceBundle bundle = bundles.get(target);
@@ -43,6 +100,13 @@ public class LocalizationAsset extends BaseResource implements LoadableResource,
return bundle.getString(key); return bundle.getString(key);
} }
/**
* Finds the best matching locale among loaded bundles.
* Prefers an exact match, then language-only match, then fallback.
*
* @param locale the desired locale
* @return the best matching locale
*/
private Locale findBestLocale(Locale locale) { private Locale findBestLocale(Locale locale) {
if (bundles.containsKey(locale)) return locale; if (bundles.containsKey(locale)) return locale;
for (Locale l : bundles.keySet()) { for (Locale l : bundles.keySet()) {
@@ -51,13 +115,24 @@ public class LocalizationAsset extends BaseResource implements LoadableResource,
return fallback; return fallback;
} }
/**
* Returns an unmodifiable set of all locales for which bundles are loaded.
*
* @return available locales
*/
public Set<Locale> getAvailableLocales() { public Set<Locale> getAvailableLocales() {
return Collections.unmodifiableSet(bundles.keySet()); return Collections.unmodifiableSet(bundles.keySet());
} }
/**
* Loads a specific property file as a resource bundle.
* The locale is extracted from the file name if present.
*
* @param file the property file to load
* @throws RuntimeException if the file cannot be read
*/
@Override @Override
public void loadFile(File file) { public void loadFile(File file) {
String baseName = getBaseName(file.getName());
try (InputStreamReader reader = try (InputStreamReader reader =
new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8)) { new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8)) {
Locale locale = extractLocale(file.getName(), baseName); Locale locale = extractLocale(file.getName(), baseName);
@@ -68,20 +143,40 @@ public class LocalizationAsset extends BaseResource implements LoadableResource,
isLoaded = true; isLoaded = true;
} }
/**
* Returns the base name of the underlying file (without locale or extension).
*
* @return the base name
*/
@Override @Override
public String getBaseName() { public String getBaseName() {
return getBaseName(getFile().getName()); return this.baseName;
} }
private String getBaseName(String fileName) { // /**
int underscoreIndex = fileName.indexOf('_'); // * Extracts the base name from a file name.
int dotIndex = fileName.lastIndexOf('.'); // *
if (underscoreIndex > 0) { // * @param fileName the file name
return fileName.substring(0, underscoreIndex); // * @return base name without locale or extension
} // */
return fileName.substring(0, dotIndex); // private String getBaseName(String fileName) {
} // int dotIndex = fileName.lastIndexOf('.');
// String nameWithoutExtension = (dotIndex > 0) ? fileName.substring(0, dotIndex) : fileName;
//
// int underscoreIndex = nameWithoutExtension.indexOf('_');
// if (underscoreIndex > 0) {
// return nameWithoutExtension.substring(0, underscoreIndex);
// }
// return nameWithoutExtension;
// }
/**
* Extracts a locale from a file name based on the pattern "base_LOCALE.properties".
*
* @param fileName the file name
* @param baseName the base name
* @return extracted locale, or fallback if none found
*/
private Locale extractLocale(String fileName, String baseName) { private Locale extractLocale(String fileName, String baseName) {
int underscoreIndex = fileName.indexOf('_'); int underscoreIndex = fileName.indexOf('_');
int dotIndex = fileName.lastIndexOf('.'); int dotIndex = fileName.lastIndexOf('.');

View File

@@ -66,4 +66,9 @@ public interface BundledResource {
* @return the base name used to identify this bundled resource * @return the base name used to identify this bundled resource
*/ */
String getBaseName(); String getBaseName();
// /**
// Returns the name
// */
// String getDefaultName();
} }