mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-22 16:17:44 +00:00
dwa
This commit is contained in:
1 parent
f6f06c1ab9
commit
9e1e202f15
135 files changed
+34492
-1795
No files matched your search
@@ -10,6 +10,7 @@ public class PaperLibBootstrapTest {
|
||||
public void isModernVersionSchemeAcceptsMajorVersionAboveOne() {
|
||||
assertTrue(PaperLibBootstrap.isModernVersionScheme("26.2-R0.1-SNAPSHOT"));
|
||||
assertTrue(PaperLibBootstrap.isModernVersionScheme("26.2"));
|
||||
assertTrue(PaperLibBootstrap.isModernVersionScheme("26.2.build.2614-stable"));
|
||||
assertTrue(PaperLibBootstrap.isModernVersionScheme("27.0.0-R0.1-SNAPSHOT"));
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,576 @@
|
||||
package art.arcane.iris.core.localization;
|
||||
|
||||
import art.arcane.volmlib.util.localization.MessageArgument;
|
||||
import art.arcane.volmlib.util.localization.LocaleOverlay;
|
||||
import art.arcane.volmlib.util.localization.LocalizationValidationResult;
|
||||
import art.arcane.volmlib.util.localization.LocalizationValidator;
|
||||
import art.arcane.volmlib.util.localization.LinesValue;
|
||||
import art.arcane.volmlib.util.localization.MessageKey;
|
||||
import art.arcane.volmlib.util.localization.MessageValue;
|
||||
import art.arcane.volmlib.util.localization.PluralValue;
|
||||
import art.arcane.volmlib.util.localization.TextValue;
|
||||
import art.arcane.volmlib.util.localization.VolmitLocales;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class IrisLanguageTest {
|
||||
private static final Pattern PLACEHOLDER = Pattern.compile("\\{[A-Za-z][A-Za-z0-9_]*}");
|
||||
private static final Pattern COLOR_CODE = Pattern.compile("(?i)(?:§|&)[0-9A-FK-ORX]");
|
||||
private static final Pattern MINI_MESSAGE_TAG = Pattern.compile("<[^<>\\n]+>");
|
||||
private static final Pattern URL = Pattern.compile("\\b[a-z][a-z0-9+.-]*://\\S+");
|
||||
private static final Pattern COMMAND = Pattern.compile(
|
||||
"/iris(?:\\s|$).*?(?=(?:\\s+(?:to|for|from|when|or)\\s+)|(?:\\s+(?:first|again)\\b)|[.;,)\\n]|$)"
|
||||
+ "|/execute(?:\\s|$)[^.;,)\\n]*"
|
||||
);
|
||||
private static final Pattern IDENTIFIER = Pattern.compile(
|
||||
"\\b[a-z][a-z0-9_]*:[a-z0-9_./{}-]+\\b"
|
||||
+ "|(?:\\*/)?(?:plugins|world|objects|dimensions|structures|jigsaw-pieces|jigsaw-pools|packs|dump|config|assets|data)(?:/[A-Za-z0-9_*{}.-]*[A-Za-z0-9_*{}-])+/?"
|
||||
+ "|\\b[A-Za-z0-9_*{}.-]+\\.(?:json|toml|ya?ml|jar|nbt|iob|properties|log|zip)\\b"
|
||||
);
|
||||
private static final Pattern PROPER_NAME = Pattern.compile(
|
||||
"\\b(?:IrisDimensions|Iris|Minecraft|CraftBukkit|Bukkit|Paper|Folia|Fabric|NeoForge|Forge|Modrinth|GitHub|VSCode|Brigadier|Mantle|MiniMessage|Adventure|Sentry|NMS|NBT|JSON|TOML|YAML|GUI|HUD|TPS|MSPT|FPS|CPU|GPU|RAM|JAR|API|UUID|BUD|ETA|HD|LQ|ms)\\b"
|
||||
);
|
||||
private static final List<String> IRIS_PRODUCT_NAMES = List.of(
|
||||
"Iris Vision",
|
||||
"Object Studio",
|
||||
"Noise Explorer",
|
||||
"Volmit Software",
|
||||
"WorldEdit",
|
||||
"ResourceManager",
|
||||
"GoldenHash",
|
||||
"IGenData",
|
||||
"Sentry",
|
||||
"TectonicPlates"
|
||||
);
|
||||
private static final Pattern MARKER_DEBRIS = Pattern.compile("(?:⟬|⟭|\\b(?:XQ|QZ)[A-Z0-9_]*\\b|[\\uE000-\\uF8FF])");
|
||||
private static final Pattern MOJIBAKE = Pattern.compile("(?:Ã[©¨ªº§³´¼¶¢£¥]|Â[©®°±·«» ]|â(?:€|€™|€œ|€|€“|€”|€¦)|ðŸ)");
|
||||
private static final Pattern CONTROL_CHARACTER = Pattern.compile("[\\p{Cc}&&[^\\n\\r\\t]]");
|
||||
private static final Pattern WORD = Pattern.compile(
|
||||
"[\\p{L}\\p{N}_]+"
|
||||
);
|
||||
private static final List<Pattern> REQUIRED_LITERAL_PATTERNS = List.of(
|
||||
MINI_MESSAGE_TAG,
|
||||
URL,
|
||||
COMMAND,
|
||||
IDENTIFIER,
|
||||
PROPER_NAME
|
||||
);
|
||||
private static final Map<String, Pattern> FORBIDDEN_TRANSLATION_ARTIFACTS = Map.ofEntries(
|
||||
Map.entry("es_ES", Pattern.compile("(?iu)\\b(?:Pónganse|sdatapackImports|TectonicPlates Conde|Iris World Director|FED 7)\\b")),
|
||||
Map.entry("fr_FR", Pattern.compile("(?iu)\\b(?:sentinelle|groupe électrogène|Iris Directeur mondial|FED 7)\\b")),
|
||||
Map.entry("he_IL", Pattern.compile("וניל")),
|
||||
Map.entry("it_IT", Pattern.compile("(?iu)\\bMonolocale\\b")),
|
||||
Map.entry("ja-JP", Pattern.compile("お問い合わせ|返品について|データパックの摂取|構成されたdatapackの輸入|§a通信|§7ログイン|包装次元|バリアフリー Iris|第一次世界|サイトマップ|コンタクトサポート|新着情報|生物医学|プレジェント|パユース|ドーワン|フィードバック|簡体中文|ジャグジー")),
|
||||
Map.entry("ko_KR", Pattern.compile("회사연혁|사이트맵|뚱 베어|페이스 북|스페인 사람|이름 \\*|관련 기사|지원하다|이 모수|내 계정|세계 가족|스타트 낙하|견적 요청|포장 차원|세계 시장|제품\\s*정보|기타\\s*제품|₢")),
|
||||
Map.entry("lt_LT", Pattern.compile("(?iu)\\b(?:vanilė|vanilės)\\b")),
|
||||
Map.entry("fi_FI", Pattern.compile("(?iu)\\bYksiö\\b")),
|
||||
Map.entry("nl_NL", Pattern.compile("(?iu)\\b(?:StudioName|Vanille)\\b")),
|
||||
Map.entry("pl_PL", Pattern.compile("(?iu)\\bwanili[\\p{L}]*\\b")),
|
||||
Map.entry("ru_RU", Pattern.compile("(?iu)\\bванил[\\p{L}]*\\b")),
|
||||
Map.entry("tr_TR", Pattern.compile("(?iu)Studio\\s+Stüdyo|\\bvanilya\\b")),
|
||||
Map.entry("zh_CN", Pattern.compile("香草|虹膜|艾里斯|爱丽丝|地幔|包装|发电机|装入|卸货|包子|快跑 /iris")),
|
||||
Map.entry("zh_TW", Pattern.compile("香草|虹膜|艾里斯|愛麗絲|地幔|包裝|包装|發電機|发电机|裝入|装入|卸貨|卸货|解除安裝|包子|快跑 /iris"))
|
||||
);
|
||||
private static final String STRUCTURAL_CHARACTERS = "%\\[]{}<>\n";
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||
|
||||
private File dataFolder;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
dataFolder = temporaryFolder.newFolder();
|
||||
assertTrue(IrisLanguage.reload(dataFolder, "en_US"));
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
assertTrue(IrisLanguage.reload(dataFolder, "en_US"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void usesCodeOwnedEnglishWithoutLocaleFile() {
|
||||
String rendered = IrisLanguage.plain(
|
||||
IrisMessages.COMMAND_PERMISSION_DENIED,
|
||||
MessageArgument.untrusted("permission", "iris.all")
|
||||
);
|
||||
|
||||
assertEquals("You lack the permission 'iris.all'", rendered);
|
||||
assertEquals("en_US", IrisLanguage.activeLocale());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void appliesExternalOverlayAndFallsBackPerKey() throws Exception {
|
||||
writeOverride("de_DE", """
|
||||
{
|
||||
"locale": "de_DE",
|
||||
"messages": {
|
||||
"iris.command.permission_denied": "Fehlende Berechtigung: {permission}"
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
assertTrue(IrisLanguage.reload(dataFolder, "de_DE"));
|
||||
assertEquals(
|
||||
"Fehlende Berechtigung: iris.all",
|
||||
IrisLanguage.plain(
|
||||
IrisMessages.COMMAND_PERMISSION_DENIED,
|
||||
MessageArgument.untrusted("permission", "iris.all")
|
||||
)
|
||||
);
|
||||
assertFalse("Unknown Iris command".equals(IrisLanguage.plain(IrisMessages.COMMAND_UNKNOWN)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bundledLocalesMatchSharedManifestAndCoverEntireCatalog() throws Exception {
|
||||
assertEquals(17, VolmitLocales.nonEnglish().size());
|
||||
for (String locale : VolmitLocales.nonEnglish()) {
|
||||
LocaleOverlay overlay = IrisLanguage.loadBundledOverlay(locale);
|
||||
assertEquals(locale, overlay.locale());
|
||||
assertEquals(IrisLanguage.catalog().ids(), overlay.values().keySet());
|
||||
|
||||
LocalizationValidationResult validation = LocalizationValidator.validate(
|
||||
IrisLanguage.catalog(),
|
||||
List.of(overlay)
|
||||
);
|
||||
assertTrue(locale + " errors: " + validation.errors(), validation.errors().isEmpty());
|
||||
assertTrue(locale + " warnings: " + validation.warnings(), validation.warnings().isEmpty());
|
||||
|
||||
int translated = 0;
|
||||
for (MessageKey key : IrisLanguage.catalog().keys()) {
|
||||
if (!key.englishValue().equals(overlay.value(key.id()))) {
|
||||
translated++;
|
||||
}
|
||||
assertValueIntegrity(locale, key.id(), key.englishValue(), overlay.value(key.id()));
|
||||
}
|
||||
assertTrue(locale + " contains too many English placeholder values", translated >= 1050);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bundledServerResourcesExactlyMatchNonEnglishManifest() throws Exception {
|
||||
Set<String> expected = VolmitLocales.nonEnglish().stream()
|
||||
.map(locale -> locale + ".json")
|
||||
.collect(Collectors.toUnmodifiableSet());
|
||||
|
||||
assertEquals(expected, resourceFiles("languages"));
|
||||
assertFalse(expected.contains(VolmitLocales.ENGLISH + ".json"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bundledLocalesPreserveReservedWorldNames() throws Exception {
|
||||
for (String locale : VolmitLocales.nonEnglish()) {
|
||||
LocaleOverlay overlay = IrisLanguage.loadBundledOverlay(locale);
|
||||
TextValue irisName = (TextValue) overlay.value(
|
||||
BukkitCommandMessagesExtended.COMMAND_IRIS_YOU_CANNOT_USE_WORLD_NAME_IRIS_CREATING_WORLDS_AS_IRIS.id()
|
||||
);
|
||||
TextValue benchmarkName = (TextValue) overlay.value(
|
||||
BukkitCommandMessagesExtended.COMMAND_IRIS_YOU_CANNOT_USE_WORLD_NAME_BENCHMARK_CREATING_WORLDS_AS_IRIS.id()
|
||||
);
|
||||
|
||||
assertTrue(locale + " must preserve the reserved iris world name", irisName.template().contains("\"iris\""));
|
||||
assertTrue(locale + " must preserve the reserved benchmark world name", benchmarkName.template().contains("\"benchmark\""));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bundledLocaleLoadsWithoutUserOverride() {
|
||||
assertTrue(IrisLanguage.reload(dataFolder, "de_DE"));
|
||||
assertEquals("de_DE", IrisLanguage.activeLocale());
|
||||
assertFalse("Unknown Iris command".equals(IrisLanguage.plain(IrisMessages.COMMAND_UNKNOWN)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rejectsUnknownKeysAndPlaceholderShapeWhileRetainingLastGood() throws Exception {
|
||||
writeOverride("de_DE", """
|
||||
{
|
||||
"locale": "de_DE",
|
||||
"messages": {
|
||||
"iris.command.permission_denied": "Erlaubnis {permission} fehlt"
|
||||
}
|
||||
}
|
||||
""");
|
||||
assertTrue(IrisLanguage.reload(dataFolder, "de_DE"));
|
||||
|
||||
writeOverride("de_DE", """
|
||||
{
|
||||
"locale": "de_DE",
|
||||
"messages": {
|
||||
"iris.command.permission_denied": "Erlaubnis fehlt",
|
||||
"iris.command.not_real": "Unbekannt"
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
assertFalse(IrisLanguage.reload(dataFolder, "de_DE"));
|
||||
assertEquals(
|
||||
"Erlaubnis iris.all fehlt",
|
||||
IrisLanguage.plain(
|
||||
IrisMessages.COMMAND_PERMISSION_DENIED,
|
||||
MessageArgument.untrusted("permission", "iris.all")
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void untrustedArgumentsCannotInjectLegacyOrMiniMessageFormatting() throws Exception {
|
||||
writeOverride("de_DE", """
|
||||
{
|
||||
"locale": "de_DE",
|
||||
"messages": {
|
||||
"iris.command.permission_denied": "&aWert: {permission}"
|
||||
}
|
||||
}
|
||||
""");
|
||||
assertTrue(IrisLanguage.reload(dataFolder, "de_DE"));
|
||||
|
||||
String rendered = IrisLanguage.text(
|
||||
IrisMessages.COMMAND_PERMISSION_DENIED,
|
||||
MessageArgument.untrusted("permission", "&c<red>Bad\u00a74Name")
|
||||
);
|
||||
|
||||
assertTrue(rendered.startsWith("\u00a7aWert: "));
|
||||
assertTrue(rendered.endsWith("&c‹red›BadName"));
|
||||
assertFalse(rendered.contains("\u00a7c"));
|
||||
assertFalse(rendered.contains("\u00a74"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void argumentValuesCannotCascadeIntoLaterPlaceholderSentinels() {
|
||||
String rendered = IrisLanguage.plain(
|
||||
IrisMessages.COMMAND_RELOAD_FAILED,
|
||||
MessageArgument.untrusted("locale", "\uE0001\uE001"),
|
||||
MessageArgument.untrusted("activeLocale", "en_US")
|
||||
);
|
||||
|
||||
assertEquals(
|
||||
"Settings were reloaded, but locale \uE0001\uE001 was rejected; continuing with en_US.",
|
||||
rendered
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lookupsUseImmutableSnapshotWithoutReadingTheOverrideAgain() throws Exception {
|
||||
File override = writeOverride("de_DE", """
|
||||
{
|
||||
"locale": "de_DE",
|
||||
"messages": {
|
||||
"iris.command.unknown": "Unbekannter Iris-Befehl"
|
||||
}
|
||||
}
|
||||
""");
|
||||
assertTrue(IrisLanguage.reload(dataFolder, "de_DE"));
|
||||
Files.delete(override.toPath());
|
||||
|
||||
assertEquals("Unbekannter Iris-Befehl", IrisLanguage.plain(IrisMessages.COMMAND_UNKNOWN));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void plainRenderingPreservesCommandSyntaxPlaceholders() {
|
||||
assertEquals(
|
||||
"Use /iris download <pack> branch=<branch> to download manually.",
|
||||
IrisLanguage.plain(BukkitRuntimeMessages.STUDIO_S_V_C_USE_IRIS_DOWNLOAD_PACK_BRANCH_BRANCH_DOWNLOAD_MANUALLY)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void appliesLineAndPluralOverlayShapes() throws Exception {
|
||||
writeOverride("de_DE", """
|
||||
{
|
||||
"locale": "de_DE",
|
||||
"messages": {
|
||||
"iris.bukkit.runtime.commanddeveloper.update_world_warning": [
|
||||
"Sicherung erstellen.",
|
||||
"Mögliche Probleme:",
|
||||
" - Beschädigte Chunks",
|
||||
" - Neu generierte Chunks",
|
||||
" - Fehlende Strukturen",
|
||||
" - Nicht verbundene Höhlen",
|
||||
" - Nicht verbundene Geländeschichten",
|
||||
"Risiken bestätigt.",
|
||||
"Welt {world}, Paket {pack}"
|
||||
],
|
||||
"iris.bukkit.runtime.commandpack.more_warning_s": {
|
||||
"one": "Noch {count} Warnung.",
|
||||
"other": "Noch {count} Warnungen."
|
||||
}
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
assertTrue(IrisLanguage.reload(dataFolder, "de_DE"));
|
||||
assertEquals(
|
||||
"Sicherung erstellen.\n"
|
||||
+ "Mögliche Probleme:\n"
|
||||
+ " - Beschädigte Chunks\n"
|
||||
+ " - Neu generierte Chunks\n"
|
||||
+ " - Fehlende Strukturen\n"
|
||||
+ " - Nicht verbundene Höhlen\n"
|
||||
+ " - Nicht verbundene Geländeschichten\n"
|
||||
+ "Risiken bestätigt.\n"
|
||||
+ "Welt world, Paket pack",
|
||||
IrisLanguage.plain(
|
||||
BukkitRuntimeMessages.COMMAND_DEVELOPER_UPDATE_WORLD_WARNING,
|
||||
MessageArgument.untrusted("world", "world"),
|
||||
MessageArgument.untrusted("pack", "pack")
|
||||
)
|
||||
);
|
||||
assertEquals(
|
||||
"Noch 1 Warnung.",
|
||||
IrisLanguage.plain(
|
||||
BukkitRuntimeMessages.COMMAND_PACK_MORE_WARNING_S,
|
||||
MessageArgument.trusted("count", 1)
|
||||
)
|
||||
);
|
||||
assertEquals(
|
||||
"Noch 3 Warnungen.",
|
||||
IrisLanguage.plain(
|
||||
BukkitRuntimeMessages.COMMAND_PACK_MORE_WARNING_S,
|
||||
MessageArgument.trusted("count", 3)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private File writeOverride(String locale, String json) throws Exception {
|
||||
File file = new File(dataFolder, "languages/overrides/" + locale + ".json");
|
||||
Files.createDirectories(file.toPath().getParent());
|
||||
Files.writeString(file.toPath(), json, StandardCharsets.UTF_8);
|
||||
return file;
|
||||
}
|
||||
|
||||
private void assertValueIntegrity(String locale, String key, MessageValue english, MessageValue translated) {
|
||||
if (english instanceof TextValue englishText && translated instanceof TextValue translatedText) {
|
||||
assertTemplateIntegrity(locale, key, englishText.template(), translatedText.template());
|
||||
return;
|
||||
}
|
||||
if (english instanceof LinesValue englishLines && translated instanceof LinesValue translatedLines) {
|
||||
assertEquals(locale + ": " + key + " line count", englishLines.lines().size(), translatedLines.lines().size());
|
||||
for (int index = 0; index < englishLines.lines().size(); index++) {
|
||||
assertTemplateIntegrity(
|
||||
locale,
|
||||
key + "[" + index + "]",
|
||||
englishLines.lines().get(index),
|
||||
translatedLines.lines().get(index)
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (english instanceof PluralValue englishPlural && translated instanceof PluralValue translatedPlural) {
|
||||
assertEquals(locale + ": " + key + " plural forms", englishPlural.forms().keySet(), translatedPlural.forms().keySet());
|
||||
for (String form : englishPlural.forms().keySet()) {
|
||||
assertTemplateIntegrity(
|
||||
locale,
|
||||
key + "." + form,
|
||||
englishPlural.forms().get(form),
|
||||
translatedPlural.forms().get(form)
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
throw new AssertionError(locale + ": " + key + " has mismatched message value types");
|
||||
}
|
||||
|
||||
private void assertTemplateIntegrity(String locale, String key, String english, String translated) {
|
||||
String context = locale + ": " + key;
|
||||
assertEquals(context + " color codes", matches(COLOR_CODE, english), matches(COLOR_CODE, translated));
|
||||
for (int index = 0; index < STRUCTURAL_CHARACTERS.length(); index++) {
|
||||
char character = STRUCTURAL_CHARACTERS.charAt(index);
|
||||
assertEquals(
|
||||
context + " structural character " + character,
|
||||
countCharacter(english, character),
|
||||
countCharacter(translated, character)
|
||||
);
|
||||
}
|
||||
for (Pattern pattern : REQUIRED_LITERAL_PATTERNS) {
|
||||
Map<String, Integer> required = frequencies(matches(pattern, english));
|
||||
for (Map.Entry<String, Integer> entry : required.entrySet()) {
|
||||
assertTrue(
|
||||
context + " lost literal " + entry.getKey(),
|
||||
countLiteral(translated, entry.getKey()) >= entry.getValue()
|
||||
);
|
||||
}
|
||||
}
|
||||
assertFalse(context + " contains translation marker debris", MARKER_DEBRIS.matcher(translated).find());
|
||||
assertFalse(context + " contains an encoded ampersand", translated.contains("&"));
|
||||
assertFalse(context + " contains a replacement character", translated.contains("�"));
|
||||
assertFalse(context + " contains mojibake", MOJIBAKE.matcher(translated).find());
|
||||
assertFalse(context + " contains a control character", CONTROL_CHARACTER.matcher(translated).find());
|
||||
Pattern forbiddenArtifacts = FORBIDDEN_TRANSLATION_ARTIFACTS.get(locale);
|
||||
if (forbiddenArtifacts != null) {
|
||||
assertFalse(context + " contains a known translation artifact", forbiddenArtifacts.matcher(translated).find());
|
||||
}
|
||||
assertTechnicalTermIntegrity(locale, context, english, translated);
|
||||
assertTrue(
|
||||
context + " is pathologically longer than English",
|
||||
translated.length() <= Math.max(120, english.length() * 4 + 60)
|
||||
);
|
||||
assertFalse(
|
||||
context + " contains pathological repetition",
|
||||
hasPathologicalRepetition(translated) && !hasPathologicalRepetition(english)
|
||||
);
|
||||
}
|
||||
|
||||
private void assertTechnicalTermIntegrity(String locale, String context, String english, String translated) {
|
||||
if (!locale.equals("es_ES") && !locale.equals("fr_FR")) {
|
||||
return;
|
||||
}
|
||||
|
||||
String source = PLACEHOLDER.matcher(english).replaceAll("");
|
||||
String target = PLACEHOLDER.matcher(translated).replaceAll("");
|
||||
for (String productName : IRIS_PRODUCT_NAMES) {
|
||||
if (source.contains(productName)) {
|
||||
assertTrue(context + " must preserve product name " + productName, target.contains(productName));
|
||||
}
|
||||
}
|
||||
if (containsWord(source, "chunks?")) {
|
||||
assertTrue(context + " must preserve the Minecraft term chunk", containsWord(target, "chunks?"));
|
||||
assertFalse(context + " mistranslates chunk", artifact(locale, target, "pedazos?|trozos?|porciones?|tontos?|idiotas?|gorros?", "morceaux?|choux?"));
|
||||
}
|
||||
if (containsWord(source, "mantle")) {
|
||||
assertTrue(context + " must preserve Mantle", target.contains("Mantle"));
|
||||
assertFalse(context + " mistranslates Mantle", artifact(locale, target, "mantos?|manteles?", "manteaux?"));
|
||||
}
|
||||
String sourceWithoutDataPack = source.replaceAll("(?iu)\\bdata\\s+packs?\\b", "datapack");
|
||||
if (containsWord(sourceWithoutDataPack, "packs?")) {
|
||||
assertTrue(context + " must preserve the Iris term pack", containsWord(target, "packs?"));
|
||||
assertFalse(context + " mistranslates pack", artifact(locale, target, "paquetes?|embalajes?|envases?", "paquets?|boîtes?|boites?|emballages?|colis"));
|
||||
}
|
||||
if (containsWord(source, "vanilla")) {
|
||||
assertTrue(context + " must preserve the Minecraft term vanilla", containsWord(target, "vanilla"));
|
||||
assertFalse(context + " mistranslates vanilla", artifact(locale, target, "vainilla", "vanille"));
|
||||
}
|
||||
if (containsWord(source, "studio")) {
|
||||
assertTrue(context + " must preserve Studio", containsWord(target, "Studio"));
|
||||
}
|
||||
if (containsWord(source, "pastes?")) {
|
||||
assertFalse(context + " mistranslates paste", artifact(locale, target, "pastas?|sabores?", "pâtes?|saveurs?"));
|
||||
}
|
||||
if (containsWord(source, "unloads?|unloaded|unloading")) {
|
||||
assertFalse(context + " confuses unload with download", artifact(locale, target, "descarg(?:ar|a|ado|ando)", "télécharg(?:er|é|ement)"));
|
||||
}
|
||||
if (containsWord(source, "downloads?|downloaded|downloading")) {
|
||||
assertFalse(context + " confuses download with unload", artifact(locale, target, "retir(?:ar|ado|ando).{0,20}memoria", "décharg(?:er|é|ement)"));
|
||||
}
|
||||
if (containsWord(source, "spawns?|spawned|spawning")) {
|
||||
assertFalse(context + " mistranslates spawn", artifact(locale, target, "desov(?:ar|a|ado)|escup(?:ir|e|ido)", "fray(?:er|é|age)"));
|
||||
}
|
||||
if (containsWord(source, "benchmarks?|benchmarked|benchmarking")) {
|
||||
assertTrue(context + " must preserve benchmark", containsWord(target, "benchmarks?"));
|
||||
}
|
||||
if (locale.equals("fr_FR") && containsWord(source, "generators?")) {
|
||||
assertFalse(context + " uses the electrical sense of generator", containsWord(target, "groupes?\\s+électrogènes?"));
|
||||
}
|
||||
if (locale.equals("fr_FR") && containsWord(source, "caves?")) {
|
||||
assertFalse(context + " uses the cellar sense of cave", containsWord(target, "caves?"));
|
||||
}
|
||||
if (containsWord(source, "saves?|saved|saving")) {
|
||||
assertFalse(context + " uses the rescue sense of save", artifact(locale, target, "salv(?:ar|a|ado|ando)", "sauv(?:er|é)"));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean artifact(String locale, String value, String spanish, String french) {
|
||||
return containsWord(value, locale.equals("es_ES") ? spanish : french);
|
||||
}
|
||||
|
||||
private boolean containsWord(String value, String expression) {
|
||||
return Pattern.compile("(?iuU)\\b(?:" + expression + ")\\b").matcher(value).find();
|
||||
}
|
||||
|
||||
private boolean hasPathologicalRepetition(String value) {
|
||||
String withoutPlaceholders = PLACEHOLDER.matcher(value).replaceAll("");
|
||||
List<String> words = new ArrayList<>();
|
||||
Matcher matcher = WORD.matcher(withoutPlaceholders);
|
||||
while (matcher.find()) {
|
||||
words.add(matcher.group().toLowerCase(Locale.ROOT));
|
||||
}
|
||||
for (int index = 0; index + 2 < words.size(); index++) {
|
||||
if (words.get(index).equals(words.get(index + 1))
|
||||
&& words.get(index).equals(words.get(index + 2))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (int size = 2; size <= 4; size++) {
|
||||
for (int index = 0; index + size * 3 <= words.size(); index++) {
|
||||
if (words.subList(index, index + size).equals(words.subList(index + size, index + size * 2))
|
||||
&& words.subList(index, index + size).equals(words.subList(index + size * 2, index + size * 3))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private Set<String> resourceFiles(String directory) throws Exception {
|
||||
URL resource = IrisLanguageTest.class.getClassLoader().getResource(directory);
|
||||
assertNotNull("Missing resource directory: " + directory, resource);
|
||||
assertEquals("file", resource.getProtocol());
|
||||
try (Stream<Path> paths = Files.list(Path.of(resource.toURI()))) {
|
||||
return paths
|
||||
.filter(Files::isRegularFile)
|
||||
.map(path -> path.getFileName().toString())
|
||||
.collect(Collectors.toUnmodifiableSet());
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> matches(Pattern pattern, String value) {
|
||||
List<String> matches = new ArrayList<>();
|
||||
Matcher matcher = pattern.matcher(value);
|
||||
while (matcher.find()) {
|
||||
matches.add(matcher.group());
|
||||
}
|
||||
return matches;
|
||||
}
|
||||
|
||||
private Map<String, Integer> frequencies(List<String> values) {
|
||||
Map<String, Integer> frequencies = new LinkedHashMap<>();
|
||||
for (String value : values) {
|
||||
frequencies.merge(value, 1, Integer::sum);
|
||||
}
|
||||
return frequencies;
|
||||
}
|
||||
|
||||
private int countCharacter(String value, char character) {
|
||||
int count = 0;
|
||||
for (int index = 0; index < value.length(); index++) {
|
||||
if (value.charAt(index) == character) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
private int countLiteral(String value, String literal) {
|
||||
int count = 0;
|
||||
int index = 0;
|
||||
while ((index = value.indexOf(literal, index)) >= 0) {
|
||||
count++;
|
||||
index += literal.length();
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
||||
@@ -149,7 +149,7 @@ public class GoldenHashEngineTest {
|
||||
GoldenHashEngine hashEngine = new GoldenHashEngine(engine, request(GoldenHashEngine.Mode.AUTO), goldenDir, broken, feedback, progress());
|
||||
assertFalse(hashEngine.run());
|
||||
assertFalse(new File(goldenDir, "testdim-s1234-c0x0-r0.hashes").exists());
|
||||
assertTrue(feedback.fail.stream().anyMatch((String line) -> line.startsWith("GoldenHash aborted: 1 chunk(s)")));
|
||||
assertTrue(feedback.fail.stream().anyMatch((String line) -> line.startsWith("GoldenHash aborted: 1 chunk")));
|
||||
}
|
||||
|
||||
private GoldenHashEngine.Request request(GoldenHashEngine.Mode mode) {
|
||||
|
||||
-24
@@ -1,24 +0,0 @@
|
||||
package art.arcane.iris.util.common.plugin;
|
||||
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class VolmitSenderMiniMessageEscapeTest {
|
||||
@Test
|
||||
public void escapesApostrophesForQuotedHoverText() {
|
||||
String escaped = VolmitSender.escapeMiniMessageQuotedText("This world's dimension config");
|
||||
|
||||
assertEquals("This world\\'s dimension config", escaped);
|
||||
MiniMessage.miniMessage().deserialize("<hover:show_text:'" + escaped + "'>ok</hover>");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void escapesBackslashesBeforeQuotedHoverText() {
|
||||
String escaped = VolmitSender.escapeMiniMessageQuotedText("Path \\\\ data");
|
||||
|
||||
assertEquals("Path \\\\\\\\ data", escaped);
|
||||
MiniMessage.miniMessage().deserialize("<hover:show_text:'" + escaped + "'>ok</hover>");
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user