Skip to content
This repository was archived by the owner on May 22, 2025. It is now read-only.

Commit b871b39

Browse files
committed
Implement localization validation
1 parent 2e23240 commit b871b39

File tree

4 files changed

+132
-30
lines changed

4 files changed

+132
-30
lines changed

Natives/AppDelegate.m

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,27 @@ - (void)applicationWillTerminate:(UIApplication *)application {
3131
}
3232
}
3333

34+
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
35+
// Проверяем корректность файлов локализации
36+
validateAllLocalizations();
37+
38+
// Инициализируем локализацию в начале запуска приложения
39+
NSString *languageCode = [[NSLocale preferredLanguages] firstObject];
40+
NSString *resourcePath = [NSBundle.mainBundle pathForResource:languageCode ofType:@"lproj"];
41+
42+
// Если не найден язык пользователя, используем английский по умолчанию
43+
if (!resourcePath || !validateLocalizationFile(languageCode)) {
44+
// Если основной язык не прошел валидацию, пробуем английский
45+
resourcePath = [NSBundle.mainBundle pathForResource:@"en" ofType:@"lproj"];
46+
}
47+
48+
// Загружаем бандл с локализацией и устанавливаем его
49+
if (resourcePath) {
50+
NSBundle *localizationBundle = [NSBundle bundleWithPath:resourcePath];
51+
[NSBundle setPreferredLocalizationsFromArray:[NSArray arrayWithObject:[localizationBundle preferredLocalizations].firstObject]];
52+
}
53+
54+
return YES;
55+
}
56+
3457
@end

Natives/resources/en.lproj/Localizable.strings

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/*
1+
/*
22
Localizable.strings
33
StringLocalization
44
*/
@@ -68,7 +68,7 @@
6868

6969
"init.migrateDir" = "The PojavLauncher data directory is now %@. All of your existing data has been moved to the new location.";
7070

71-
"java.error.missing_main_class" = "%@ is missing main class attribute. If this is a mod, put it into the mods folder of your selected instance.";
71+
"java.error.missing_main_class" = "%@ is missing main class attribute. If this is a mod, put it into the "mods" folder of your selected instance.";
7272
"java.error.missing_runtime" = "%@ requires Java %d or later to run. Please install it first and specify it in Manage Runtimes.";
7373

7474
"launcher.mcl.downloading_file" = "Downloading %@";
@@ -84,6 +84,10 @@
8484
"login.error.username.outOfRange" = "Username must be at least 3 characters and a maximum of 16 characters";
8585

8686
"login.alert.field.username" = "Username";
87+
"login.alert.field.password" = "Password";
88+
"login.error.fields.empty" = "Username or password is empty";
89+
"login.error.invalid_response" = "Invalid server response";
90+
"login.error.invalid_credentials" = "Invalid username or password";
8791

8892
"login.jit.checking" = "Checking for JIT";
8993
"login.jit.enabled" = "JIT has been enabled";
@@ -98,6 +102,7 @@
98102
"login.option.select" = "Select account";
99103
"login.option.demo" = "Demo account";
100104
"login.option.microsoft" = "Microsoft account";
105+
"login.option.elyby" = "Ely.by account";
101106
"login.option.local" = "Local account";
102107

103108
"login.warn.title.legacy_device" = "The next release of PojavLauncher will not be compatible with this device.";
@@ -125,6 +130,20 @@
125130
"login.msa.progress.acquireMCToken" = "(4/5) Acquiring the Minecraft token";
126131
"login.msa.progress.checkMCProfile" = "(5/5) Checking the Minecraft profile";
127132

