diff options
Diffstat (limited to 'ios/chrome/tools/strings/generate_localizable_strings.mm')
-rw-r--r-- | ios/chrome/tools/strings/generate_localizable_strings.mm | 373 |
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; +} |