mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-25 21:40:05 +00:00
Fixed language reload fallback
- Invalid language updates retain the last valid messages - Malformed startup catalogs use English defaults - Unrelated reloads preserve personal language caches - Deleted locales use English without changing saved choices - Restored locale files resume their translations - Language catalogs include shared editor titles and remove obsolete usage messages
This commit is contained in:
1 parent
bc9b7e86d7
commit
e832239730
20 files changed
+142
-55
No files matched your search
@@ -5,6 +5,7 @@ import art.arcane.iris.localization.IrisMessages;
|
||||
import art.arcane.iris.testsupport.Await;
|
||||
import art.arcane.volmlib.util.hotload.ConfigHotloadEngine;
|
||||
import art.arcane.volmlib.util.localization.MessageArgument;
|
||||
import art.arcane.volmlib.util.localization.LanguageAudience;
|
||||
import java.time.Duration;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
@@ -17,7 +18,9 @@ import java.io.File;
|
||||
import java.io.PrintStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.attribute.FileTime;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
@@ -68,22 +71,23 @@ public class SettingsHotloadWatchTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void activeLocaleSnapshotAppliesAndInvalidSnapshotUsesEnglish() {
|
||||
public void activeLocaleSnapshotAppliesAndInvalidSnapshotRetainsValidMessages() {
|
||||
File override = override("en_US");
|
||||
String valid = locale("en_US", "Active {permission}");
|
||||
|
||||
assertTrue(watch.applySnapshot(present(override, valid)));
|
||||
assertEquals("Active " + PERMISSION, permissionMessage());
|
||||
|
||||
assertTrue(watch.applySnapshot(present(override, "{ invalid")));
|
||||
assertEquals("You lack the permission '" + PERMISSION + "'", permissionMessage());
|
||||
assertFalse(watch.applySnapshot(present(override, "{ invalid")));
|
||||
assertEquals("Active " + PERMISSION, permissionMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void activeLocaleDeletionFallsBackToCodeOwnedEnglish() {
|
||||
public void activeLocaleDeletionFallsBackToCodeOwnedEnglish() throws Exception {
|
||||
File override = override("en_US");
|
||||
assertTrue(watch.applySnapshot(present(override, locale("en_US", "Temporary {permission}"))));
|
||||
assertEquals("Temporary " + PERMISSION, permissionMessage());
|
||||
Files.delete(override.toPath());
|
||||
|
||||
assertTrue(watch.applySnapshot(missing(override)));
|
||||
|
||||
@@ -120,13 +124,13 @@ public class SettingsHotloadWatchTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invalidLocaleAllowsItsSettingsSwitchWithEnglishFallback() throws Exception {
|
||||
public void invalidLocaleRejectsItsSettingsSwitchAndRetainsTheCurrentDefault() throws Exception {
|
||||
Files.writeString(override("de_DE").toPath(), "{ invalid", StandardCharsets.UTF_8);
|
||||
|
||||
assertTrue(watch.applySnapshot(present(settingsFile, settings("de_DE"))));
|
||||
assertFalse(watch.applySnapshot(present(settingsFile, settings("de_DE"))));
|
||||
|
||||
assertEquals("de_DE", IrisSettings.get().getGeneral().getLanguage());
|
||||
assertEquals("de_DE", IrisLanguage.activeLocale());
|
||||
assertEquals("en_US", IrisSettings.get().getGeneral().getLanguage());
|
||||
assertEquals("en_US", IrisLanguage.activeLocale());
|
||||
assertEquals("You lack the permission '" + PERMISSION + "'", permissionMessage());
|
||||
}
|
||||
|
||||
@@ -226,7 +230,7 @@ public class SettingsHotloadWatchTest {
|
||||
}
|
||||
|
||||
@Test(timeout = 8_000L)
|
||||
public void oversizedActiveLanguageReportsOnceAndUsesEnglish() throws Exception {
|
||||
public void oversizedActiveLanguageReportsOnceAndRetainsValidMessages() throws Exception {
|
||||
File override = override("en_US");
|
||||
assertTrue(watch.applySnapshot(present(override, locale("en_US", "Active {permission}"))));
|
||||
byte[] oversized = new byte[2 * 1024 * 1024 + 1];
|
||||
@@ -238,11 +242,11 @@ public class SettingsHotloadWatchTest {
|
||||
diagnostic,
|
||||
"Failed to read watched Iris file " + override.getAbsolutePath()
|
||||
));
|
||||
assertEquals("You lack the permission '" + PERMISSION + "'", permissionMessage());
|
||||
assertEquals("Active " + PERMISSION, permissionMessage());
|
||||
}
|
||||
|
||||
@Test(timeout = 8_000L)
|
||||
public void malformedUtf8FailureIsDeduplicatedAndUsesEnglish() throws Exception {
|
||||
public void malformedUtf8FailureIsDeduplicatedAndRetainsValidMessages() throws Exception {
|
||||
File override = override("en_US");
|
||||
assertTrue(watch.applySnapshot(present(override, locale("en_US", "Active {permission}"))));
|
||||
Files.write(override.toPath(), new byte[]{(byte) 0xC3, 0x28});
|
||||
@@ -253,7 +257,74 @@ public class SettingsHotloadWatchTest {
|
||||
diagnostic,
|
||||
"Failed to read watched Iris file " + override.getAbsolutePath()
|
||||
));
|
||||
assertEquals("You lack the permission '" + PERMISSION + "'", permissionMessage());
|
||||
assertEquals("Active " + PERMISSION, permissionMessage());
|
||||
}
|
||||
|
||||
@Test(timeout = 30_000L)
|
||||
public void personalLanguageWatcherRetainsMalformedFilesAndHandlesDeletionAndRestoration() throws Exception {
|
||||
File personal = override("de_DE");
|
||||
Files.writeString(personal.toPath(), locale("de_DE", "Personal {permission}"));
|
||||
UUID player = UUID.randomUUID();
|
||||
IrisLanguage.start();
|
||||
try {
|
||||
IrisLanguage.selections().selectPlayer(player, "de_DE").get(5L, TimeUnit.SECONDS);
|
||||
Path preferences = dataFolder.toPath().resolve("languages/language-preferences.properties");
|
||||
String savedPreferences = Files.readString(preferences);
|
||||
Files.writeString(personal.toPath(), locale("de_DE", "Live {permission}"));
|
||||
awaitPersonalMessage(player, "Live " + PERMISSION);
|
||||
|
||||
Files.writeString(personal.toPath(), "[unterminated");
|
||||
captureErrorsWhilePolling("Could not reload Iris language de_DE", 150L);
|
||||
assertEquals("Live " + PERMISSION, personalMessage(player));
|
||||
|
||||
Files.writeString(override("en_US").toPath(), locale("en_US", "Server {permission}"));
|
||||
awaitPermissionMessage("Server " + PERMISSION);
|
||||
assertEquals("Live " + PERMISSION, personalMessage(player));
|
||||
assertEquals("[unterminated", Files.readString(personal.toPath()));
|
||||
|
||||
String partial = "[iris.command]\nunknown = 'Partial'\n";
|
||||
Files.writeString(personal.toPath(), partial);
|
||||
awaitPersonalUnknown(player, "Partial");
|
||||
assertEquals("You lack the permission '" + PERMISSION + "'", personalMessage(player));
|
||||
|
||||
Files.delete(personal.toPath());
|
||||
awaitPersonalUnknown(player, "Unknown Iris command");
|
||||
assertFalse(personal.exists());
|
||||
assertEquals("de_DE", IrisLanguage.selections().playerLocale(player).orElseThrow());
|
||||
assertEquals(savedPreferences, Files.readString(preferences));
|
||||
assertEquals("en_US", IrisLanguage.activeLocale());
|
||||
assertTrue(IrisLanguage.reload(dataFolder, "en_US"));
|
||||
assertEquals("Unknown Iris command", personalUnknown(player));
|
||||
assertFalse(personal.exists());
|
||||
|
||||
Files.writeString(personal.toPath(), partial);
|
||||
awaitPersonalUnknown(player, "Partial");
|
||||
assertEquals(savedPreferences, Files.readString(preferences));
|
||||
} finally {
|
||||
IrisLanguage.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
private void awaitPersonalMessage(UUID player, String expected) {
|
||||
assertTrue(Await.reached("the personal permission message '" + expected + "'", Duration.ofSeconds(5L), () -> {
|
||||
watch.checkConfigHotload();
|
||||
return expected.equals(personalMessage(player));
|
||||
}));
|
||||
}
|
||||
|
||||
private void awaitPersonalUnknown(UUID player, String expected) {
|
||||
assertTrue(Await.reached("the personal unknown-command message '" + expected + "'", Duration.ofSeconds(5L), () -> {
|
||||
watch.checkConfigHotload();
|
||||
return expected.equals(personalUnknown(player));
|
||||
}));
|
||||
}
|
||||
|
||||
private String personalMessage(UUID player) {
|
||||
return LanguageAudience.call(player, this::permissionMessage);
|
||||
}
|
||||
|
||||
private String personalUnknown(UUID player) {
|
||||
return LanguageAudience.call(player, () -> IrisLanguage.plain(IrisMessages.COMMAND_UNKNOWN));
|
||||
}
|
||||
|
||||
private void awaitPermissionMessage(String expected) {
|
||||
|
||||
@@ -130,7 +130,7 @@ public class IrisLanguageTest {
|
||||
|
||||
private LocaleOverlay loadSourceOverlay(String locale) throws Exception {
|
||||
Path source = ProjectPaths.moduleFile("src/main/resources/languages").resolve(locale + ".toml");
|
||||
return IrisLanguage.parseDownloadedOverlay(source.toString(), locale, Files.readString(source));
|
||||
return IrisLanguage.parseOverlay(source.toString(), locale, Files.readString(source));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -139,7 +139,7 @@ public class IrisLanguageTest {
|
||||
"iris.command.unknown" = "Unbekannter Iris-Befehl"
|
||||
"iris.modded.help.entry.command.version" = "Version anzeigen"
|
||||
""";
|
||||
LocaleOverlay downloaded = IrisLanguage.parseDownloadedOverlay("download", "de_DE", raw);
|
||||
LocaleOverlay downloaded = IrisLanguage.parseOverlay("download", "de_DE", raw);
|
||||
assertEquals(Set.of(IrisMessages.COMMAND_UNKNOWN.id()), downloaded.values().keySet());
|
||||
assertTrue(LocalizationValidator.validate(IrisLanguage.catalog(), List.of(downloaded)).errors().isEmpty());
|
||||
LocaleOverlay local = IrisLanguage.parseOverlay("language", "de_DE", raw);
|
||||
@@ -151,7 +151,7 @@ public class IrisLanguageTest {
|
||||
|
||||
@Test
|
||||
public void invalidPlaceholdersAreExcludedWithoutDiscardingValidMessages() {
|
||||
LocaleOverlay downloaded = IrisLanguage.parseDownloadedOverlay("download", "de_DE", """
|
||||
LocaleOverlay downloaded = IrisLanguage.parseOverlay("download", "de_DE", """
|
||||
"iris.command.permission_denied" = "Keine Erlaubnis"
|
||||
"iris.command.unknown" = "Unbekannter Iris-Befehl"
|
||||
""");
|
||||
@@ -238,7 +238,7 @@ public class IrisLanguageTest {
|
||||
public void generatedEnglishLanguageRoundTripsTheEntireCatalog() throws Exception {
|
||||
Path english = dataFolder.toPath().resolve("languages/en_US.toml");
|
||||
assertTrue(Files.isRegularFile(english));
|
||||
LocaleOverlay overlay = IrisLanguage.parseDownloadedOverlay(english.toString(), "en_US", Files.readString(english));
|
||||
LocaleOverlay overlay = IrisLanguage.parseOverlay(english.toString(), "en_US", Files.readString(english));
|
||||
assertEquals(IrisLanguage.catalog().ids(), overlay.values().keySet());
|
||||
for (MessageKey key : IrisLanguage.catalog().keys()) {
|
||||
assertEquals(key.id(), key.englishValue(), overlay.value(key.id()));
|
||||
@@ -475,16 +475,33 @@ public class IrisLanguageTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unreadableTomlUsesEnglishWithoutReplacingTheFile() throws Exception {
|
||||
public void unreadableTomlRetainsTheLastValidMessagesWithoutReplacingTheFile() throws Exception {
|
||||
assertTrue(IrisLanguage.reload(dataFolder, "de_DE"));
|
||||
String previous = IrisLanguage.plain(IrisMessages.COMMAND_UNKNOWN);
|
||||
String raw = "[unterminated";
|
||||
File language = writeLanguage("de_DE", raw);
|
||||
|
||||
assertTrue(IrisLanguage.reload(dataFolder, "de_DE"));
|
||||
assertEquals("Unknown Iris command", IrisLanguage.plain(IrisMessages.COMMAND_UNKNOWN));
|
||||
assertFalse(IrisLanguage.reload(dataFolder, "de_DE"));
|
||||
assertEquals(previous, IrisLanguage.plain(IrisMessages.COMMAND_UNKNOWN));
|
||||
assertEquals(raw, Files.readString(language.toPath()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void malformedStartupLanguageUsesBuiltInEnglishAndKeepsSelectionsAvailable() throws Exception {
|
||||
IrisLanguage.shutdown();
|
||||
String raw = "[unterminated";
|
||||
File language = writeLanguage("en_US", raw);
|
||||
try {
|
||||
assertTrue(IrisLanguage.reload(dataFolder, "en_US"));
|
||||
IrisLanguage.start();
|
||||
assertNotNull(IrisLanguage.selections());
|
||||
assertEquals("Unknown Iris command", IrisLanguage.plain(IrisMessages.COMMAND_UNKNOWN));
|
||||
assertEquals(raw, Files.readString(language.toPath()));
|
||||
} finally {
|
||||
IrisLanguage.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void englishEditsSurviveReloadAndMissingEnglishIsCreatedForAnotherLocale() throws Exception {
|
||||
String edited = "\"iris.command.unknown\" = \"My Iris command\"\n";
|
||||
|
||||
Reference in new issue
Block a user