Skip to content
Closed
1 change: 1 addition & 0 deletions CHANGELOG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ This file documents all notable changes to https://github.com/devonfw/IDEasy[IDE

Release with new features and bugfixes:

* https://github.com/devonfw/IDEasy/issues/2126[#2126]: Fix language dropdown in GUI
* https://github.com/devonfw/IDEasy/issues/2176[#2176]: Support 7z archive extraction
* https://github.com/devonfw/IDEasy/issues/2100[#2100]: Fix Python not available for Mac x64

Expand Down
2 changes: 1 addition & 1 deletion gui/src/main/java/com/devonfw/ide/gui/MainController.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import java.util.List;
import java.util.Locale;
import java.util.Map;

import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
Expand Down Expand Up @@ -76,7 +77,6 @@ private void initialize() {
}

private void initLanguageComboBox() {

this.languageMap.clear();
selectedLanguage.getItems().clear();

Expand Down
16 changes: 11 additions & 5 deletions gui/src/main/java/com/devonfw/ide/gui/nls/NlsService.java
Original file line number Diff line number Diff line change
Expand Up @@ -68,20 +68,23 @@ public class NlsService {
* @param locale the preferred locale, or {@code null} to use persisted or default locale.
*/
public NlsService(Locale locale) {

this.availableLocales = getAvailableLocales();
initialize(locale);
}

/**
* Initializes the service using an explicit, persisted, or system default locale.
* Initializes the service using an explicit, persisted, or system default locale (if applicable) or English as fallback.
*
* @param explicitLocale the locale requested by the caller, or {@code null}.
*/
private void initialize(Locale explicitLocale) {

Locale localeToApply = explicitLocale;
if (localeToApply == null) {
localeToApply = Locale.getDefault();
// Locale.getDefault() returns "de_DE" (for systems set to German) which isn't the same as "de"
localeToApply = Locale.of(Locale.getDefault().getLanguage());
if (!availableLocales.contains(localeToApply)) {
localeToApply = Locale.ENGLISH;
}
}
applyLocale(localeToApply, false);
Comment on lines +83 to 89

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO there is a small misunderstanding of the Locale concept.
Internationalisation (l18n) defines a inheritance tree of:

  • root (the "empty" locale as Locale.ROOT)
  • the language level (e.g. de, en, es, etc.)
  • country specific aspects within a language (e.g. British English as en_GB uses different spelling than American English en_US, you could even differentiate aspects to German for Austria in de_AT).
  • there is even another level below but lets stop at this point.

So we can have a resource bundle with de for German translation.
However, we could also have en_US for American English specialities.

You might think that this does not matter since IDEasy does not do country specific differentiations. However, JavaFx does a lot of things when the locale changes.
For that we cannot simply remove such aspects here since the localeToApply is actually what is applied to JavaFx.
Therefore line 84 will sooner or later cause undesired bugs.

Great that you traced down the reason why our language dropdown was having problems because the implemented in our team was not aware of the difference between de and de_DE.
However, only consider this for the dropdown but do not change the Locale that is applied.
Some users with LTR instead of RTL writing will not like if their GUI behaves broken...
Also bear in mind that with AI translation becomes cheap.
We might introduce en_US with many other languages one day via automatic translation.

@oanding-blrng oanding-blrng Jul 29, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the explanation. After some testing (documented with a comment under the issue #2126 (comment)) on a clean branch I came up with a different solution. We could just introduce a new messages_en.properties file. This would mean, that the ResourceBundle would find the proper bundle and because of how ResourceBundle is working, he would go up the chain (in this case) to the root properties, which is containing the English localization.

messages_en.properties would be completely empty with just a comment to make additions/edits to the English localization within the root properties file.

Therefore no assumptions had to made about the structure and we don't remove certain information about the locale the user is using.

Would this be a better solution @hohwille ?

@oanding-blrng oanding-blrng Jul 29, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I created a new pr #2234 with the new approach. This pr is ready for the team-review, but I wanted to wait for your opinion before kicking of the process of the team review.

If the new approach is suitable, we can close this pr and work with the new one.

}
Expand Down Expand Up @@ -240,7 +243,10 @@ private void loadBundle() {
}

private ResourceBundle loadBundle(Locale locale) {

// ResourceBundle.getBundle(...) with Locale.ENGLISH would return German properties on system locale = German
if (locale.equals(Locale.ENGLISH)) {
return ResourceBundle.getBundle(BUNDLE_NAME, Locale.ROOT);
}
return ResourceBundle.getBundle(BUNDLE_NAME, locale);
}
Comment on lines +246 to 251

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same kind of error here:
The resource bundle will do the proper inheritance automatically.
Our code shall not hard-code crazy assumptions about the current structure of our *_de.properties and *.properties vs. *_en.properties, etc.


Expand Down
34 changes: 23 additions & 11 deletions gui/src/test/java/com/devonfw/ide/gui/nls/NlsServiceTest.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.devonfw.ide.gui.nls;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.params.provider.Arguments.arguments;

import java.io.IOException;
import java.io.InputStream;
Expand All @@ -17,11 +18,15 @@
import java.util.concurrent.atomic.AtomicInteger;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import java.util.stream.Stream;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import com.devonfw.ide.gui.context.IdeGuiStateManager;

Expand Down Expand Up @@ -56,16 +61,28 @@ public void tearDown() {
}
}

@Test
public void testGetInstanceWithLocale() {

NlsService service = new NlsService(Locale.ENGLISH);
@ParameterizedTest
@MethodSource("localeProvider")
public void testGetInstanceWithLocale(Locale systemLocale, Locale providedLocal, Locale expectedServiceLocal, String expectedString) {
Locale.setDefault(systemLocale);
NlsService service = new NlsService(providedLocal);

assertThat(service.getLocale()).isEqualTo(Locale.ENGLISH);
assertThat(service.getLocale()).isEqualTo(expectedServiceLocal);
assertThat(service.getResourceBundle()).isNotNull();
assertThat(service.get("CurrentLanguage")).isEqualTo("English (en)");
assertThat(service.get("CurrentLanguage")).isEqualTo(expectedString);
}

static Stream<Arguments> localeProvider() {
return Stream.of(
arguments(Locale.GERMANY, Locale.ENGLISH, Locale.ENGLISH, "English (en)"),
arguments(Locale.GERMANY, Locale.GERMAN, Locale.GERMAN, "Deutsch (de)"),
arguments(Locale.GERMANY, null, Locale.GERMAN, "Deutsch (de)"),
arguments(Locale.UK, Locale.ENGLISH, Locale.ENGLISH, "English (en)"),
arguments(Locale.UK, Locale.GERMAN, Locale.GERMAN, "Deutsch (de)"),
arguments(Locale.UK, null, Locale.ENGLISH, "English (en)"),
arguments(Locale.FRANCE, null, Locale.ENGLISH, "English (en)")
);
}

@Test
public void testSetLocale() {
Expand All @@ -78,7 +95,6 @@ public void testSetLocale() {
assertThat(service.get("CurrentLanguage")).isEqualTo("Deutsch (de)");
}


@Test
public void testAllLocalizationBundlesContainExactlyTheEnglishKeys() throws IOException {

Expand Down Expand Up @@ -147,7 +163,6 @@ public void testSetLocalePersistsSelectionInUserHomeIdeProperties() throws IOExc
assertThat(properties.getProperty("IDE_OPTIONS")).isEqualTo("-Duser.language=de");
}


@Test
public void testPersistLocaleUpdatesExistingUserLangInIdeOptions() throws IOException {

Expand Down Expand Up @@ -314,6 +329,3 @@ private static void writeJarEntry(JarOutputStream jos, String name) throws IOExc
}

}



Loading