summaryrefslogtreecommitdiffstats
path: root/ios
diff options
context:
space:
mode:
authorsdefresne <sdefresne@chromium.org>2016-03-18 12:22:20 -0700
committerCommit bot <commit-bot@chromium.org>2016-03-18 19:24:24 +0000
commit10e1fb261ba9e1d18241a3a6e86571ce2dfe4a6d (patch)
tree644463e6ad33f8d77eb7e69deaaea379c52d1c9b /ios
parent1b8cd416b2eb6eadc565b9b3a3be13de8a424c87 (diff)
downloadchromium_src-10e1fb261ba9e1d18241a3a6e86571ce2dfe4a6d.zip
chromium_src-10e1fb261ba9e1d18241a3a6e86571ce2dfe4a6d.tar.gz
chromium_src-10e1fb261ba9e1d18241a3a6e86571ce2dfe4a6d.tar.bz2
[iOS] Upstream tool to generate Localizable.strings and InfoPlist.strings.
This tool is used to generate .strings files from .pak files so that some strings may be used by the OS (e.g. to display notifications). BUG=None Review URL: https://codereview.chromium.org/1805333003 Cr-Commit-Position: refs/heads/master@{#382046}
Diffstat (limited to 'ios')
-rw-r--r--ios/chrome/tools/strings/DEPS4
-rw-r--r--ios/chrome/tools/strings/generate_localizable_strings.gyp96
-rw-r--r--ios/chrome/tools/strings/generate_localizable_strings.mm373
3 files changed, 473 insertions, 0 deletions
diff --git a/ios/chrome/tools/strings/DEPS b/ios/chrome/tools/strings/DEPS
new file mode 100644
index 0000000..177f4aa
--- /dev/null
+++ b/ios/chrome/tools/strings/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+ "+ui/base/resource/data_pack.h",
+ "+ui/base/resource/resource_handle.h",
+]
diff --git a/ios/chrome/tools/strings/generate_localizable_strings.gyp b/ios/chrome/tools/strings/generate_localizable_strings.gyp
new file mode 100644
index 0000000..c39caf5
--- /dev/null
+++ b/ios/chrome/tools/strings/generate_localizable_strings.gyp
@@ -0,0 +1,96 @@
+# 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.
+
+# Builds an OS X command line tool to generate Localizable.strings and
+# InfoPlist.strings from compiled locales.pak.
+
+{
+ 'conditions': [
+ ['OS!="ios" or "<(GENERATOR)"!="xcode" or "<(GENERATOR_FLAVOR)"=="ninja"', {
+ 'targets': [
+ {
+ 'target_name': 'generate_localizable_strings',
+ 'type': 'executable',
+ 'toolsets': ['host'],
+ 'dependencies': [
+ '../../../../base/base.gyp:base',
+ '../../../../ui/base/ui_base.gyp:ui_data_pack',
+ ],
+ 'sources': [
+ 'generate_localizable_strings.mm',
+ ],
+ 'libraries': [
+ '$(SDKROOT)/System/Library/Frameworks/Foundation.framework',
+ ],
+ },
+ ],
+ }, { # else, OS=="ios" and "<(GENERATOR)"=="xcode" and "<(GENERATOR_FLAVOR)"!="ninja"
+ # Generation is done via two actions: (1) compiling the executable with
+ # ninja, and (2) copying the executable into a location that is shared
+ # with other projects. These actions are separated into two targets in
+ # order to be able to specify that the second action should not run until
+ # the first action finishes (since the ordering of multiple actions in
+ # one target is defined only by inputs and outputs, and it's impossible
+ # to set correct inputs for the ninja build, so setting all the inputs
+ # and outputs isn't an option).
+ 'variables': {
+ # Hardcode the ninja_product_dir variable to avoid having to include
+ # mac_build.gypi, as otherwise the re-generation of the project will
+ # happen twice while running gyp. It's a bit fragile, but if it gets
+ # out of sync we'll know as soon as anyone does a clobber build
+ # because it won't find the input.
+ 'ninja_output_dir': 'ninja-localizable_string_tool',
+ 'ninja_product_dir':
+ '../../../xcodebuild/<(ninja_output_dir)/<(CONFIGURATION_NAME)',
+ # Gyp to rerun
+ 're_run_targets': [
+ 'ios/chrome/tools/strings/generate_localizable_strings.gyp',
+ ],
+ },
+ 'targets': [
+ {
+ 'target_name': 'compile_generate_localizable_strings',
+ 'type': 'none',
+ 'includes': ['../../../../build/ios/mac_build.gypi'],
+ 'actions': [
+ {
+ 'action_name': 'compile generate_localizable_strings',
+ 'inputs': [],
+ 'outputs': [],
+ 'action': [
+ '<@(ninja_cmd)',
+ 'generate_localizable_strings',
+ ],
+ 'message':
+ 'Generating the generate_localizable_strings executable',
+ },
+ ],
+ },
+ {
+ 'target_name': 'generate_localizable_strings',
+ 'type': 'none',
+ 'dependencies': [
+ 'compile_generate_localizable_strings',
+ ],
+ 'actions': [
+ {
+ 'action_name': 'copy generate_localizable_strings',
+ 'inputs': [
+ '<(ninja_product_dir)/generate_localizable_strings',
+ ],
+ 'outputs': [
+ '<(PRODUCT_DIR)/generate_localizable_strings',
+ ],
+ 'action': [
+ 'cp',
+ '<(ninja_product_dir)/generate_localizable_strings',
+ '<(PRODUCT_DIR)/generate_localizable_strings',
+ ],
+ },
+ ],
+ },
+ ],
+ }],
+ ],
+}
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;
+}