Add command to check translations between widgets <-> strings file

This commit is contained in:
David Capello 2016-12-22 14:19:47 -03:00
parent 01a6fe6dbc
commit 9377471425
5 changed files with 196 additions and 1 deletions

View File

@ -53,6 +53,16 @@ add_custom_command(
DEPENDS gen)
list(APPEND generated_files ${output_fn})
# Check translations
file(GLOB string_files ${CMAKE_SOURCE_DIR}/data/strings/*.ini)
set(output_fn ${CMAKE_CURRENT_BINARY_DIR}/check-translations.txt)
add_custom_command(
OUTPUT ${output_fn}
COMMAND ${CMAKE_BINARY_DIR}/bin/gen --widgets-dir "${CMAKE_SOURCE_DIR}/data/widgets/" --strings-dir "${CMAKE_SOURCE_DIR}/data/strings/" >${output_fn}
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
DEPENDS gen ${widget_files} ${string_files})
list(APPEND generated_files ${output_fn})
# Directory where generated files by "gen" utility will stay.
include_directories(${CMAKE_CURRENT_BINARY_DIR})

View File

@ -2,6 +2,7 @@
# Copyright (C) 2014-2016 David Capello
add_executable(gen
check_strings.cpp
gen.cpp
pref_types.cpp
skin_class.cpp
@ -15,4 +16,5 @@ endif()
target_link_libraries(gen
laf-base
cfg-lib
${TINYXML_LIBRARY})

158
src/gen/check_strings.cpp Normal file
View File

@ -0,0 +1,158 @@
// Aseprite Code Generator
// Copyright (c) 2016 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#include "gen/check_strings.h"
#include "base/exception.h"
#include "base/file_handle.h"
#include "base/fs.h"
#include "base/split_string.h"
#include "base/string.h"
#include "base/unique_ptr.h"
#include "cfg/cfg.h"
#include "gen/common.h"
#include "tinyxml.h"
#include <iostream>
#include <vector>
#include <cctype>
typedef std::vector<TiXmlElement*> XmlElements;
static std::string find_first_id(TiXmlElement* elem)
{
TiXmlElement* child = elem->FirstChildElement();
while (child) {
const char* id = child->Attribute("id");
if (id)
return id;
std::string idStr = find_first_id(child);
if (!idStr.empty())
return idStr;
child = child->NextSiblingElement();
}
return "";
}
static void collect_widgets_with_strings(TiXmlElement* elem, XmlElements& widgets)
{
TiXmlElement* child = elem->FirstChildElement();
while (child) {
const char* text = child->Attribute("text");
const char* tooltip = child->Attribute("tooltip");
if (text || tooltip)
widgets.push_back(child);
collect_widgets_with_strings(child, widgets);
child = child->NextSiblingElement();
}
}
static bool has_alpha_char(const char* p)
{
while (*p) {
if (std::isalpha(*p))
return true;
else
++p;
}
return false;
}
class CheckStrings {
public:
void loadStrings(const std::string& dir) {
for (const auto& fn : base::list_files(dir)) {
cfg::CfgFile* f = new cfg::CfgFile;
f->load(base::join_path(dir, fn));
m_stringFiles.push_back(f);
}
}
void checkStringsOnWidgets(const std::string& dir) {
for (const auto& fn : base::list_files(dir)) {
std::string fullFn = base::join_path(dir, fn);
base::FileHandle inputFile(base::open_file(fullFn, "rb"));
base::UniquePtr<TiXmlDocument> doc(new TiXmlDocument());
doc->SetValue(fullFn.c_str());
if (!doc->LoadFile(inputFile.get())) {
std::cerr << doc->Value() << ":"
<< doc->ErrorRow() << ":"
<< doc->ErrorCol() << ": "
<< "error " << doc->ErrorId() << ": "
<< doc->ErrorDesc() << "\n";
throw std::runtime_error("invalid input file");
}
TiXmlHandle handle(doc);
XmlElements widgets;
m_prefixId = find_first_id(doc->RootElement());
collect_widgets_with_strings(doc->RootElement(), widgets);
for (TiXmlElement* elem : widgets) {
checkString(elem, elem->Attribute("text"));
checkString(elem, elem->Attribute("tooltip"));
}
}
}
void checkString(TiXmlElement* elem, const char* text) {
if (!text)
return; // Do nothing
else if (text[0] == '@') {
for (auto cfg : m_stringFiles) {
std::string lang = base::get_file_title(cfg->filename());
std::string section, var;
if (text[1] == '.') {
section = m_prefixId.c_str();
var = text+2;
}
else {
std::vector<std::string> parts;
base::split_string(text, parts, ".");
if (parts.size() >= 1) section = parts[0].c_str()+1;
if (parts.size() >= 2) var = parts[1];
}
const char* translated =
cfg->getValue(section.c_str(), var.c_str(), nullptr);
if (!translated || translated[0] == 0) {
std::cerr << elem->GetDocument()->Value() << ":"
<< elem->Row() << ":"
<< elem->Column() << ": "
<< "warning: <" << lang
<< "> translation for a string ID wasn't found '"
<< text << "' (" << section << "." << var << ")\n";
}
}
}
else if (has_alpha_char(text)) {
std::cerr << elem->GetDocument()->Value() << ":"
<< elem->Row() << ":"
<< elem->Column() << ": "
<< "warning: raw string found '"
<< text << "'\n";
}
}
private:
std::vector<cfg::CfgFile*> m_stringFiles;
std::string m_prefixId;
};
void check_strings(const std::string& widgetsDir,
const std::string& stringsDir)
{
CheckStrings cs;
cs.loadStrings(stringsDir);
cs.checkStringsOnWidgets(widgetsDir);
}

16
src/gen/check_strings.h Normal file
View File

@ -0,0 +1,16 @@
// Aseprite Code Generator
// Copyright (c) 2016 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifndef GEN_CHECK_STRINGS_H_INCLUDED
#define GEN_CHECK_STRINGS_H_INCLUDED
#pragma once
#include <string>
void check_strings(const std::string& widgetsDir,
const std::string& stringsDir);
#endif

View File

@ -8,9 +8,11 @@
#include "base/fs.h"
#include "base/program_options.h"
#include "base/string.h"
#include "gen/check_strings.h"
#include "gen/pref_types.h"
#include "gen/skin_class.h"
#include "gen/ui_class.h"
#include "gen/check_strings.h"
#include "tinyxml.h"
#include <iostream>
@ -25,10 +27,12 @@ static void run(int argc, const char* argv[])
PO::Option& prefH = po.add("pref-h");
PO::Option& prefCpp = po.add("pref-cpp");
PO::Option& skin = po.add("skin");
PO::Option& widgetsDir = po.add("widgets-dir").requiresValue("<dir>");
PO::Option& stringsDir = po.add("strings-dir").requiresValue("<dir>");
po.parse(argc, argv);
// Try to load the XML file
TiXmlDocument* doc = NULL;
TiXmlDocument* doc = nullptr;
std::string inputFilename = po.value_of(inputOpt);
if (!inputFilename.empty()) {
@ -56,6 +60,11 @@ static void run(int argc, const char* argv[])
else if (po.enabled(skin))
gen_skin_class(doc, inputFilename);
}
else if (po.enabled(widgetsDir) &&
po.enabled(stringsDir)) {
check_strings(po.value_of(widgetsDir),
po.value_of(stringsDir));
}
}
int main(int argc, const char* argv[])