133+
"login.ely.progress.auth" = "Authenticating with Ely.by...";
134+
"login.ely.progress.refresh" = "Refreshing Ely.by token...";
135+
"login.ely.error.auth" = "Failed to authenticate with Ely.by";
136+
"login.ely.error.refresh" = "Failed to refresh Ely.by token";
137+
"login.ely.error.authenticate" = "Authentication with Ely.by failed";
138+
"login.ely.error.session" = "Invalid Ely.by session";
139+
"login.ely.error.timeout" = "Ely.by connection timed out";
140+
"login.ely.2fa.title" = "Two-Factor Authentication";
141+
"login.ely.2fa.message" = "Please enter your two-factor authentication code";
142+
"login.ely.2fa.code" = "Authentication code";
143+
"login.ely.2fa.empty" = "Authentication code cannot be empty";
144+
"login.ely.2fa.incorrect" = "Incorrect authentication code";
145+
"login.cancelled" = "Login cancelled";
146+
128147
"News" = "News";
129148
"Profiles" = "Profiles";
130149
"Settings" = "Settings";
@@ -233,7 +252,7 @@
233252
"preference.manage_runtime.header.default" = "Preferred Java version";
234253
"preference.manage_runtime.default.1165" = "1.16.5 and older";
235254
"preference.manage_runtime.default.117" = "1.17 and newer";
236-
"preference.manage_runtime.footer.default" = "Minecraft 1.16.5 and older may run with a newer Java version, however compatibility with mods is not guaranteed. External runtimes can be placed in the java_runtimes folder. Changing Java version for Execute .jar only takes effect if the installer does not expect a newer Java version.";
255+
"preference.manage_runtime.footer.default" = "Minecraft 1.16.5 and older may run with a newer Java version, however compatibility with mods is not guaranteed. External runtimes can be placed in the "java_runtimes" folder. Changing Java version for "Execute .jar" only takes effect if the installer does not expect a newer Java version.";
237256
"preference.manage_runtime.footer.java8" = "This is the default version for Minecraft 1.16.5 and older.";
238257
"preference.manage_runtime.footer.java17" = "This is the default version for Minecraft 1.17 and newer.";
239258
"preference.manage_runtime.header.invalid" = "Invalid runtimes";
@@ -253,7 +272,7 @@
253272
"preference.title.custom_env" = "Environment variables";
254273
"preference.detail.custom_env" = "This option is under construction. Create and edit custom_env.txt in the meantime.";
255274

256-
"preference.title.debug_skip_wait_jit" = "Skip \"Waiting for JIT\" dialog";
275+
"preference.title.debug_skip_wait_jit" = "Skip "Waiting for JIT" dialog";
257276
"preference.detail.debug_skip_wait_jit" = "This does not allow you to launch without JIT, instead this skips debugger check, because check fails on certain jailbreaks.";
258277
"preference.title.debug_ipad_ui" = "Unlock iPadOS UI";
259278
"preference.detail.debug_ipad_ui" = "Unlock iPad-exclusive UI (alert, keyboard, etc.) if enabled or iPhone UI if disabled.";
@@ -324,3 +343,6 @@
324343
"controller_configurator.section.footer.menu_mappings" = "Bindings when the mouse is not locked, such as on the title screen or in the pause menu.";
325344
"controller_configurator.section.controller_style" = "Controller type";
326345
"controller_configurator.section.footer.controller_style" = "Show different controller button names in this configurator.";
346+
347+
"OK" = "OK";
348+
"Cancel" = "Cancel";

Natives/utils.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ UIViewController* currentVC();
6969
void openLink(UIViewController* sender, NSURL* link);
7070

7171
NSString* localize(NSString* key, NSString* comment);
72+
BOOL validateLocalizationFile(NSString* languageCode);
73+
void validateAllLocalizations(void);
7274
NSMutableDictionary* parseJSONFromFile(NSString *path);
7375
NSError* saveJSONToFile(NSDictionary *dict, NSString *path);
7476
void customNSLog(const char *file, int lineNumber, const char *functionName, NSString *format, ...);

Natives/utils.m

Lines changed: 81 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -95,36 +95,49 @@ void openLink(UIViewController* sender, NSURL* link) {
9595
}
9696

