Added validator for locales based on all strings in the source code.

It checks:
 - whether all keys used by the program are in the locale
 - whether the right number of %s are used
 - if there are no extra keys in the locale that shouldn't be there
This will become very useful when translations need to be updated for new MSE versions.

There is a perl script for generating the 'expected_locale_keys' resource file.
This file contains a list of all the locale keys used.
This is a resource and not a data file because it is automatically generated from the code,
 the user has no business modifying it.

I also fixed all the locale errors I found in the process.

git-svn-id: svn://svn.code.sf.net/p/magicseteditor/code/trunk@613 0fc631ac-6414-0410-93d0-97cfa31319b6
This commit is contained in:
twanvl
2007-08-17 21:10:48 +00:00
parent 232c8b3aa9
commit 35bbf36e04
14 changed files with 789 additions and 24 deletions
+1 -1
View File
@@ -82,7 +82,7 @@ intrusive_ptr<Field> read_new<Field>(Reader& reader) {
reader.warning(_ERROR_1_("expected key", _("type")));
throw ParseError(_ERROR_("aborting parsing"));
} else {
reader.warning(_ERROR_1_("Unsupported field type", type));
reader.warning(_ERROR_1_("unsupported field type", type));
throw ParseError(_ERROR_("aborting parsing"));
}
}
+1 -1
View File
@@ -697,7 +697,7 @@ void ApprenticeExportWindow::onOk(wxCommandEvent& ev) {
}
} catch (const AbortException&) {
// aborted, cleanup is already handled by dtors
wxMessageBox(_LABEL_("apprentice export cancled"), _TITLE_("export canceled"), wxOK | wxICON_INFORMATION);
wxMessageBox(_LABEL_("apprentice export cancelled"), _TITLE_("export cancelled"), wxOK | wxICON_INFORMATION);
}
// Done, close progress window
progress_target->Hide();
+150
View File
@@ -13,6 +13,11 @@
#include <util/io/package_manager.hpp>
#include <script/to_value.hpp>
#include <wx/stdpaths.h>
#if defined(__WXMSW__)
#include <wx/mstream.h>
#endif
// ----------------------------------------------------------------------------- : Locale class
LocaleP the_locale;
@@ -109,3 +114,148 @@ String tr(const SymbolFont& f, const String& key, const String& def) {
if (!loc) return def; // no information on this symbol font
return loc->tr(key, def);
}
// ----------------------------------------------------------------------------- : Validation
DECLARE_POINTER_TYPE(SubLocaleValidator);
class KeyValidator {
public:
int args;
bool optional;
DECLARE_REFLECTION();
};
class SubLocaleValidator : public IntrusivePtrBase<SubLocaleValidator> {
public:
map<String,KeyValidator> keys; ///< Arg count for each key
DECLARE_REFLECTION();
};
/// Validation information for locales
class LocaleValidator {
public:
map<String, SubLocaleValidatorP> sublocales;
DECLARE_REFLECTION();
};
template <> void Reader::handle(KeyValidator& k) {
String v = getValue();
if (starts_with(v, _("optional, "))) {
k.optional = true;
v = v.substr(10);
} else {
k.optional = false;
}
long l = 0;
v.ToLong(&l);
k.args = l;
}
template <> void Writer::handle(const KeyValidator& v) {
assert(false);
}
IMPLEMENT_REFLECTION_NO_SCRIPT(SubLocaleValidator) {
REFLECT_NAMELESS(keys);
}
IMPLEMENT_REFLECTION_NO_SCRIPT(LocaleValidator) {
REFLECT_NAMELESS(sublocales);
}
/// Count "%s" in str
int string_format_args(const String& str) {
int count = 0;
bool in_percent = false;
FOR_EACH_CONST(c, str) {
if (in_percent) {
if (c == _('s')) {
count++;
}
in_percent = false;
} else if (c == _('%')) {
in_percent = true;
}
}
return count;
}
/// Load a text file from a resource
/** TODO: Move me
*/
InputStreamP load_resource_text(const String& name);
InputStreamP load_resource_text(const String& name) {
#if defined(__WXMSW__)
HRSRC hResource = ::FindResource(wxGetInstance(), name, _("TEXT"));
if ( hResource == 0 ) throw InternalError(String::Format(_("Resource not found: %s"), name));
HGLOBAL hData = ::LoadResource(wxGetInstance(), hResource);
if ( hData == 0 ) throw InternalError(String::Format(_("Resource not text: %s"), name));
char* data = (char *)::LockResource(hData);
if ( !data ) throw InternalError(String::Format(_("Resource cannot be locked: %s"), name));
int len = ::SizeofResource(wxGetInstance(), hResource);
return new_shared2<wxMemoryInputStream>(data, len);
#else
static String path = wxStandardPaths::Get().GetDataDir() + _("/resource/") + name;
return new_shared1<wxFileInputStream>(path);
#endif
}
DECLARE_TYPEOF(map<String COMMA String>);
DECLARE_TYPEOF(map<String COMMA KeyValidator>);
void Locale::validate(Version ver) {
// load locale validator
LocaleValidator v;
Reader r(load_resource_text(_("expected_locale_keys")), _("expected_locale_keys"));
r.handle_greedy(v);
// validate
String errors;
errors += translations[LOCALE_CAT_MENU ].validate(_("menu"), *v.sublocales[_("menu") ]);
errors += translations[LOCALE_CAT_HELP ].validate(_("help"), *v.sublocales[_("help") ]);
errors += translations[LOCALE_CAT_TOOL ].validate(_("tool"), *v.sublocales[_("tool") ]);
errors += translations[LOCALE_CAT_TOOLTIP].validate(_("tooltip"), *v.sublocales[_("tooltip")]);
errors += translations[LOCALE_CAT_LABEL ].validate(_("label"), *v.sublocales[_("label") ]);
errors += translations[LOCALE_CAT_BUTTON ].validate(_("button"), *v.sublocales[_("button") ]);
errors += translations[LOCALE_CAT_TITLE ].validate(_("title"), *v.sublocales[_("title") ]);
errors += translations[LOCALE_CAT_ACTION ].validate(_("action"), *v.sublocales[_("action") ]);
errors += translations[LOCALE_CAT_ERROR ].validate(_("error"), *v.sublocales[_("error") ]);
errors += translations[LOCALE_CAT_TYPE ].validate(_("type"), *v.sublocales[_("type") ]);
// errors?
if (!errors.empty()) {
if (ver != app_version) {
errors = _("Errors in locale file ") + short_name + _(":") + errors;
} else {
errors = _("Errors in locale file ") + short_name +
_("\nThis is probably because the locale was made for a different version of MSE.") + errors;
}
handle_warning(errors);
}
}
String SubLocale::validate(const String& name, const SubLocaleValidator& v) const {
String errors;
// 1. keys in v but not in this, check arg count
FOR_EACH_CONST(kc, v.keys) {
map<String,String>::const_iterator it = translations.find(kc.first);
if (it == translations.end()) {
if (!kc.second.optional) {
errors += _("\n Missing key:\t\t\t") + name + _(": ") + kc.first;
}
} else if (string_format_args(it->second) != kc.second.args) {
errors += _("\n Incorrect number of arguments for:\t") + name + _(": ") + kc.first
+ String::Format(_("\t expected: %d, found %d"), kc.second.args, string_format_args(it->second));
}
}
// 2. keys in this but not in v
FOR_EACH_CONST(kv, translations) {
map<String,KeyValidator>::const_iterator it = v.keys.find(kv.first);
if (it == v.keys.end() && !kv.second.empty()) {
// allow extra keys with empty values as a kind of documentation
// for example in the help stirngs:
// help:
// file:
// new set: blah blah
errors += _("\n Unexpected key:\t\t\t") + name + _(": ") + kv.first;
}
}
return errors;
}
+7
View File
@@ -16,6 +16,7 @@
DECLARE_POINTER_TYPE(Locale);
DECLARE_POINTER_TYPE(SubLocale);
class SubLocaleValidator;
// ----------------------------------------------------------------------------- : Locale class
@@ -29,6 +30,9 @@ class SubLocale : public IntrusivePtrBase<SubLocale> {
/// Translate a key with a default value
String tr(const String& key, const String& def);
/// Is this a valid sublocale? Returns errors
String validate(const String& name, const SubLocaleValidator&) const;
DECLARE_REFLECTION();
};
@@ -47,6 +51,9 @@ class Locale : public Packaged {
/// Open a locale with the given name
static LocaleP byName(const String& name);
/// Validate that the locale is valid for this MSE version
virtual void validate(Version = app_version);
protected:
String typeName() const;
DECLARE_REFLECTION();
+1 -1
View File
@@ -70,7 +70,7 @@ class KeywordList : public ItemList, public SetView {
/// Send an 'item selected' event for the currently selected item (selected_item)
virtual void sendEvent();
/// Compare cards
/// Compare keywords
virtual bool compareItems(void* a, void* b) const;
/// Get the text of an item in a specific column
+1 -1
View File
@@ -141,7 +141,7 @@ SetWindow::SetWindow(Window* parent, const SetP& set)
addPanel(menuWindow, tabBar, new StylePanel (this, wxID_ANY), 2, _("window_style"), _("style tab"));
addPanel(menuWindow, tabBar, new KeywordsPanel(this, wxID_ANY), 3, _("window_keywords"), _("keywords tab"));
addPanel(menuWindow, tabBar, new StatsPanel (this, wxID_ANY), 4, _("window_statistics"), _("stats tab"));
// addPanel(*s, *menuWindow, *tabBar, new DraftPanel (&this, wxID_ANY), 5, _("F10"))
// addPanel(menuWindow, tabBar, new DraftPanel (this, wxID_ANY), 5, _("window_draft"), _("draft tab"))
selectPanel(ID_WINDOW_CARDS); // select cards panel
// loose ends
+9 -9
View File
@@ -177,7 +177,7 @@ bool TextValueEditor::onChar(wxKeyEvent& ev) {
return true;
}
}
replaceSelection(wxEmptyString, _("Backspace"));
replaceSelection(wxEmptyString, _ACTION_("backspace"));
break;
case WXK_DELETE:
if (selection_start == selection_end) {
@@ -188,15 +188,15 @@ bool TextValueEditor::onChar(wxKeyEvent& ev) {
moveSelection(TYPE_CURSOR, nextCharBoundry(selection_end), true, MOVE_RIGHT);
}
}
replaceSelection(wxEmptyString, _("Delete"));
replaceSelection(wxEmptyString, _ACTION_("delete"));
break;
case WXK_RETURN:
if (field().multi_line) {
if (ev.ShiftDown()) {
// soft line break
replaceSelection(_("<soft-line>\n</soft-line>"), _("Soft line break"));
replaceSelection(_("<soft-line>\n</soft-line>"), _ACTION_("soft line break"));
} else {
replaceSelection(_("\n"), _("Enter"));
replaceSelection(_("\n"), _ACTION_("enter"));
}
}
break;
@@ -212,9 +212,9 @@ bool TextValueEditor::onChar(wxKeyEvent& ev) {
// this might not work for internationalized input.
// It might also not be portable!
#ifdef UNICODE
replaceSelection(escape(String(ev.GetUnicodeKey(), 1)), _("Typing"));
replaceSelection(escape(String(ev.GetUnicodeKey(), 1)), _ACTION_("typing"));
#else
replaceSelection(escape(String((Char)ev.GetKeyCode(), 1)), _("Typing"));
replaceSelection(escape(String((Char)ev.GetKeyCode(), 1)), _ACTION_("typing"));
#endif
} else {
return false;
@@ -256,7 +256,7 @@ bool TextValueEditor::onCommand(int id) {
if (!style().always_symbol) {
code = _("<sym>") + code + _("</sym>");
}
replaceSelection(code, _("Insert Symbol"));
replaceSelection(code, _ACTION_("insert symbol"));
return true;
}
}
@@ -350,7 +350,7 @@ bool TextValueEditor::doPaste() {
wxTheClipboard->Close();
if (!ok) return false;
// paste
replaceSelection(escape(data.GetText()), _("Paste"));
replaceSelection(escape(data.GetText()), _ACTION_("paste"));
return true;
}
@@ -370,7 +370,7 @@ bool TextValueEditor::doCopy() {
}
bool TextValueEditor::doDelete() {
replaceSelection(wxEmptyString, _("Cut"));
replaceSelection(wxEmptyString, _ACTION_("cut"));
return true;
}
+495
View File
@@ -0,0 +1,495 @@
# This file contains the keys expected to be in MSE locales
# It was automatically generated by tools/locale/locale.pl
# Generated on Fri Aug 17 22:58:51 2007
action:
add control point: 0
add part: 1
add symmetry: 0
backspace: 0
change: 1
change combine mode: 0
change shape name: 0
change symmetry copies: 0
change symmetry type: 0
convert to curve: 0
convert to line: 0
cut: 0
delete: 0
delete point: 0
delete points: 0
duplicate: 1
enter: 0
group parts: 0
insert symbol: 0
lock point: 0
move: 1
move curve: 0
move handle: 0
move symmetry center: 0
move symmetry handle: 0
paste: 0
remove parts: 1
reorder parts: 0
rotate: 1
scale: 1
shear: 1
soft line break: 0
typing: 0
ungroup parts: 0
button:
always: 0
browse: 0
check now: 0
close: 0
edit symbol: 0
hide: 0
high quality: 0
if internet connection exists: 0
insert parameter: 0
keep old: 0
last opened set: 0
move down: 0
move up: 0
never: 0
new set: 0
number: 0
number overwrite: 0
open set: 0
overwrite: 0
refer parameter: 0
select: optional, 0
select all: 0
select none: 0
show: 0
show lines: 0
symbol gallery: optional, 0
use custom styling options: 0
use for all cards: 0
zoom export: 0
error:
aborting parsing: 0
can't convert: 2
can't convert value: 3
checking updates: 0
checking updates failed: 0
coordinates for blending overlap: 0
dimension not found: 1
expected key: 1
file not found: 2
file parse error: 2
has no member: 2
has no member value: 2
images used for blending must have the same size: 0
internal error: 1
newer version: 2
no game specified for the set: 0
no packages: 0
no stylesheet specified for the set: 0
no updates: 0
package not found: 1
package out of date: 3
stylesheet and set refer to different game: 0
unable to open output file: 0
unable to store file: 0
unrecognized value: 1
unsupported field type: 1
unsupported fill type: 1
unsupported format: 1
help:
about: 0
add card: 0
add cards: 0
add keyword: 0
add symmetry: 0
app language: 0
basic shapes: 0
bold: 0
border: 0
card list columns: 0
cards tab: 0
check updates: 0
click to select shape: 0
close symbol editor: 0
copies: 0
copy: 0
copy card: 0
copy keyword: 0
curve segment: 0
cut: 0
cut card: 0
cut keyword: 0
difference: 0
draft tab: optional, 0
drag to draw shape: 0
drag to move curve: 0
drag to move line: 0
drag to move point: 0
drag to resize: 1
drag to rotate: 1
drag to shear: 1
draw ellipse: 0
draw polygon: 0
draw rectangle: 0
draw star: 0
duplicate: 0
ellipse: 0
exit: 0
export: 0
export apprentice: 0
export html: 0
export image: 0
export images: 0
export mws: 0
filename format: 0
free point: 0
grid: 0
group: 0
index: 0
intersect: 0
italic: 0
keywords tab: 0
last opened set: 1
line segment: 0
merge: 0
new set: 0
new symbol: 0
new window: 0
next card: 0
next keyword: 0
open set: 0
open symbol: 0
orientation: 0
overlap: 0
paint: 0
paste: 0
paste card: 0
paste keyword: 0
points: 0
polygon: 0
preferences: 0
previous card: 0
previous keyword: 0
print: 0
print preview: 0
rectangle: 0
redo: 0
reflection: 0
reload data: 0
reminder text: 0
remove card: 0
remove keyword: 0
remove symmetry: 0
rotate: 0
rotate 0: 0
rotate 180: 0
rotate 270: 0
rotate 90: 0
rotate card: 0
rotation: 0
save set: 0
save set as: 0
save symbol: 0
save symbol as: 0
select: 0
set code: 0
set info tab: 0
sides: 0
smooth point: 0
snap: 0
star: 0
stats tab: 0
store symbol: 0
style tab: 0
subtract: 0
symbols: 0
symmetric point: 0
symmetry: 0
undo: 0
ungroup: 0
website: 0
welcome: 0
zoom export: 0
label:
app language: 0
apprentice: 0
apprentice exe: 0
apprentice export cancelled: 0
card display: 0
card notes: 0
cards to export: 0
check at startup: 0
checking requires internet: 0
columns: 0
export filenames: 0
external programs: 0
filename conflicts: 0
filename format: 0
filename is ignored: 0
filter: 0
game type: 0
html export options: 0
html template: 0
keyword: 0
language: 0
match: 0
mode: 0
original: 0
percent of normal: 0
reminder: 0
result: 0
save changes: 1
select cards print: 0
select columns: 0
selection: 0
set code: 0
sides: optional, 0
size: 0
standard keyword: 1
style type: 0
styling options: 0
uses: 0
zoom: 0
menu:
about: 0
add card: 0
add cards: 0
add keyword: 0
basic shapes: 0
bold: 0
card list columns: 0
cards: 0
cards tab: 0
check updates: 0
close symbol editor: 0
copy: 0
cut: 0
draft tab: optional, 0
duplicate: 0
edit: 0
exit: 0
export: 0
export apprentice: 0
export html: 0
export image: 0
export images: 0
export mws: 0
file: 0
find: 0
find next: 0
format: 0
group: 0
help: 0
index: 0
insert symbol: 0
italic: 0
keywords: 0
keywords tab: 0
new set: 0
new symbol: 0
new window: 0
next card: 0
next keyword: 0
open set: 0
open symbol: 0
orientation: 0
paint: 0
paste: 0
points: 0
preferences: 0
previous card: 0
previous keyword: 0
print: 0
print preview: 0
redo: 1
reload data: 0
reminder text: 0
remove card: 0
remove keyword: 0
replace: 0
rotate: 0
rotate 0: 0
rotate 180: 0
rotate 270: 0
rotate 90: 0
save set: 0
save set as: 0
save symbol: 0
save symbol as: 0
select: 0
set info tab: 0
stats tab: 0
store symbol: 0
style tab: 0
symbols: 0
symmetry: 0
tool: 0
undo: 1
ungroup: 0
website: 0
window: 0
title:
%s - magic set editor: 1
about: 0
directories: 0
display: 0
export cancelled: 0
export html: 0
export images: 0
global: 0
locate apprentice: 0
magic set editor: 0
new status: 0
open set: 0
package list: 0
package name: 0
package status: 0
preferences: 0
print preview: 0
save changes: 0
save html: 0
save image: 0
save set: 0
select cards: 0
select cards export: 0
select columns: 0
slice image: 0
symbol editor: 0
untitled: 0
update check: 0
updates: 0
updates availible: 0
tool:
add symmetry: 0
basic shapes: 0
border: 0
cards tab: 0
curve segment: 0
difference: 0
draft tab: optional, 0
ellipse: 0
free point: 0
grid: 0
intersect: 0
keywords tab: 0
line segment: 0
merge: 0
overlap: 0
paint: optional, 0
points: 0
polygon: 0
rectangle: 0
redo: 0
reflection: 0
remove symmetry: 0
rotate: 0
rotation: 0
select: 0
set info tab: 0
smooth point: 0
snap: 0
star: 0
stats tab: 0
store symbol: 0
style tab: 0
subtract: 0
symmetric point: 0
symmetry: 0
undo: 0
tooltip:
add card: 0
add keyword: 0
add symmetry: 0
basic shapes: 0
bold: 0
border: 0
cards tab: 0
copy: 0
curve segment: 0
cut: 0
difference: 0
draft tab: optional, 0
ellipse: 0
export: 0
free point: 0
grid: 0
intersect: 0
italic: 0
keywords tab: 0
line segment: 0
merge: 0
new set: 0
open set: 0
overlap: 0
paint: optional, 0
paste: 0
points: 0
polygon: 0
rectangle: 0
redo: 1
reflection: 0
reminder text: 0
remove card: 0
remove keyword: 0
remove symmetry: 0
rotate: 0
rotate card: 0
rotation: 0
save set: 0
select: 0
set info tab: 0
smooth point: 0
snap: 0
star: 0
stats tab: 0
store symbol: 0
style tab: 0
subtract: 0
symbols: 0
symmetric point: 0
symmetry: 0
undo: 1
type:
boolean: 0
card: 0
circle: 0
collection: 0
collection of: 1
color: 0
do nothing: 0
double: 0
ellipse: 0
field: 0
function: 0
game: 0
group: 0
hexagon: 0
image: 0
install: 0
installed: 0
integer: 0
new mse: 0
nil: 0
object: 0
pentagon: 0
point: 0
points: 0
polygon: 0
rectangle: 0
reflection: 0
rhombus: 0
rotation: 0
set: 0
shape: 0
shapes: 0
square: 0
star: 0
string: 0
style: 0
stylesheet: 0
triangle: 0
uninstall: 0
uninstalled: 0
upgrade: 0
upgradeable: 0
value: 0
+4
View File
@@ -173,6 +173,10 @@ edit_symbol IMAGE "../common/edit_symbol.png"
wxBITMAP_STD_COLOURS BITMAP "wx/msw/colours.bmp"
WXCURSOR_HAND CURSOR DISCARDABLE "wx/msw/hand.cur"
// -------------------------------------------------------- : Other
expected_locale_keys TEXT "../common/expected_locale_keys"
// -------------------------------------------------------- : Version info
1 VERSIONINFO
+2 -2
View File
@@ -112,7 +112,7 @@ class ScriptCollection : public ScriptValue {
public:
inline ScriptCollection(const Collection* v) : value(v) {}
virtual ScriptType type() const { return SCRIPT_COLLECTION; }
virtual String typeName() const { return format_string(_TYPE_("collection"), type_name(*value->begin())); }
virtual String typeName() const { return format_string(_TYPE_("collection of"), type_name(*value->begin())); }
virtual ScriptValueP getMember(const String& name) const {
long index;
if (name.ToLong(&index) && index >= 0 && (size_t)index < value->size()) {
@@ -160,7 +160,7 @@ class ScriptMap : public ScriptValue {
public:
inline ScriptMap(const Collection* v) : value(v) {}
virtual ScriptType type() const { return SCRIPT_COLLECTION; }
virtual String typeName() const { return format_string(_TYPE_("collection"), type_name(value->begin())); }
virtual String typeName() const { return format_string(_TYPE_("collection of"), type_name(value->begin())); }
virtual ScriptValueP getMember(const String& name) const {
return get_member(*value, name);
}
+2 -2
View File
@@ -16,7 +16,7 @@
ScriptValue::operator String() const { return _("[[") + typeName() + _("]]"); }
ScriptValue::operator int() const { throw ScriptError(_ERROR_2_("can't convert", typeName(), _TYPE_("integer" ))); }
ScriptValue::operator double() const { throw ScriptError(_ERROR_2_("can't convert", typeName(), _TYPE_("real" ))); }
ScriptValue::operator double() const { throw ScriptError(_ERROR_2_("can't convert", typeName(), _TYPE_("double" ))); }
ScriptValue::operator Color() const { throw ScriptError(_ERROR_2_("can't convert", typeName(), _TYPE_("color" ))); }
ScriptValueP ScriptValue::eval(Context&) const { return delayError(_ERROR_2_("can't convert", typeName(), _TYPE_("function"))); }
ScriptValueP ScriptValue::getMember(const String& name) const { return delayError(_ERROR_2_("has no member", typeName(), name)); }
@@ -149,7 +149,7 @@ class ScriptDouble : public ScriptValue {
public:
ScriptDouble(double v) : value(v) {}
virtual ScriptType type() const { return SCRIPT_DOUBLE; }
virtual String typeName() const { return _TYPE_("real"); }
virtual String typeName() const { return _TYPE_("double"); }
virtual operator String() const { return String() << value; }
virtual operator double() const { return value; }
virtual operator int() const { return (int)value; }
+2
View File
@@ -25,6 +25,8 @@ struct Version {
Version() : version(0) {}
Version(UInt version) : version(version) {}
inline bool operator == (Version v) const { return version == v.version; }
inline bool operator != (Version v) const { return version != v.version; }
inline bool operator < (Version v) const { return version < v.version; }
inline bool operator <= (Version v) const { return version <= v.version; }
inline bool operator > (Version v) const { return version > v.version; }