#include "SourcePos.h" #include "ValuesFile.h" #include "XLIFFFile.h" #include "Perforce.h" #include "merge_res_and_xliff.h" #include "localize.h" #include "file_utils.h" #include "res_check.h" #include "xmb.h" #include #include #include #include #include #include #include using namespace std; FILE* g_logFile = NULL; int test(); int read_settings(const string& filename, map* result, const string& rootDir) { XMLNode* root = NodeHandler::ParseFile(filename, XMLNode::PRETTY); if (root == NULL) { SourcePos(filename, -1).Error("Error reading file."); return 1; } // vector configNodes = root->GetElementsByName("", "configuration"); const size_t I = configNodes.size(); for (size_t i=0; iGetAttribute("", "id", ""); if (settings.id == "") { configNode->Position().Error(" needs an id attribute."); delete root; return 1; } settings.oldVersion = configNode->GetAttribute("", "old-cl", ""); settings.currentVersion = configNode->GetAttribute("", "new-cl", ""); if (settings.currentVersion == "") { configNode->Position().Error(" needs a new-cl attribute."); delete root; return 1; } // vector appNodes = configNode->GetElementsByName("", "app"); const size_t J = appNodes.size(); for (size_t j=0; jGetAttribute("", "dir", ""); if (dir == "") { appNode->Position().Error(" needs a dir attribute."); delete root; return 1; } settings.apps.push_back(dir); } // vector rejectNodes = configNode->GetElementsByName("", "reject"); const size_t K = rejectNodes.size(); for (size_t k=0; kGetAttribute("", "file", ""); if (reject.file == "") { rejectNode->Position().Error(" needs a file attribute."); delete root; return 1; } string f = reject.file; reject.file = rootDir; reject.file += '/'; reject.file += f; reject.name = rejectNode->GetAttribute("", "name", ""); if (reject.name == "") { rejectNode->Position().Error(" needs a name attribute."); delete root; return 1; } reject.comment = trim_string(rejectNode->CollapseTextContents()); settings.reject.push_back(reject); } (*result)[settings.id] = settings; } delete root; return 0; } static void ValuesFile_to_XLIFFFile(const ValuesFile* values, XLIFFFile* xliff, const string& englishFilename) { const set& strings = values->GetStrings(); for (set::const_iterator it=strings.begin(); it!=strings.end(); it++) { StringResource res = *it; res.file = englishFilename; xliff->AddStringResource(res); } } static bool contains_reject(const Settings& settings, const string& file, const TransUnit& tu) { const string name = tu.id; const vector& reject = settings.reject; const size_t I = reject.size(); for (size_t i=0; i field themselves. Or if the string hasn't changed since last time, we can just * not even tell them about it. As the project nears the end, it will be convenient to see * the xliff files reducing in size, so we pick the latter. Obviously, if the string has * changed, then we need to get it retranslated. */ bool keep_this_trans_unit(const string& file, const TransUnit& unit, void* cookie) { const Settings* settings = reinterpret_cast(cookie); if (contains_reject(*settings, file, unit)) { return true; } if (unit.source.id == "") { return false; } if (unit.altTarget.id == "" || unit.altSource.id == "") { return true; } return unit.source.value->ContentsToString(XLIFF_NAMESPACES) != unit.altSource.value->ContentsToString(XLIFF_NAMESPACES); } int validate_config(const string& settingsFile, const map& settings, const string& config) { if (settings.find(config) == settings.end()) { SourcePos(settingsFile, -1).Error("settings file does not contain setting: %s\n", config.c_str()); return 1; } return 0; } int validate_configs(const string& settingsFile, const map& settings, const vector& configs) { int err = 0; for (size_t i=0; i *resFiles, const string& config, const map& settings, const string& rootDir) { int err; vector > allResFiles; vector configs; configs.push_back(config); err = select_files(&allResFiles, configs, settings, rootDir); if (err == 0) { *resFiles = allResFiles[0]; } return err; } int select_files(vector > *allResFiles, const vector& configs, const map& settings, const string& rootDir) { int err; printf("Selecting files..."); fflush(stdout); for (size_t i=0; isecond; vector resFiles; err = Perforce::GetResourceFileNames(setting.currentVersion, rootDir, setting.apps, &resFiles, true); if (err != 0) { fprintf(stderr, "error with perforce. bailing\n"); return err; } allResFiles->push_back(resFiles); } return 0; } static int do_export(const string& settingsFile, const string& rootDir, const string& outDir, const string& targetLocale, const vector& configs) { bool success = true; int err; if (false) { printf("settingsFile=%s\n", settingsFile.c_str()); printf("rootDir=%s\n", rootDir.c_str()); printf("outDir=%s\n", outDir.c_str()); for (size_t i=0; i settings; err = read_settings(settingsFile, &settings, rootDir); if (err != 0) { return err; } err = validate_configs(settingsFile, settings, configs); if (err != 0) { return err; } vector > allResFiles; err = select_files(&allResFiles, configs, settings, rootDir); if (err != 0) { return err; } size_t totalFileCount = 0; for (size_t i=0; i stats; vector > xliffs; for (size_t i=0; i& resFiles = allResFiles[i]; const size_t J = resFiles.size(); for (size_t j=0; jToString().c_str()); } else { fprintf(stderr, "error reading file %s@%s\n", resFile.c_str(), setting.currentVersion.c_str()); success = false; } // old file print_file_status(++fileProgress, totalFileCount); ValuesFile* oldFile = get_values_file(resFile, english, OLD_VERSION, setting.oldVersion, false); if (oldFile != NULL) { ValuesFile_to_XLIFFFile(oldFile, xliff, resFile); //printf("oldFile=[%s]\n", oldFile->ToString().c_str()); } // translated version // (get the head of the tree for the most recent translation, but it's considered // the old one because the "current" one hasn't been made yet, and this goes into // the tag if necessary print_file_status(++fileProgress, totalFileCount); string transFilename = translated_file_name(resFile, targetLocale); ValuesFile* transFile = get_values_file(transFilename, translated, OLD_VERSION, setting.currentVersion, false); if (transFile != NULL) { ValuesFile_to_XLIFFFile(transFile, xliff, resFile); } delete currentFile; delete oldFile; delete transFile; } Stats beforeFilterStats = xliff->GetStats(config); // run through the XLIFFFile and strip out TransUnits that have identical // old and current source values and are not in the reject list, or just // old values and no source values xliff->Filter(keep_this_trans_unit, (void*)&setting); Stats afterFilterStats = xliff->GetStats(config); afterFilterStats.totalStrings = beforeFilterStats.totalStrings; // add the reject comments for (vector::const_iterator reject = setting.reject.begin(); reject != setting.reject.end(); reject++) { TransUnit* tu = xliff->EditTransUnit(reject->file, reject->name); tu->rejectComment = reject->comment; } // config-locale-current_cl.xliff stringstream filename; if (outDir != "") { filename << outDir << '/'; } filename << config << '-' << targetLocale << '-' << setting.currentVersion << ".xliff"; xliffs.push_back(pair(filename.str(), xliff)); stats.push_back(afterFilterStats); } // today is a good day to die if (!success || SourcePos::HasErrors()) { return 1; } // write the XLIFF files printf("\nWriting %zd file%s...\n", xliffs.size(), xliffs.size() == 1 ? "" : "s"); for (vector >::iterator it = xliffs.begin(); it != xliffs.end(); it++) { const string& filename = it->first; XLIFFFile* xliff = it->second; string text = xliff->ToString(); write_to_file(filename, text); } // the stats printf("\n" " to without total\n" " config files translate comments strings\n" "-----------------------------------------------------------------------\n"); Stats totals; totals.config = "total"; totals.files = 0; totals.toBeTranslated = 0; totals.noComments = 0; totals.totalStrings = 0; for (vector::iterator it=stats.begin(); it!=stats.end(); it++) { string cfg = it->config; if (cfg.length() > 20) { cfg.resize(20); } printf(" %-20s %-9zd %-9zd %-9zd %-19zd\n", cfg.c_str(), it->files, it->toBeTranslated, it->noComments, it->totalStrings); totals.files += it->files; totals.toBeTranslated += it->toBeTranslated; totals.noComments += it->noComments; totals.totalStrings += it->totalStrings; } if (stats.size() > 1) { printf("-----------------------------------------------------------------------\n" " %-20s %-9zd %-9zd %-9zd %-19zd\n", totals.config.c_str(), totals.files, totals.toBeTranslated, totals.noComments, totals.totalStrings); } printf("\n"); return 0; } struct PseudolocalizeSettings { XLIFFFile* xliff; bool expand; }; string pseudolocalize_string(const string& source, const PseudolocalizeSettings* settings) { return pseudolocalize_string(source); } static XMLNode* pseudolocalize_xml_node(const XMLNode* source, const PseudolocalizeSettings* settings) { if (source->Type() == XMLNode::TEXT) { return XMLNode::NewText(source->Position(), pseudolocalize_string(source->Text(), settings), source->Pretty()); } else { XMLNode* target; if (source->Namespace() == XLIFF_XMLNS && source->Name() == "g") { // XXX don't translate these target = XMLNode::NewElement(source->Position(), source->Namespace(), source->Name(), source->Attributes(), source->Pretty()); } else { target = XMLNode::NewElement(source->Position(), source->Namespace(), source->Name(), source->Attributes(), source->Pretty()); } const vector& children = source->Children(); const size_t I = children.size(); for (size_t i=0; iEditChildren().push_back(pseudolocalize_xml_node(children[i], settings)); } return target; } } void pseudolocalize_trans_unit(const string&file, TransUnit* unit, void* cookie) { const PseudolocalizeSettings* settings = (PseudolocalizeSettings*)cookie; const StringResource& source = unit->source; StringResource* target = &unit->target; *target = source; target->config = settings->xliff->TargetConfig(); delete target->value; target->value = pseudolocalize_xml_node(source.value, settings); } int pseudolocalize_xliff(XLIFFFile* xliff, bool expand) { PseudolocalizeSettings settings; settings.xliff = xliff; settings.expand = expand; xliff->Map(pseudolocalize_trans_unit, &settings); return 0; } static int do_pseudo(const string& infile, const string& outfile, bool expand) { int err; XLIFFFile* xliff = XLIFFFile::Parse(infile); if (xliff == NULL) { return 1; } pseudolocalize_xliff(xliff, expand); err = write_to_file(outfile, xliff->ToString()); delete xliff; return err; } void log_printf(const char *fmt, ...) { int ret; va_list ap; if (g_logFile != NULL) { va_start(ap, fmt); ret = vfprintf(g_logFile, fmt, ap); va_end(ap); fflush(g_logFile); } } void close_log_file() { if (g_logFile != NULL) { fclose(g_logFile); } } void open_log_file(const char* file) { g_logFile = fopen(file, "w"); printf("log file: %s -- %p\n", file, g_logFile); atexit(close_log_file); } static int usage() { fprintf(stderr, "usage: localize export OPTIONS CONFIGS...\n" " REQUIRED OPTIONS\n" " --settings SETTINGS The settings file to use. See CONFIGS below.\n" " --root TREE_ROOT The location in Perforce of the files. e.g. //device\n" " --target LOCALE The target locale. See LOCALES below.\n" "\n" " OPTIONAL OPTIONS\n" " --out DIR Directory to put the output files. Defaults to the\n" " current directory if not supplied. Files are\n" " named as follows:\n" " CONFIG-LOCALE-CURRENT_CL.xliff\n" "\n" "\n" "usage: localize import XLIFF_FILE...\n" "\n" "Import a translated XLIFF file back into the tree.\n" "\n" "\n" "usage: localize xlb XMB_FILE VALUES_FILES...\n" "\n" "Read resource files from the tree file and write the corresponding XLB file\n" "\n" "Supply all of the android resource files (values files) to export after that.\n" "\n" "\n" "\n" "CONFIGS\n" "\n" "LOCALES\n" "Locales are specified in the form en_US They will be processed correctly\n" "to locate the resouce files in the tree.\n" "\n" "\n" "usage: localize pseudo OPTIONS INFILE [OUTFILE]\n" " OPTIONAL OPTIONS\n" " --big Pad strings so they get longer.\n" "\n" "Read INFILE, an XLIFF file, and output a pseudotranslated version of that file. If\n" "OUTFILE is specified, the results are written there; otherwise, the results are\n" "written back to INFILE.\n" "\n" "\n" "usage: localize rescheck FILES...\n" "\n" "Reads the base strings and prints warnings about bad resources from the given files.\n" "\n"); return 1; } int main(int argc, const char** argv) { //open_log_file("log.txt"); //g_logFile = stdout; if (argc == 2 && 0 == strcmp(argv[1], "--test")) { return test(); } if (argc < 2) { return usage(); } int index = 1; if (0 == strcmp("export", argv[index])) { string settingsFile; string rootDir; string outDir; string baseLocale = "en"; string targetLocale; string language, region; vector configs; index++; while (index < argc) { if (0 == strcmp("--settings", argv[index])) { settingsFile = argv[index+1]; index += 2; } else if (0 == strcmp("--root", argv[index])) { rootDir = argv[index+1]; index += 2; } else if (0 == strcmp("--out", argv[index])) { outDir = argv[index+1]; index += 2; } else if (0 == strcmp("--target", argv[index])) { targetLocale = argv[index+1]; index += 2; } else if (argv[index][0] == '-') { fprintf(stderr, "unknown argument %s\n", argv[index]); return usage(); } else { break; } } for (; index xliffFilenames; index++; for (; index resFiles; index++; if (argc < index+1) { return usage(); } outfile = argv[index]; index++; for (; index files; index++; while (index < argc) { if (argv[index][0] == '-') { fprintf(stderr, "unknown argument %s\n", argv[index]); return usage(); } else { break; } } for (; index