9797
NSString* localize(NSString* key, NSString* comment) {
98-
// 1. Попытка получить строку из основного бандла для текущего языка
99-
NSString *value = NSLocalizedString(key, comment);
100-
101-
// 2. Если строка не найдена (возвращен ключ), всегда пробовать английский,
102-
// независимо от текущего языка пользователя
103-
if ([value isEqualToString:key]) {
104-
NSString* path = [NSBundle.mainBundle pathForResource:@"en" ofType:@"lproj"];
105-
if (path) { // Убедиться, что путь существует
106-
NSBundle* englishBundle = [NSBundle bundleWithPath:path];
107-
if (englishBundle) {
108-
NSString *englishValue = [englishBundle localizedStringForKey:key value:comment table:nil];
109-
if (![englishValue isEqualToString:key]) { // Если найдено в английском
110-
value = englishValue;
98+
if (key == nil) {
99+
NSLog(@"[Localization] Warning: nil key passed to localize()");
100+
return comment ?: @"";
101+
}
102+
103+
NSString *value = nil;
104+
105+
@try {
106+
// 1. Попытка получить строку из основного бандла для текущего языка
107+
value = NSLocalizedString(key, comment);
108+
109+
// 2. Если строка не найдена (возвращен ключ), всегда пробовать английский,
110+
// независимо от текущего языка пользователя
111+
if ([value isEqualToString:key]) {
112+
NSString* path = [NSBundle.mainBundle pathForResource:@"en" ofType:@"lproj"];
113+
if (path) { // Убедиться, что путь существует
114+
NSBundle* englishBundle = [NSBundle bundleWithPath:path];
115+
if (englishBundle) {
116+
NSString *englishValue = [englishBundle localizedStringForKey:key value:comment table:nil];
117+
if (![englishValue isEqualToString:key]) { // Если найдено в английском
118+
value = englishValue;
119+
}
111120
}
112121
}
113122
}
114-
}
115-
116-
// 3. Если строка все еще не найдена (равна ключу), попробовать найти в UIKit
117-
if ([value isEqualToString:key]) {
118-
NSString *uikitValue = [[NSBundle bundleWithIdentifier:@"com.apple.UIKit"] localizedStringForKey:key value:comment table:nil];
119-
if (uikitValue && ![uikitValue isEqualToString:key]) { // Если найдено в UIKit
120-
value = uikitValue;
123+
124+
// 3. Если строка все еще не найдена (равна ключу), попробовать найти в UIKit
125+
if ([value isEqualToString:key]) {
126+
NSString *uikitValue = [[NSBundle bundleWithIdentifier:@"com.apple.UIKit"] localizedStringForKey:key value:comment table:nil];
127+
if (uikitValue && ![uikitValue isEqualToString:key]) { // Если найдено в UIKit
128+
value = uikitValue;
129+
}
121130
}
122-
}
123-
124-
// 4. Если после всех попыток строка все еще равна ключу, вернуть значение comment если оно есть,
125-
// иначе вернуть ключ как запасной вариант
126-
if ([value isEqualToString:key] && comment != nil) {
127-
return comment;
131+
132+
// 4. Если после всех попыток строка все еще равна ключу, вернуть значение comment если оно есть,
133+
// иначе вернуть ключ как запасной вариант
134+
if ([value isEqualToString:key] && comment != nil) {
135+
return comment;
136+
}
137+
} @catch (NSException *exception) {
138+
NSLog(@"[Localization] Error for key '%@': %@", key, exception.reason);
139+
// В случае ошибки возвращаем комментарий или ключ
140+
return comment ?: key;
128141
}
129142

130143
return value;
@@ -170,3 +183,45 @@ void setButtonPointerInteraction(UIButton *button) {
170183
return [NSClassFromString(@"UIPointerStyle") styleWithEffect:[NSClassFromString(@"UIPointerHighlightEffect") effectWithPreview:preview] shape:proposedShape];
171184
};
172185
}
186+
187+
BOOL validateLocalizationFile(NSString* languageCode) {
188+
NSString* path = [NSBundle.mainBundle pathForResource:languageCode ofType:@"lproj"];
189+
if (!path) {
190+
NSLog(@"[Localization] Warning: Could not find .lproj directory for language %@", languageCode);
191+
return NO;
192+
}
193+
194+
NSString* stringsPath = [path stringByAppendingPathComponent:@"Localizable.strings"];
195+
if (![[NSFileManager defaultManager] fileExistsAtPath:stringsPath]) {
196+
NSLog(@"[Localization] Warning: Localizable.strings not found for language %@", languageCode);
197+
return NO;
198+
}
199+
200+
NSError* error = nil;
201+
NSDictionary* stringsDictionary = [NSDictionary dictionaryWithContentsOfFile:stringsPath error:&error];
202+
if (!stringsDictionary || error) {
203+
NSLog(@"[Localization] Error: Invalid Localizable.strings for language %@: %@", languageCode, error.localizedDescription);
204+
// Можно сохранить детали ошибки для диагностики
205+
NSString* errorPath = [NSString stringWithFormat:@"%s/localization_error_%@.log", getenv("POJAV_HOME"), languageCode];
206+
NSString* fileContents = [NSString stringWithContentsOfFile:stringsPath encoding:NSUTF8StringEncoding error:nil];
207+
NSString* errorLog = [NSString stringWithFormat:@"Error: %@\n\nFile contents:\n%@", error.localizedDescription, fileContents];
208+
[errorLog writeToFile:errorPath atomically:YES encoding:NSUTF8StringEncoding error:nil];
209+
return NO;
210+
}
211+
212+
NSLog(@"[Localization] Validated %@ localization with %lu strings", languageCode, (unsigned long)stringsDictionary.count);
213+
return YES;
214+
}
215+
216+
void validateAllLocalizations(void) {
217+
NSArray* languages = [NSBundle.mainBundle localizations];
218+
NSMutableArray* validLanguages = [NSMutableArray array];
219+
220+
for (NSString* language in languages) {
221+
if ([validateLocalizationFile(language) boolValue]) {
222+
[validLanguages addObject:language];
223+
}
224+
}
225+
226+
NSLog(@"[Localization] Valid localizations: %@", validLanguages);
227+
}

0 commit comments

Comments
 (0)