diff --git a/.bazelrc b/.bazelrc new file mode 100644 index 0000000..c4c173b --- /dev/null +++ b/.bazelrc @@ -0,0 +1,3 @@ +build --macos_minimum_os=13.0 +build --host_macos_minimum_os=13.0 +build --apple_platform_type=macos diff --git a/.gitignore b/.gitignore index 1dfad1d..97b500d 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ compress # Ignore executable that gets compiled + +bazel-* diff --git a/BUILD.bazel b/BUILD.bazel new file mode 100644 index 0000000..8f3e63b --- /dev/null +++ b/BUILD.bazel @@ -0,0 +1,49 @@ +objc_library( + name = "SSTSmallStrings", + srcs = [ + "Source/SSTSmallStrings.m", + ], + hdrs = [ + "Source/SSTSmallStrings.h", + ], + sdk_dylibs = [ + "compression", + ], + visibility = ["//visibility:public"], +) + +cc_binary( + name = "compress", + visibility = ["//visibility:public"], + deps = [ + ":compress_lib", + ], +) + +objc_library( + name = "compress_lib", + srcs = [ + "compress.m", + ], + sdk_dylibs = [ + "compression", + ], +) + +cc_binary( + name = "localize", + data = [ + ":compress", + ], + visibility = ["//visibility:public"], + deps = [ + ":localize_lib", + ], +) + +objc_library( + name = "localize_lib", + srcs = [ + "localize.m", + ], +) diff --git a/Example/Localization/BUILD b/Example/Localization/BUILD new file mode 100644 index 0000000..fdacd10 --- /dev/null +++ b/Example/Localization/BUILD @@ -0,0 +1,13 @@ +load("//:localize.bzl", "localize") + +localize( + name = "localize_test", + srcs = [ + "en.lproj/Localizable.strings", + "es.lproj/Localizable.strings", + ], + target_name = "example_Source_app_Sources", + visibility = [ + "//Example/Source:__pkg__", + ], +) diff --git a/Example/Localization/en.lproj/Localizable.strings b/Example/Localization/en.lproj/Localizable.strings new file mode 100644 index 0000000..4c6646c --- /dev/null +++ b/Example/Localization/en.lproj/Localizable.strings @@ -0,0 +1,3 @@ +"string1" = "en_value1"; +"string2" = "en_value2"; +"string3" = "en_value3"; diff --git a/Example/Localization/es.lproj/Localizable.strings b/Example/Localization/es.lproj/Localizable.strings new file mode 100644 index 0000000..2cd880f --- /dev/null +++ b/Example/Localization/es.lproj/Localizable.strings @@ -0,0 +1,3 @@ +"string1" = "es_value1"; +"string2" = "es_value2"; +"string3" = "es_value3"; diff --git a/Example/Source/App.h b/Example/Source/App.h new file mode 100644 index 0000000..ca25075 --- /dev/null +++ b/Example/Source/App.h @@ -0,0 +1,7 @@ +#include + +@interface App : NSObject + ++ (NSString *)fetchLocalizationValueForKey:(NSString *)key; + +@end diff --git a/Example/Source/App.m b/Example/Source/App.m new file mode 100644 index 0000000..f1a609b --- /dev/null +++ b/Example/Source/App.m @@ -0,0 +1,10 @@ +#import "Example/Source/App.h" +#import "Source/SSTSmallStrings.h" + +@implementation App + ++ (NSString *)fetchLocalizationValueForKey:(NSString *)key { + return SSTStringForKeyWithBundleAndSubdirectoryAndTargetName(key, [NSBundle bundleForClass:[self class]], nil, @"example_Source_app_Sources"); +} + +@end diff --git a/Example/Source/AppDelegate.h b/Example/Source/AppDelegate.h new file mode 100644 index 0000000..a5a8b38 --- /dev/null +++ b/Example/Source/AppDelegate.h @@ -0,0 +1,7 @@ +#import + +@interface AppDelegate : UIResponder + +@property (strong, nonatomic) UIWindow *window; + +@end diff --git a/Example/Source/AppDelegate.m b/Example/Source/AppDelegate.m new file mode 100644 index 0000000..e89416e --- /dev/null +++ b/Example/Source/AppDelegate.m @@ -0,0 +1,11 @@ +#import "Example/Source/AppDelegate.h" +#import "Source/SSTSmallStrings.h" + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application + didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + return YES; +} + +@end diff --git a/Example/Source/AppTest.m b/Example/Source/AppTest.m new file mode 100644 index 0000000..568a377 --- /dev/null +++ b/Example/Source/AppTest.m @@ -0,0 +1,14 @@ +#import "Example/Source/App.h" +#import + +@interface AppTest : XCTestCase +@end + +@implementation AppTest + +- (void)testLocalization { + XCTAssertTrue([@"en_value1" isEqual:[App fetchLocalizationValueForKey:@"string1"]]); + XCTAssertTrue([@"does_not_exist" isEqual:[App fetchLocalizationValueForKey:@"does_not_exist"]]); +} + +@end diff --git a/Example/Source/BUILD b/Example/Source/BUILD new file mode 100644 index 0000000..5de64d1 --- /dev/null +++ b/Example/Source/BUILD @@ -0,0 +1,54 @@ +load( + "@build_bazel_rules_apple//apple:ios.bzl", + "ios_application", + "ios_unit_test", +) + +objc_library( + name = "Sources", + srcs = [ + "App.m", + "AppDelegate.m", + "main.m", + ], + hdrs = [ + "App.h", + "AppDelegate.h", + ], + data = [ + "//Example/Localization:localize_test", + ], + deps = [ + "//:SSTSmallStrings", + ], +) + +objc_library( + name = "app_test_lib", + testonly = True, + srcs = [ + "AppTest.m", + ], + deps = [ + ":Sources", + ], +) + +ios_unit_test( + name = "app_test", + minimum_os_version = "16.0", + deps = [ + ":app_test_lib", + ], +) + +ios_application( + name = "LocalizationExampleApp", + bundle_id = "com.example.localization-example", + families = [ + "iphone", + ], + infoplists = [":Info.plist"], + minimum_os_version = "16.0", + deps = [":Sources"], +) diff --git a/Example/Source/Info.plist b/Example/Source/Info.plist new file mode 100644 index 0000000..057681c --- /dev/null +++ b/Example/Source/Info.plist @@ -0,0 +1,34 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleVersion + 10000000 + CFBundleShortVersionString + 1.0.0 + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + LSRequiresIPhoneOS + + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/Example/Source/Module/BUILD b/Example/Source/Module/BUILD new file mode 100644 index 0000000..2184d29 --- /dev/null +++ b/Example/Source/Module/BUILD @@ -0,0 +1,33 @@ +load( + "@build_bazel_rules_apple//apple:ios.bzl", + "ios_unit_test", +) + +objc_library( + name = "module", + srcs = ["Module.m"], + hdrs = ["Module.h"], + data = [ + "//Example/Source/Module/Localization:localizations", + ], + deps = [ + "//:SSTSmallStrings", + ], +) + +objc_library( + name = "module_test_lib", + testonly = True, + srcs = ["ModuleTest.m"], + deps = [ + ":module", + ], +) + +ios_unit_test( + name = "module_test", + minimum_os_version = "16.0", + deps = [ + ":module_test_lib", + ], +) diff --git a/Example/Source/Module/Localization/BUILD b/Example/Source/Module/Localization/BUILD new file mode 100644 index 0000000..d1ff8a9 --- /dev/null +++ b/Example/Source/Module/Localization/BUILD @@ -0,0 +1,25 @@ +load("//:localize.bzl", "localize") + +localize( + name = "localize_module_test", + srcs = [ + "en.lproj/Localizable.strings", + "es.lproj/Localizable.strings", + ], + target_name = "example_Source_Module_module", + visibility = [ + "//Example/Source/Module:__pkg__", + ], +) + +filegroup( + name = "localizations", + srcs = [ + "en.lproj/Localizable.stringsdict", + "es.lproj/Localizable.stringsdict", + ":localize_module_test", + ], + visibility = [ + "//Example/Source/Module:__pkg__", + ], +) diff --git a/Example/Source/Module/Localization/en.lproj/Localizable.strings b/Example/Source/Module/Localization/en.lproj/Localizable.strings new file mode 100644 index 0000000..a16b76b --- /dev/null +++ b/Example/Source/Module/Localization/en.lproj/Localizable.strings @@ -0,0 +1,2 @@ +"string1" = "en_module_value1"; +"string2" = "en_module_value2"; diff --git a/Example/Source/Module/Localization/en.lproj/Localizable.stringsdict b/Example/Source/Module/Localization/en.lproj/Localizable.stringsdict new file mode 100644 index 0000000..050873f --- /dev/null +++ b/Example/Source/Module/Localization/en.lproj/Localizable.stringsdict @@ -0,0 +1,22 @@ + + + + + string1_stringdict + + NSStringLocalizedFormatKey + string1_stringdict_en %#@formattedValue@ + formattedValue + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + zd + one + %1$zd thing + other + %1$zd things + + + + diff --git a/Example/Source/Module/Localization/es.lproj/Localizable.strings b/Example/Source/Module/Localization/es.lproj/Localizable.strings new file mode 100644 index 0000000..06ce34c --- /dev/null +++ b/Example/Source/Module/Localization/es.lproj/Localizable.strings @@ -0,0 +1,2 @@ +"string1" = "es_module_value1"; +"string2" = "es_module_value2"; diff --git a/Example/Source/Module/Localization/es.lproj/Localizable.stringsdict b/Example/Source/Module/Localization/es.lproj/Localizable.stringsdict new file mode 100644 index 0000000..d3fe83f --- /dev/null +++ b/Example/Source/Module/Localization/es.lproj/Localizable.stringsdict @@ -0,0 +1,22 @@ + + + + + string1_stringdict + + NSStringLocalizedFormatKey + string1_stringdict_es %#@formattedValue@ + formattedValue + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + zd + one + %1$zd thing + other + %1$zd things + + + + diff --git a/Example/Source/Module/Module.h b/Example/Source/Module/Module.h new file mode 100644 index 0000000..d7cc515 --- /dev/null +++ b/Example/Source/Module/Module.h @@ -0,0 +1,7 @@ +#include + +@interface Module : NSObject + ++ (NSString *)fetchLocalizationValueForKey:(NSString *)key; + +@end diff --git a/Example/Source/Module/Module.m b/Example/Source/Module/Module.m new file mode 100644 index 0000000..7c12124 --- /dev/null +++ b/Example/Source/Module/Module.m @@ -0,0 +1,10 @@ +#import "Example/Source/Module/Module.h" +#import "Source/SSTSmallStrings.h" + +@implementation Module + ++ (NSString *)fetchLocalizationValueForKey:(NSString *)key { + return SSTStringForKeyWithBundleAndSubdirectoryAndTargetName(key, [NSBundle bundleForClass:[self class]], nil, @"example_Source_Module_module"); +} + +@end diff --git a/Example/Source/Module/ModuleTest.m b/Example/Source/Module/ModuleTest.m new file mode 100644 index 0000000..0afc076 --- /dev/null +++ b/Example/Source/Module/ModuleTest.m @@ -0,0 +1,14 @@ +#import +#import "Example/Source/Module/Module.h" + +@interface ModuleTest : XCTestCase +@end + +@implementation ModuleTest + +- (void)testLocalization { + XCTAssertTrue([@"en_module_value1" isEqual:[Module fetchLocalizationValueForKey:@"string1"]]); + XCTAssertTrue([@"does_not_exist" isEqual:[Module fetchLocalizationValueForKey:@"does_not_exist"]]); +} + +@end diff --git a/Example/Source/main.m b/Example/Source/main.m new file mode 100644 index 0000000..b1c4f7a --- /dev/null +++ b/Example/Source/main.m @@ -0,0 +1,10 @@ +#import + +#import "Example/Source/AppDelegate.h" + +int main(int argc, char *argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, + NSStringFromClass([AppDelegate class])); + } +} diff --git a/Source/SSTSmallStrings.h b/Source/SSTSmallStrings.h index 3b8cad9..c4dd6b0 100644 --- a/Source/SSTSmallStrings.h +++ b/Source/SSTSmallStrings.h @@ -1,3 +1,6 @@ #import NSString *SSTStringForKey(NSString *key); +NSString *SSTStringForKeyWithBundle(NSString *key, NSBundle *bundle); +NSString *SSTStringForKeyWithBundleAndSubdirectory(NSString *key, NSBundle *bundle, NSString *subdirectory); +NSString *SSTStringForKeyWithBundleAndSubdirectoryAndTargetName(NSString *key, NSBundle *bundle, NSString *subdirectory, NSString *targetName); diff --git a/Source/SSTSmallStrings.m b/Source/SSTSmallStrings.m index 9b6abf3..a18e845 100644 --- a/Source/SSTSmallStrings.m +++ b/Source/SSTSmallStrings.m @@ -1,4 +1,5 @@ #import "SSTSmallStrings.h" +#include #import static NSDictionary *sKeyToString = nil; @@ -17,25 +18,29 @@ return [NSData dataWithBytesNoCopy:outBuffer length:actualSize freeWhenDone:YES]; } -id SSTJsonForName(NSString *name) +id SSTJsonForName(NSString *name, NSBundle *bundle, NSString *subdirectory) { - NSURL *compressedFile = [[NSBundle mainBundle] URLForResource:name withExtension:nil subdirectory:@"localization"]; + NSURL *compressedFile = [bundle URLForResource:name withExtension:nil subdirectory:subdirectory]; NSData *data = SSTDecompressedDataForFile(compressedFile); return [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]; } -NSDictionary *SSTCreateKeyToString() +NSDictionary *SSTCreateKeyToString(NSBundle *bundle, NSString *subdirectory, NSString *targetName) { // Note that the preferred list does seem to at least include the development region as a fallback if there aren't // any other languages - NSString *bestLocalization = [[[NSBundle mainBundle] preferredLocalizations] firstObject] ?: [[NSBundle mainBundle] developmentLocalization]; + NSString *bestLocalization = [[bundle preferredLocalizations] firstObject] ?: [bundle developmentLocalization]; if (!bestLocalization) { return @{}; } - NSString *valuesPath = [NSString stringWithFormat:@"%@.values.json.lzfse", bestLocalization]; - NSArray *values = SSTJsonForName(valuesPath); + NSString *targetNamePrefix = @""; + if (targetName) { + targetNamePrefix = [NSString stringWithFormat:@"%@.", targetName]; + } + NSString *valuesPath = [NSString stringWithFormat:@"%@%@.values.json.lzfse", targetNamePrefix, bestLocalization]; + NSArray *values = SSTJsonForName(valuesPath, bundle, subdirectory); - NSArray *keys = SSTJsonForName(@"keys.json.lzfse"); + NSArray *keys = SSTJsonForName([NSString stringWithFormat:@"%@keys.json.lzfse", targetNamePrefix], bundle, subdirectory); NSMutableDictionary *keyToString = [NSMutableDictionary dictionaryWithCapacity:keys.count]; NSInteger count = keys.count; @@ -50,13 +55,27 @@ id SSTJsonForName(NSString *name) return keyToString; // Avoid -copy to be a bit faster } -NSString *SSTStringForKey(NSString *key) -{ +NSString *SSTStringForKeyWithBundleAndSubdirectoryAndTargetName(NSString *key, NSBundle *bundle, NSString *subdirectory, NSString *targetName) { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - sKeyToString = SSTCreateKeyToString(); + sKeyToString = SSTCreateKeyToString(bundle, subdirectory, targetName); }); // Haven't tested with CFBundleAllowMixedLocalizations set to YES, although it seems like that'd be handled by the // NSLocalizedString fallback return sKeyToString[key] ?: NSLocalizedString(key, @""); } + +NSString *SSTStringForKeyWithBundleAndSubdirectory(NSString *key, NSBundle *bundle, NSString *subdirectory) +{ + return SSTStringForKeyWithBundleAndSubdirectoryAndTargetName(key, bundle, subdirectory, nil); +} + +NSString *SSTStringForKeyWithBundle(NSString *key, NSBundle *bundle) +{ + return SSTStringForKeyWithBundleAndSubdirectoryAndTargetName(key, bundle, @"localization", nil); +} + +NSString *SSTStringForKey(NSString *key) +{ + return SSTStringForKeyWithBundle(key, [NSBundle mainBundle]); +} diff --git a/WORKSPACE b/WORKSPACE new file mode 100644 index 0000000..019d81e --- /dev/null +++ b/WORKSPACE @@ -0,0 +1,37 @@ +workspace(name = "SmallStrings") + +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +http_archive( + name = "build_bazel_rules_apple", + sha256 = "34c41bfb59cdaea29ac2df5a2fa79e5add609c71bb303b2ebb10985f93fa20e7", + url = "https://github.com/bazelbuild/rules_apple/releases/download/3.1.1/rules_apple.3.1.1.tar.gz", +) + +load( + "@build_bazel_rules_apple//apple:repositories.bzl", + "apple_rules_dependencies", +) + +apple_rules_dependencies() + +load( + "@build_bazel_rules_swift//swift:repositories.bzl", + "swift_rules_dependencies", +) + +swift_rules_dependencies() + +load( + "@build_bazel_rules_swift//swift:extras.bzl", + "swift_rules_extra_dependencies", +) + +swift_rules_extra_dependencies() + +load( + "@build_bazel_apple_support//lib:repositories.bzl", + "apple_support_dependencies", +) + +apple_support_dependencies() diff --git a/localize.bzl b/localize.bzl new file mode 100644 index 0000000..f9fa97e --- /dev/null +++ b/localize.bzl @@ -0,0 +1,105 @@ +def _localize_impl(ctx): + localized_strings = {} + lzfse_output_files = {} + placeholder_files = [] + target_name_prefix = "" + if ctx.attr.target_name: + target_name_prefix = ctx.attr.target_name + "." + for src in ctx.files.srcs: + locale = src.dirname.split("/")[-1].split(".")[0] + localized_strings[locale] = _create_plutil_json_file(ctx, src, locale) + lzfse_output_files[locale] = ctx.actions.declare_file( + "{target_name_prefix}{locale}.values.json.lzfse".format(target_name_prefix = target_name_prefix, locale = locale), + ) + placeholder_files.append(_create_placeholder_file(ctx, src)) + localized_strings_json_file = ctx.actions.declare_file( + "localized_strings.json", + ) + ctx.actions.write( + localized_strings_json_file, + json.encode(_stringify_file_dict(localized_strings)), + ) + lzfse_output_files_json_file = ctx.actions.declare_file( + "lzfse_output_files.json", + ) + ctx.actions.write( + lzfse_output_files_json_file, + json.encode(_stringify_file_dict(lzfse_output_files)), + ) + keys_json_lzfse_file = ctx.actions.declare_file( + "{target_name_prefix}keys.json.lzfse".format(target_name_prefix = target_name_prefix), + ) + args = ctx.actions.args() + args.add_all([ + ctx.executable._compress_tool, + localized_strings_json_file, + keys_json_lzfse_file, + lzfse_output_files_json_file, + ]) + ctx.actions.run( + outputs = lzfse_output_files.values() + [keys_json_lzfse_file], + inputs = [localized_strings_json_file, lzfse_output_files_json_file] + localized_strings.values(), + tools = [ctx.executable._compress_tool], + executable = ctx.executable._localize_tool, + arguments = [args], + mnemonic = "SmallStringsLocalize", + ) + return DefaultInfo( + files = depset(lzfse_output_files.values() + [keys_json_lzfse_file] + placeholder_files), + ) + +def _create_placeholder_file(ctx, src): + output = ctx.actions.declare_file(src.dirname + "/" + ctx.attr.target_name + src.basename) + ctx.actions.write( + output, + "\"placeholder\" = \"_\";\n", + ) + return output + +def _create_plutil_json_file(ctx, src, locale): + output = ctx.actions.declare_file("{basename}.{locale}.json".format( + basename = src.basename, + locale = locale, + )) + args = ctx.actions.args() + args.add_all([ + "-convert", + "json", + "-o", + output, + src, + ]) + ctx.actions.run_shell( + outputs = [output], + inputs = [src], + command = "plutil $@", + arguments = [args], + mnemonic = "PlutilJson", + ) + return output + +def _stringify_file_dict(dict): + result = {} + for key, value in dict.items(): + result[key] = value.path + return result + +localize = rule( + implementation = _localize_impl, + attrs = { + "srcs": attr.label_list(allow_files = [".strings"]), + "target_name": attr.string( + doc = "A string to key the localization artifacts by, this is optional since non-static builds do not require for assets to have unique name in the final bundle", + ), + "_localize_tool": attr.label( + default = Label("@SmallStrings//:localize"), + executable = True, + cfg = "exec", + ), + "_compress_tool": attr.label( + default = Label("@SmallStrings//:compress"), + executable = True, + cfg = "exec", + ), + }, +) diff --git a/localize.m b/localize.m new file mode 100644 index 0000000..a8c633d --- /dev/null +++ b/localize.m @@ -0,0 +1,90 @@ +#include + +@interface LocalizationResult : NSObject + +@property (nonatomic) NSSet *keySet; +@property (nonatomic) NSDictionary *> *languageCodeToLocalizationsMap; + +@end + +@implementation LocalizationResult + +@end + +@interface LocalizationHelper: NSObject {} + ++ (LocalizationResult *)readLocalizableStringsJSONMapPath:(NSString *)readLocalizableStringsJSONMapPath; ++ (void)writeLanguageMaps:(LocalizationResult *)result sortedKeySetArray:(NSArray *)sortedKeySetArray valuesJsonLzfseOutputMap:(NSDictionary *)valuesJsonLzfseOutputMap compressToolPath:(NSString *)compressToolPath; ++ (void)writeCompressedData:(NSData *)data outputPath:(NSString *)outputPath compressToolPath:(NSString *)compressToolPath; + +@end + +@implementation LocalizationHelper + ++ (LocalizationResult *)readLocalizableStringsJSONMapPath:(NSString *)readLocalizableStringsJSONMapPath { + NSData *fileData = [NSData dataWithContentsOfFile:readLocalizableStringsJSONMapPath options:0 error:nil]; + NSDictionary *jsonDictionary = [NSJSONSerialization JSONObjectWithData:fileData options:0 error:nil]; + NSMutableDictionary *> *langJsonMap = [NSMutableDictionary dictionary]; + NSMutableSet *keySet = [NSMutableSet set]; + [jsonDictionary enumerateKeysAndObjectsUsingBlock:^(id key, id value, BOOL* stop) { + NSString *lang = key; + NSString *putilsLocalizableStringsFile = value; + NSDictionary *localizationValuesMap = [LocalizationHelper createLocalizationValuesMapping:putilsLocalizableStringsFile]; + [keySet addObjectsFromArray:localizationValuesMap.allKeys]; + langJsonMap[lang] = localizationValuesMap; + }]; + LocalizationResult *result = [[LocalizationResult alloc] init]; + result.keySet = keySet; + result.languageCodeToLocalizationsMap = langJsonMap; + return result; +} + ++ (NSDictionary *)createLocalizationValuesMapping:(NSString *)putilsLocalizableStringsFile { + return [NSJSONSerialization JSONObjectWithData:[NSData dataWithContentsOfFile:putilsLocalizableStringsFile options:0 error:nil] options:0 error:nil]; +} + ++ (void)writeLanguageMaps:(LocalizationResult *)result sortedKeySetArray:(NSArray *)sortedKeySetArray valuesJsonLzfseOutputMap:(NSDictionary *)valuesJsonLzfseOutputMap compressToolPath:(NSString *)compressToolPath { + [result.languageCodeToLocalizationsMap enumerateKeysAndObjectsUsingBlock:^(id key, id value, BOOL* stop) { + NSString *lang = key; + NSDictionary *localizationValuesMap = value; + NSMutableArray *sortedValues = [NSMutableArray array]; + for (NSString *key in sortedKeySetArray) { + NSString *value = localizationValuesMap[key]; + if (value) { + [sortedValues addObject:value]; + } + } + NSString *outputPath = valuesJsonLzfseOutputMap[lang]; + [LocalizationHelper writeCompressedData:[NSJSONSerialization dataWithJSONObject:sortedValues options:0 error:nil] outputPath:outputPath compressToolPath:compressToolPath]; + }]; +} + ++ (void)writeCompressedData:(NSData *)data outputPath:(NSString *)outputPath compressToolPath:(NSString *)compressToolPath { + NSURL *outputPathURL = [NSURL fileURLWithPath: outputPath]; + NSString *tempFilePath = [NSTemporaryDirectory() stringByAppendingPathComponent:outputPathURL.lastPathComponent]; + [data writeToURL:[NSURL fileURLWithPath: tempFilePath] atomically:YES]; + NSTask *task = [[NSTask alloc] init]; + task.launchPath = compressToolPath; + task.arguments = @[tempFilePath, outputPath]; + [task launch]; + [task waitUntilExit]; + [[NSFileManager defaultManager] removeItemAtPath:tempFilePath error:nil]; +} + +@end + +int main(int argc, char *argv[]) { + if (argc != 5) { + return 1; + } + NSString *compressToolPath = @(argv[1]); + NSString *localizableStringsJSONMapPath = @(argv[2]); + NSString *keysJsonLzfseOutputPath = @(argv[3]); + NSString *valuesJsonLzfseOutputMapPath = @(argv[4]); + LocalizationResult *result = [LocalizationHelper readLocalizableStringsJSONMapPath:localizableStringsJSONMapPath]; + NSArray *sortedLanguageKeys = [result.keySet.allObjects sortedArrayUsingSelector:@selector(compare:)]; + [LocalizationHelper writeCompressedData:[NSJSONSerialization dataWithJSONObject:sortedLanguageKeys options:0 error:nil] outputPath:keysJsonLzfseOutputPath compressToolPath:compressToolPath]; + NSData *valuesJsonLzfseOutputMapPathData = [NSData dataWithContentsOfFile:valuesJsonLzfseOutputMapPath options:0 error:nil]; + [LocalizationHelper writeLanguageMaps:result sortedKeySetArray:sortedLanguageKeys valuesJsonLzfseOutputMap:[NSJSONSerialization JSONObjectWithData:valuesJsonLzfseOutputMapPathData options:0 error:nil] compressToolPath:compressToolPath]; + return 0; +}