summaryrefslogtreecommitdiffstats
path: root/ios/chrome/tools/strings/generate_localizable_strings.mm
diff options
context:
space:
mode:
Diffstat (limited to 'ios/chrome/tools/strings/generate_localizable_strings.mm')
-rw-r--r--ios/chrome/tools/strings/generate_localizable_strings.mm373
1 files changed, 373 insertions, 0 deletions
diff --git a/ios/chrome/tools/strings/generate_localizable_strings.mm b/ios/chrome/tools/strings/generate_localizable_strings.mm
new file mode 100644
index 0000000..0352f9e
--- /dev/null
+++ b/ios/chrome/tools/strings/generate_localizable_strings.mm
@@ -0,0 +1,373 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Helper tool that is built and run during a build to pull strings from the
+// GRD files and generate a localized string files needed for iOS app bundles.
+// Arguments:
+// -p dir_to_data_pak
+// -o output_dir
+// -c config_file
+// -I header_root_dir
+// and a list of locales.
+//
+// Example:
+// generate_localizable_strings \
+// -p "${SHARED_INTERMEDIATE_DIR}/repack_ios/repack" \
+// -o "${INTERMEDIATE_DIR}/app_infoplist_strings" \
+// -c "<(DEPTH)/ios/chrome/localizable_strings_config.plist" \
+// -I "${SHARED_INTERMEDIATE_DIR}" \
+// ar ca cs da de el en-GB en-US es fi fr he hr hu id it ja ko ms nb nl pl \
+// pt pt-PT ro ru sk sv th tr uk vi zh-CN zh-TW
+
+#import <Foundation/Foundation.h>
+
+#include <stdio.h>
+#include <map>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/mac/scoped_nsautorelease_pool.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/sys_string_conversions.h"
+#include "ui/base/resource/data_pack.h"
+#include "ui/base/resource/resource_handle.h"
+
+namespace {
+
+// Load the packed resource data pack for |locale| from |packed_data_pack_dir|.
+// If loading fails, null is returned.
+scoped_ptr<ui::DataPack> LoadResourceDataPack(NSString* packed_data_pack_dir,
+ NSString* locale_name) {
+ scoped_ptr<ui::DataPack> resource_data_pack;
+ NSString* resource_path =
+ [NSString stringWithFormat:@"%@/%@.lproj/locale.pak",
+ packed_data_pack_dir, locale_name];
+
+ if (!resource_path)
+ return resource_data_pack;
+
+ // FilePath may contain components that references parent directory
+ // (".."). DataPack disallows paths with ".." for security reasons.
+ base::FilePath resources_pak_path([resource_path fileSystemRepresentation]);
+ resources_pak_path = base::MakeAbsoluteFilePath(resources_pak_path);
+ if (!base::PathExists(resources_pak_path))
+ return resource_data_pack;
+
+ resource_data_pack.reset(new ui::DataPack(ui::SCALE_FACTOR_100P));
+ if (!resource_data_pack->LoadFromPath(resources_pak_path))
+ resource_data_pack.reset();
+
+ return resource_data_pack;
+}
+
+// Create a |NSString| from the string with |resource_id| from |data_pack|.
+// Return nil if none is found.
+NSString* GetStringFromDataPack(const ui::DataPack& data_pack,
+ uint16_t resource_id) {
+ base::StringPiece data;
+ if (!data_pack.GetStringPiece(resource_id, &data))
+ return nil;
+
+ // Data pack encodes strings as either UTF8 or UTF16.
+ if (data_pack.GetTextEncodingType() == ui::DataPack::UTF8) {
+ return [[[NSString alloc] initWithBytes:data.data()
+ length:data.length()
+ encoding:NSUTF8StringEncoding] autorelease];
+ } else if (data_pack.GetTextEncodingType() == ui::DataPack::UTF16) {
+ return [[[NSString alloc] initWithBytes:data.data()
+ length:data.length()
+ encoding:NSUTF16LittleEndianStringEncoding]
+ autorelease];
+ }
+ return nil;
+}
+
+NSString* EscapeStringForLocalizableStrings(NSString* string) {
+ NSString* slashEscapedString =
+ [string stringByReplacingOccurrencesOfString:@"\\" withString:@"\\\\"];
+ return [slashEscapedString stringByReplacingOccurrencesOfString:@"\""
+ withString:@"\\\""];
+}
+
+// Generate the content of the Localizable.string file for |locale| from the
+// resource data pack |data_pack|. The content should have the format of:
+// "IDS_PRINT_TO_PHONE" = "Print to phone jobs are available.";
+// "IDS_SNAPSHOTS" = "Snapshots are available.";
+NSString* GenerateLocalizableStringsFileContent(const ui::DataPack& data_pack,
+ const char* locale,
+ NSArray* resources,
+ NSDictionary* resources_ids) {
+ NSMutableString* localizable_strings = [NSMutableString string];
+ for (id resource : resources) {
+ NSString* resource_name = nil;
+ NSString* resource_output_name = nil;
+ if ([resource isKindOfClass:[NSString class]]) {
+ resource_name = resource;
+ resource_output_name = resource;
+ } else if ([resource isKindOfClass:[NSDictionary class]]) {
+ resource_name = [resource objectForKey:@"input"];
+ resource_output_name = [resource objectForKey:@"output"];
+ if (!resource_name || !resource_output_name) {
+ fprintf(
+ stderr,
+ "ERROR: resources must be given in <string> or <dict> format.\n");
+ return nil;
+ }
+ } else {
+ fprintf(stderr,
+ "ERROR: resources must be given in <string> or <dict> format.\n");
+ return nil;
+ }
+ NSInteger resource_id =
+ [[resources_ids objectForKey:resource_name] integerValue];
+ NSString* string = GetStringFromDataPack(data_pack, resource_id);
+ if (string) {
+ const char* output_string_name = [resource_output_name UTF8String];
+ [localizable_strings
+ appendFormat:@" \"%s\" = \"%@\";\n", output_string_name,
+ EscapeStringForLocalizableStrings(string)];
+ } else {
+ fprintf(stderr, "ERROR: fail to load string '%s' for locale '%s'\n",
+ [resource_name UTF8String], locale);
+ return nil;
+ }
+ }
+
+ return localizable_strings;
+}
+
+NSDictionary* LoadResourcesListFromHeaders(NSArray* header_list,
+ NSString* root_header_dir) {
+ if (![header_list count]) {
+ fprintf(stderr, "ERROR: No header file in the config.\n");
+ return nil;
+ }
+ NSMutableDictionary* resources_ids =
+ [[[NSMutableDictionary alloc] init] autorelease];
+ for (NSString* header : header_list) {
+ NSString* header_file =
+ [root_header_dir stringByAppendingPathComponent:header];
+ if (![[NSFileManager defaultManager] isReadableFileAtPath:header_file]) {
+ fprintf(stderr, "ERROR: header file %s not readable.\n",
+ [header_file UTF8String]);
+ return nil;
+ }
+ NSString* header_content =
+ [NSString stringWithContentsOfFile:header_file
+ encoding:NSASCIIStringEncoding
+ error:nil];
+ if (!header_content) {
+ fprintf(stderr, "ERROR: header file %s contains non-ASCII chars.\n",
+ [header_file UTF8String]);
+ return nil;
+ }
+ NSCharacterSet* separator = [NSCharacterSet newlineCharacterSet];
+ NSArray* defines =
+ [header_content componentsSeparatedByCharactersInSet:separator];
+ for (NSString* define : defines) {
+ if (![define hasPrefix:@"#define "]) {
+ continue;
+ }
+ NSArray* define_string_id = [define componentsSeparatedByString:@" "];
+ if ([define_string_id count] != 3) {
+ fprintf(stderr, "ERROR: header %s contains invalid entry: %s.\n",
+ [header_file UTF8String], [define UTF8String]);
+ return nil;
+ }
+ NSString* string_name = [define_string_id objectAtIndex:1];
+ NSInteger string_id = [[define_string_id objectAtIndex:2] integerValue];
+ if (!string_id) {
+ fprintf(stderr, "ERROR: header %s contains invalid entry: %s.\n",
+ [header_file UTF8String], [define UTF8String]);
+ return nil;
+ }
+ if ([resources_ids valueForKey:string_name]) {
+ fprintf(stderr, "ERROR: duplicate entry for key %s.\n",
+ [string_name UTF8String]);
+ return nil;
+ }
+ [resources_ids setValue:[NSNumber numberWithInteger:string_id]
+ forKey:string_name];
+ }
+ }
+ return resources_ids;
+}
+
+// Save |localizable_strings| with |locale| to
+// |output_dir|/|locale|.lproj/|output_filename|.
+bool SaveLocalizableFile(NSString* localizable_strings,
+ NSString* locale,
+ NSString* output_dir,
+ NSString* output_filename) {
+ // Compute the path to the output directory with locale.
+ NSString* output_path = [output_dir
+ stringByAppendingPathComponent:[NSString
+ stringWithFormat:@"%@.lproj", locale]];
+
+ // Prepare the directory.
+ NSFileManager* file_manager = [NSFileManager defaultManager];
+ if (![file_manager fileExistsAtPath:output_path] &&
+ ![file_manager createDirectoryAtPath:output_path
+ withIntermediateDirectories:YES
+ attributes:nil
+ error:nil]) {
+ fprintf(stderr, "ERROR: '%s' didn't exist or failed to create it\n",
+ [output_path UTF8String]);
+ return false;
+ }
+
+ // Save the strings to the disk.
+ output_path = [output_path stringByAppendingPathComponent:output_filename];
+ if (![localizable_strings writeToFile:output_path
+ atomically:YES
+ encoding:NSUTF16StringEncoding
+ error:nil]) {
+ fprintf(stderr, "ERROR: Failed to write out '%s'\n",
+ [output_filename UTF8String]);
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace
+
+int main(int argc, char* const argv[]) {
+ base::mac::ScopedNSAutoreleasePool autorelease_pool;
+
+ NSString* output_dir = nil;
+ NSString* data_pack_dir = nil;
+ NSString* root_header_dir = nil;
+ NSString* config_file = nil;
+
+ int ch;
+ while ((ch = getopt(argc, argv, "c:I:p:o:h")) != -1) {
+ switch (ch) {
+ case 'c':
+ config_file = base::SysUTF8ToNSString(optarg);
+ break;
+ case 'I':
+ root_header_dir = base::SysUTF8ToNSString(optarg);
+ break;
+ case 'p':
+ data_pack_dir = base::SysUTF8ToNSString(optarg);
+ break;
+ case 'o':
+ output_dir = base::SysUTF8ToNSString(optarg);
+ break;
+ case 'h':
+ fprintf(stdout,
+ "usage: generate_localizable_strings -p data_pack_dir "
+ "-o output_dir -c config_file -I input_root "
+ "locale [locale...]\n"
+ "\n"
+ "Generate localized string files specified in |config_file|\n"
+ "for all specified locales in output_dir from packed data\n"
+ "packs in data_pack_dir.\n");
+ exit(0);
+ break;
+ default:
+ fprintf(stderr, "ERROR: bad command line arg: %c.n\n", ch);
+ exit(1);
+ break;
+ }
+ }
+
+ if (!config_file) {
+ fprintf(stderr, "ERROR: missing config file.\n");
+ exit(1);
+ }
+
+ if (!root_header_dir) {
+ fprintf(stderr, "ERROR: missing root header dir.\n");
+ exit(1);
+ }
+
+ if (!output_dir) {
+ fprintf(stderr, "ERROR: missing output directory.\n");
+ exit(1);
+ }
+
+ if (!data_pack_dir) {
+ fprintf(stderr, "ERROR: missing data pack directory.\n");
+ exit(1);
+ }
+
+ if (optind == argc) {
+ fprintf(stderr, "ERROR: missing locale list.\n");
+ exit(1);
+ }
+
+ NSDictionary* config =
+ [NSDictionary dictionaryWithContentsOfFile:config_file];
+
+ NSDictionary* resources_ids = LoadResourcesListFromHeaders(
+ [config objectForKey:@"headers"], root_header_dir);
+
+ if (!resources_ids) {
+ exit(1);
+ }
+
+ NSMutableArray* locales = [NSMutableArray arrayWithCapacity:(argc - optind)];
+ for (int i = optind; i < argc; ++i) {
+ // In order to find the locale at runtime, it needs to use '_' instead of
+ // '-' (http://crbug.com/20441). Also, 'en-US' should be represented
+ // simply as 'en' (http://crbug.com/19165, http://crbug.com/25578).
+ NSString* locale = [NSString stringWithUTF8String:argv[i]];
+ if ([locale isEqualToString:@"en-US"]) {
+ locale = @"en";
+ } else {
+ locale =
+ [locale stringByReplacingOccurrencesOfString:@"-" withString:@"_"];
+ }
+ [locales addObject:locale];
+ }
+
+ NSArray* outputs = [config objectForKey:@"outputs"];
+
+ if (![outputs count]) {
+ fprintf(stderr, "ERROR: No output in config file\n");
+ exit(1);
+ }
+
+ for (NSString* locale in locales) {
+ scoped_ptr<ui::DataPack> data_pack =
+ LoadResourceDataPack(data_pack_dir, locale);
+ if (!data_pack) {
+ fprintf(stderr, "ERROR: Failed to load branded pak for language: %s\n",
+ [locale UTF8String]);
+ exit(1);
+ }
+
+ for (NSDictionary* output : [config objectForKey:@"outputs"]) {
+ NSString* output_name = [output objectForKey:@"name"];
+ if (!output_name) {
+ fprintf(stderr, "ERROR: Output without name.\n");
+ exit(1);
+ }
+ NSArray* output_strings = [output objectForKey:@"strings"];
+ if (![output_strings count]) {
+ fprintf(stderr, "ERROR: Output without strings: %s.\n",
+ [output_name UTF8String]);
+ exit(1);
+ }
+
+ NSString* localizable_strings = GenerateLocalizableStringsFileContent(
+ *data_pack, [locale UTF8String], output_strings, resources_ids);
+ if (localizable_strings) {
+ SaveLocalizableFile(localizable_strings, locale, output_dir,
+ output_name);
+ } else {
+ fprintf(stderr, "ERROR: Unable to create %s.\n",
+ [output_name UTF8String]);
+ exit(1);
+ }
+ }
+ }
+ return 0;
+}