blob: 89801229291a63e69574bc13cfe563aac360012f [file] [log] [blame]
// Copyright (c) 2012 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 the InfoPlist.strings files needed for
// macOS app bundles.
#include <stdint.h>
#include <stdio.h>
#include <unistd.h>
#include <memory>
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/i18n/icu_util.h"
#include "base/i18n/message_formatter.h"
#include "base/logging.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "chrome/grit/chromium_strings.h"
#include "ui/base/resource/data_pack.h"
namespace {
std::unique_ptr<ui::DataPack> LoadResourceDataPack(
const char* dir_path,
const char* branding_strings_name,
const std::string& locale_name) {
auto path = base::FilePath(base::StringPrintf(
"%s/%s_%s.pak", dir_path, branding_strings_name, locale_name.c_str()));
path = base::MakeAbsoluteFilePath(path);
auto resource_pack = std::make_unique<ui::DataPack>(ui::SCALE_FACTOR_100P);
if (!resource_pack->LoadFromPath(path))
resource_pack.reset();
return resource_pack;
}
std::string LoadStringFromDataPack(ui::DataPack* data_pack,
const std::string& data_pack_lang,
uint32_t resource_id,
const char* resource_id_str) {
base::StringPiece data;
CHECK(data_pack->GetStringPiece(resource_id, &data))
<< "failed to load string " << resource_id_str << " for lang "
<< data_pack_lang;
// Data pack encodes strings as either UTF8 or UTF16.
if (data_pack->GetTextEncodingType() == ui::DataPack::UTF8)
return (std::string)data;
if (data_pack->GetTextEncodingType() == ui::DataPack::UTF16) {
return base::UTF16ToUTF8(base::string16(
reinterpret_cast<const base::char16*>(data.data()), data.length() / 2));
}
LOG(FATAL) << "requested string " << resource_id_str
<< " from binary data pack";
return std::string(); // Unreachable.
}
// Escape quotes, newlines, etc so there are no errors when the strings file
// is parsed.
std::string EscapeForStringsFileValue(std::string str) {
// Since this is a build tool, we don't really worry about making this
// the most efficient code.
// Backslash first since we need to do it before we put in all the others
base::ReplaceChars(str, "\\", "\\\\", &str);
// Now the rest of them.
base::ReplaceChars(str, "\n", "\\n", &str);
base::ReplaceChars(str, "\r", "\\r", &str);
base::ReplaceChars(str, "\t", "\\t", &str);
base::ReplaceChars(str, "\"", "\\\"", &str);
return str;
}
// The valid types for the -t arg
const char kAppType_Main[] = "main"; // Main app
const char kAppType_Helper[] = "helper"; // Helper app
} // namespace
int main(int argc, char* const argv[]) {
const char* version_string = nullptr;
const char* grit_output_dir = nullptr;
const char* branding_strings_name = nullptr;
const char* output_dir = nullptr;
std::string app_type = kAppType_Main;
// Process the args
int ch;
while ((ch = getopt(argc, argv, "t:v:g:b:o:")) != -1) {
switch (ch) {
case 't':
app_type = optarg;
break;
case 'v':
version_string = optarg;
break;
case 'g':
grit_output_dir = optarg;
break;
case 'b':
branding_strings_name = optarg;
break;
case 'o':
output_dir = optarg;
break;
default:
LOG(FATAL) << "bad command line arg";
break;
}
}
argc -= optind;
argv += optind;
// Check our args
CHECK(version_string) << "Missing version string";
CHECK(grit_output_dir) << "Missing grit output dir path";
CHECK(output_dir) << "Missing path to write InfoPlist.strings files";
CHECK(branding_strings_name) << "Missing branding strings file name";
CHECK(argc) << "Missing language list";
CHECK(app_type == kAppType_Main || app_type == kAppType_Helper)
<< "Unknown app type";
char* const* lang_list = argv;
int lang_list_count = argc;
base::i18n::InitializeICU();
for (int loop = 0; loop < lang_list_count; ++loop) {
std::string cur_lang = lang_list[loop];
// Open the branded string pak file
std::unique_ptr<ui::DataPack> branded_data_pack(
LoadResourceDataPack(grit_output_dir, branding_strings_name, cur_lang));
CHECK(branded_data_pack)
<< "failed to load branded pak for language: " << cur_lang;
uint32_t name_id = IDS_PRODUCT_NAME;
const char* name_id_str = "IDS_PRODUCT_NAME";
if (app_type == kAppType_Helper) {
name_id = IDS_HELPER_NAME;
name_id_str = "IDS_HELPER_NAME";
}
// Fetch the strings.
std::string name = LoadStringFromDataPack(branded_data_pack.get(), cur_lang,
name_id, name_id_str);
std::string copyright_format = LoadStringFromDataPack(
branded_data_pack.get(), cur_lang, IDS_ABOUT_VERSION_COPYRIGHT,
"IDS_ABOUT_VERSION_COPYRIGHT");
std::string copyright =
base::UTF16ToUTF8(base::i18n::MessageFormatter::FormatWithNumberedArgs(
base::UTF8ToUTF16(copyright_format), base::Time::Now()));
std::string permission_reason =
LoadStringFromDataPack(branded_data_pack.get(), cur_lang,
IDS_RUNTIME_PERMISSION_OS_REASON_TEXT,
"IDS_RUNTIME_PERMISSION_OS_REASON_TEXT");
// For now, assume this is ok for all languages. If we need to, this could
// be moved into generated_resources.grd and fetched.
std::string get_info = base::StringPrintf(
"%s %s, %s", name.c_str(), version_string, copyright.c_str());
// Generate the InfoPlist.strings file contents.
std::map<std::string, std::string> infoplist_strings = {
{"CFBundleGetInfoString", get_info},
{"NSHumanReadableCopyright", copyright},
{"NSBluetoothAlwaysUsageDescription", permission_reason},
{"NSBluetoothPeripheralUsageDescription", permission_reason},
{"NSCameraUsageDescription", permission_reason},
{"NSLocationUsageDescription", permission_reason},
{"NSMicrophoneUsageDescription", permission_reason},
};
std::string strings_file_contents_string;
for (const auto& kv : infoplist_strings) {
strings_file_contents_string +=
base::StringPrintf("%s = \"%s\";\n", kv.first.c_str(),
EscapeForStringsFileValue(kv.second).c_str());
}
// For Cocoa 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).
if (cur_lang == "en-US")
cur_lang = "en";
base::ReplaceChars(cur_lang, "-", "_", &cur_lang);
// Make sure the lproj we write to exists
std::string output_path =
base::StringPrintf("%s/%s.lproj", output_dir, cur_lang.c_str());
CHECK(base::CreateDirectory(base::FilePath(output_path)))
<< "failed to create '" << output_path << "'";
// Write out the file
// We set up Xcode projects expecting strings files to be UTF8, so make
// sure we write the data in that form. When Xcode copies them it will
// put the final runtime encoding.
output_path += "/InfoPlist.strings";
CHECK(base::WriteFile(base::FilePath(output_path),
strings_file_contents_string))
<< "failed to write out '" << output_path << "'";
}
}