mirror of
https://github.com/aseprite/aseprite.git
synced 2025-02-16 06:40:17 +00:00
Add command to check translations between widgets <-> strings file
This commit is contained in:
parent
01a6fe6dbc
commit
9377471425
@ -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})
|
||||
|
||||
|
@ -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
158
src/gen/check_strings.cpp
Normal 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
16
src/gen/check_strings.h
Normal 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
|
@ -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[])
|
||||
|
Loading…
x
Reference in New Issue
Block a user