Merge branch 'feature/client-recipe-i18n' into 'main'
Client recipe internationalization Closes #39 See merge request cse1105/2025-2026/teams/csep-team-76!49
This commit is contained in:
commit
adc79e2d71
14 changed files with 180 additions and 10 deletions
|
|
@ -277,7 +277,10 @@ public class FoodpalApplicationCtrl implements LocaleAware {
|
|||
public void refresh() {
|
||||
List<Recipe> recipes;
|
||||
try {
|
||||
recipes = server.getRecipesFiltered(searchBarController.getFilter());
|
||||
recipes = server.getRecipesFiltered(
|
||||
searchBarController.getFilter(),
|
||||
this.configService.getConfig().getRecipeLanguages()
|
||||
);
|
||||
} catch (IOException | InterruptedException e) {
|
||||
recipes = Collections.emptyList();
|
||||
String msg = "Failed to load recipes: " + e.getMessage();
|
||||
|
|
|
|||
87
client/src/main/java/client/scenes/LanguageFilterCtrl.java
Normal file
87
client/src/main/java/client/scenes/LanguageFilterCtrl.java
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
package client.scenes;
|
||||
|
||||
import client.utils.ConfigService;
|
||||
import client.utils.LocaleAware;
|
||||
import client.utils.LocaleManager;
|
||||
import com.google.inject.Inject;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.CheckMenuItem;
|
||||
import javafx.scene.control.MenuButton;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class LanguageFilterCtrl implements LocaleAware {
|
||||
private final LocaleManager manager;
|
||||
private final ConfigService configService;
|
||||
private final FoodpalApplicationCtrl appCtrl;
|
||||
|
||||
@Inject
|
||||
public LanguageFilterCtrl(LocaleManager manager, ConfigService configService, FoodpalApplicationCtrl appCtrl) {
|
||||
this.manager = manager;
|
||||
this.configService = configService;
|
||||
this.appCtrl = appCtrl;
|
||||
}
|
||||
|
||||
@FXML
|
||||
MenuButton langFilterMenu;
|
||||
|
||||
List<String> selectedLanguages = new ArrayList<>();
|
||||
|
||||
private String getSelectedLanguagesDisplay() {
|
||||
String joined = String.join(", ", selectedLanguages);
|
||||
if (joined.isEmpty()) {
|
||||
return "none";
|
||||
} else {
|
||||
return joined;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateMenuButtonDisplay() {
|
||||
langFilterMenu.setText(getLocaleString("menu.label.selected-langs") + ": " + this.getSelectedLanguagesDisplay());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateText() {
|
||||
this.updateMenuButtonDisplay();
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocaleManager getLocaleManager() {
|
||||
return this.manager;
|
||||
}
|
||||
|
||||
public void initializeComponents() {
|
||||
var items = this.langFilterMenu.getItems();
|
||||
|
||||
final List<String> languages = List.of("en", "nl", "pl");
|
||||
this.selectedLanguages = this.configService.getConfig().getRecipeLanguages();
|
||||
this.updateMenuButtonDisplay();
|
||||
|
||||
items.clear();
|
||||
|
||||
languages.forEach(lang -> {
|
||||
CheckMenuItem it = new CheckMenuItem(lang);
|
||||
|
||||
if (selectedLanguages.contains(it.getText())) {
|
||||
it.setSelected(true);
|
||||
}
|
||||
|
||||
it.selectedProperty().addListener((observable, _, value) -> {
|
||||
if (value) {
|
||||
selectedLanguages.add(it.getText());
|
||||
} else {
|
||||
selectedLanguages.remove(it.getText());
|
||||
}
|
||||
|
||||
configService.save();
|
||||
selectedLanguages.sort(String::compareTo);
|
||||
appCtrl.refresh();
|
||||
|
||||
this.updateMenuButtonDisplay();
|
||||
});
|
||||
|
||||
items.add(it);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package client.scenes;
|
||||
|
||||
import client.utils.ConfigService;
|
||||
import client.utils.LocaleAware;
|
||||
import client.utils.LocaleManager;
|
||||
import client.utils.ServerUtils;
|
||||
|
|
@ -28,6 +29,7 @@ public class SearchBarCtrl implements LocaleAware {
|
|||
|
||||
private final LocaleManager localeManager;
|
||||
private final ServerUtils serverUtils;
|
||||
private final ConfigService configService;
|
||||
|
||||
private Consumer<List<Recipe>> onSearchCallback;
|
||||
|
||||
|
|
@ -41,9 +43,10 @@ public class SearchBarCtrl implements LocaleAware {
|
|||
private Task<List<Recipe>> currentSearchTask = null;
|
||||
|
||||
@Inject
|
||||
public SearchBarCtrl(LocaleManager localeManager, ServerUtils serverUtils) {
|
||||
public SearchBarCtrl(LocaleManager localeManager, ServerUtils serverUtils, ConfigService configService) {
|
||||
this.localeManager = localeManager;
|
||||
this.serverUtils = serverUtils;
|
||||
this.configService = configService;
|
||||
}
|
||||
|
||||
@FXML
|
||||
|
|
@ -96,7 +99,7 @@ public class SearchBarCtrl implements LocaleAware {
|
|||
currentSearchTask = new Task<>() {
|
||||
@Override
|
||||
protected List<Recipe> call() throws IOException, InterruptedException {
|
||||
return serverUtils.getRecipesFiltered(filter);
|
||||
return serverUtils.getRecipesFiltered(filter, configService.getConfig().getRecipeLanguages());
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import javafx.scene.control.Button;
|
|||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
|
@ -77,6 +78,9 @@ public class RecipeDetailCtrl implements LocaleAware {
|
|||
@FXML
|
||||
private Button favouriteButton;
|
||||
|
||||
@FXML
|
||||
private ComboBox<String> langSelector;
|
||||
|
||||
private ListView<Recipe> getParentRecipeList() {
|
||||
return this.appCtrl.recipeList;
|
||||
}
|
||||
|
|
@ -138,6 +142,7 @@ public class RecipeDetailCtrl implements LocaleAware {
|
|||
this.showName(recipe.getName());
|
||||
this.ingredientListController.refetchFromRecipe(recipe);
|
||||
this.stepListController.refetchFromRecipe(recipe);
|
||||
this.langSelector.setValue(recipe.getLocale());
|
||||
this.refreshFavouriteButton();
|
||||
}
|
||||
|
||||
|
|
@ -294,6 +299,20 @@ public class RecipeDetailCtrl implements LocaleAware {
|
|||
editableTitleArea.getChildren().add(nameLabel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch the recipe's language.
|
||||
*/
|
||||
@FXML
|
||||
void changeLanguage() {
|
||||
recipe.setLocale(this.langSelector.getValue());
|
||||
|
||||
try {
|
||||
server.updateRecipe(this.recipe);
|
||||
} catch (IOException | InterruptedException e) {
|
||||
throw new UpdateException("Error occurred when updating recipe locale!");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateText() {
|
||||
editRecipeTitleButton.setText(getLocaleString("menu.button.edit"));
|
||||
|
|
@ -309,5 +328,7 @@ public class RecipeDetailCtrl implements LocaleAware {
|
|||
@Override
|
||||
public void initializeComponents() {
|
||||
initStepsIngredientsList();
|
||||
|
||||
langSelector.getItems().addAll("en", "nl", "pl");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ public class Config {
|
|||
|
||||
|
||||
private String language = "en";
|
||||
private List<String> recipeLanguages = new ArrayList<>();
|
||||
private String serverUrl = "http://localhost:8080";
|
||||
|
||||
private List<Long> favourites = new ArrayList<>();
|
||||
|
|
@ -66,5 +67,28 @@ public class Config {
|
|||
public void removeFavourite(long recipeId) {
|
||||
getFavourites().remove(recipeId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of languages that should filter the displayed recipes.
|
||||
*
|
||||
* @return The desired languages the user would like to see.
|
||||
*/
|
||||
public List<String> getRecipeLanguages() {
|
||||
return this.recipeLanguages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a language to the list of filtering languages.
|
||||
*/
|
||||
public void addRecipeLanguage(String lang) {
|
||||
this.recipeLanguages.add(lang);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a language from the list of filtering languages.
|
||||
*/
|
||||
public void removeRecipeLanguage(String lang) {
|
||||
this.recipeLanguages.remove(lang);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -40,9 +40,16 @@ public class ServerUtils {
|
|||
* Gets all the recipes from the backend.
|
||||
* @return a JSON string with all the recipes
|
||||
*/
|
||||
public List<Recipe> getRecipes() throws IOException, InterruptedException {
|
||||
public List<Recipe> getRecipes(List<String> locales) throws IOException, InterruptedException {
|
||||
|
||||
String uri =
|
||||
SERVER +
|
||||
"/recipes" +
|
||||
"?locales=" +
|
||||
String.join(",", locales);
|
||||
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(SERVER + "/recipes"))
|
||||
.uri(URI.create(uri))
|
||||
.GET()
|
||||
.build();
|
||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
|
|
@ -57,9 +64,9 @@ public class ServerUtils {
|
|||
return list; // JSON string-> List<Recipe> (Jackson)
|
||||
}
|
||||
|
||||
public List<Recipe> getRecipesFiltered(String filter) throws IOException, InterruptedException {
|
||||
public List<Recipe> getRecipesFiltered(String filter, List<String> locales) throws IOException, InterruptedException {
|
||||
// TODO: implement filtering on server side
|
||||
return this.getRecipes();
|
||||
return this.getRecipes(locales);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -88,7 +95,7 @@ public class ServerUtils {
|
|||
*/
|
||||
public Recipe addRecipe(Recipe newRecipe) throws IOException, InterruptedException {
|
||||
//Make sure the name of the newRecipe is unique
|
||||
List<Recipe> allRecipes = getRecipes();
|
||||
List<Recipe> allRecipes = getRecipes(List.of());
|
||||
newRecipe.setId(null); // otherwise the id is the same as the original, and that's wrong
|
||||
// now that each recipeIngredient has its own ID in the database,
|
||||
// we set that to null too to force a new persist value on the server
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@
|
|||
</Label>
|
||||
|
||||
<fx:include source="SearchBar.fxml" fx:id="searchBar" />
|
||||
<fx:include source="LanguageFilter.fxml" fx:id="langFilter" />
|
||||
|
||||
<ListView fx:id="recipeList" />
|
||||
|
||||
|
|
|
|||
12
client/src/main/resources/client/scenes/LanguageFilter.fxml
Normal file
12
client/src/main/resources/client/scenes/LanguageFilter.fxml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.scene.control.MenuButton?>
|
||||
|
||||
<MenuButton
|
||||
mnemonicParsing="false"
|
||||
text="Languages"
|
||||
xmlns:fx="http://javafx.com/fxml/1"
|
||||
xmlns="http://javafx.com/javafx/25"
|
||||
fx:controller="client.scenes.LanguageFilterCtrl"
|
||||
fx:id="langFilterMenu"
|
||||
/>
|
||||
|
|
@ -27,6 +27,8 @@
|
|||
<Button fx:id="favouriteButton" onAction="#toggleFavourite" text="☆" />
|
||||
</HBox>
|
||||
|
||||
<ComboBox fx:id="langSelector" onAction="#changeLanguage" />
|
||||
|
||||
<!-- Ingredients -->
|
||||
<fx:include source="IngredientList.fxml" fx:id="ingredientList"
|
||||
VBox.vgrow="ALWAYS" maxWidth="Infinity" />
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@ menu.button.edit=Edit
|
|||
menu.button.clone=Clone
|
||||
menu.button.print=Print recipe
|
||||
|
||||
menu.label.selected-langs=Languages
|
||||
|
||||
lang.en.display=English
|
||||
lang.nl.display=Dutch
|
||||
lang.pl.display=Polish
|
||||
|
|
@ -26,6 +26,9 @@ menu.button.clone=Clone
|
|||
menu.button.print=Print recipe
|
||||
|
||||
menu.search=Search...
|
||||
|
||||
menu.label.selected-langs=Languages
|
||||
|
||||
lang.en.display=English
|
||||
lang.nl.display=Nederlands
|
||||
lang.pl.display=Polski
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@ menu.button.edit=Bewerken
|
|||
menu.button.clone=Dupliceren
|
||||
menu.button.print=Recept afdrukken
|
||||
|
||||
menu.label.selected-langs=Talen
|
||||
|
||||
menu.search=Zoeken...
|
||||
lang.en.display=English
|
||||
lang.nl.display=Nederlands
|
||||
|
|
|
|||
|
|
@ -26,6 +26,9 @@ menu.button.clone=Duplikuj
|
|||
menu.button.print=Drukuj przepis
|
||||
|
||||
menu.search=Szukaj...
|
||||
|
||||
menu.label.selected-langs=J?zyki
|
||||
|
||||
lang.en.display=English
|
||||
lang.nl.display=Nederlands
|
||||
lang.pl.display=Polski
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ class ServerUtilsTest {
|
|||
void tearDown() throws IOException, InterruptedException {
|
||||
// Not applicable in pipeline testing
|
||||
Assumptions.assumeTrue(dv.isServerAvailable(), "Server not available");
|
||||
dv.getRecipes().stream().map(Recipe::getId).forEach(id -> {
|
||||
dv.getRecipes(List.of()).stream().map(Recipe::getId).forEach(id -> {
|
||||
try {
|
||||
dv.deleteRecipe(id);
|
||||
} catch (Exception ex) {
|
||||
|
|
@ -77,7 +77,7 @@ class ServerUtilsTest {
|
|||
|
||||
@Test
|
||||
void getAllRecipesTest() throws IOException, InterruptedException {
|
||||
List<Recipe> recipes = dv.getRecipes();
|
||||
List<Recipe> recipes = dv.getRecipes(List.of());
|
||||
|
||||
assertNotNull(recipes, "The list should not be null");
|
||||
assertTrue(recipes.size() >= 0, "The list should be 0 (when no recipes), or more");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue