Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 908e04c

Browse files
committedAug 25, 2020
Add CSV plural support
1 parent a609b30 commit 908e04c

16 files changed

+642
-162
lines changed
 

‎core/compressed_translation.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#include "compressed_translation.h"
3232

3333
#include "core/pair.h"
34+
#include "core/translation_plural_rules.h"
3435

3536
extern "C" {
3637
#include "thirdparty/misc/smaz.h"
@@ -272,8 +273,10 @@ StringName PHashTranslation::get_message(const StringName &p_src_text, const Str
272273
}
273274

274275
StringName PHashTranslation::get_plural_message(const StringName &p_src_text, const StringName &p_plural_text, int p_n, const StringName &p_context) const {
275-
// The use of plurals translation is not yet supported in PHashTranslation.
276-
return get_message(p_src_text, p_context);
276+
// It is assumed for now that PHashTranslation is only used by CSV translation. So get_plural_message() method follows the CSV plural format to fetch the translation.
277+
int index = TranslationPluralRules::get_plural_index(get_locale(), p_n);
278+
String search_src = String(p_src_text) + "[" + String::num_int64(index) + "]";
279+
return get_message(search_src, p_context);
277280
}
278281

279282
void PHashTranslation::_get_property_list(List<PropertyInfo> *p_list) const {

‎core/io/translation_loader_po.cpp

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -139,12 +139,19 @@ RES TranslationLoaderPO::load_translation(FileAccess *f, Error *r_error) {
139139
}
140140
} else if (config == "") {
141141
config = msg_str;
142-
// Record plural rule.
143-
int p_start = config.find("Plural-Forms");
144-
if (p_start != -1) {
145-
int p_end = config.find("\n", p_start);
146-
translation->set_plural_rule(config.substr(p_start, p_end - p_start));
147-
plural_forms = translation->get_plural_forms();
142+
// Set plural_forms and locale.
143+
int nplural_start = config.find("nplurals=");
144+
if (nplural_start != -1) {
145+
nplural_start += String("nplurals=").length();
146+
int nplural_end = config.find(";", nplural_start);
147+
plural_forms = config.substr(nplural_start, nplural_end - nplural_start).to_int();
148+
149+
int lang_start = config.find("Language:") + String("Language:").length();
150+
if (lang_start - String("Language:").length() == -1) {
151+
lang_start = config.find("X-Language:") + String("X-Language:").length();
152+
}
153+
int lang_end = config.find("\n", lang_start);
154+
translation->set_locale(config.substr(lang_start, lang_end - lang_start).strip_edges());
148155
}
149156
}
150157

‎core/object.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1439,10 +1439,10 @@ String Object::tr(const StringName &p_message, const StringName &p_context) cons
14391439
return TranslationServer::get_singleton()->translate(p_message, p_context);
14401440
}
14411441

1442-
String Object::tr_n(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const {
1442+
String Object::tr_n(int p_n, const StringName &p_message, const StringName &p_message_plural, const StringName &p_context) const {
14431443
if (!_can_translate || !TranslationServer::get_singleton()) {
1444-
// Return message based on English plural rule if translation is not possible.
1445-
if (p_n == 1) {
1444+
// If no plural message, just return the key (for CSV). Else, return message based on English plural rule.
1445+
if (p_message_plural == StringName() || p_n == 1) {
14461446
return p_message;
14471447
}
14481448
return p_message_plural;
@@ -1589,7 +1589,7 @@ void Object::_bind_methods() {
15891589
ClassDB::bind_method(D_METHOD("set_message_translation", "enable"), &Object::set_message_translation);
15901590
ClassDB::bind_method(D_METHOD("can_translate_messages"), &Object::can_translate_messages);
15911591
ClassDB::bind_method(D_METHOD("tr", "message", "context"), &Object::tr, DEFVAL(""));
1592-
ClassDB::bind_method(D_METHOD("tr_n", "message", "plural_message", "n", "context"), &Object::tr_n, DEFVAL(""));
1592+
ClassDB::bind_method(D_METHOD("tr_n", "n", "message", "plural_message", "context"), &Object::tr_n, DEFVAL(""), DEFVAL(""));
15931593

15941594
ClassDB::bind_method(D_METHOD("is_queued_for_deletion"), &Object::is_queued_for_deletion);
15951595

‎core/object.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -720,7 +720,7 @@ class Object {
720720
virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const;
721721

722722
String tr(const StringName &p_message, const StringName &p_context = "") const; // translate message (internationalization)
723-
String tr_n(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context = "") const;
723+
String tr_n(int p_n, const StringName &p_message, const StringName &p_message_plural = "", const StringName &p_context = "") const;
724724

725725
bool _is_queued_for_deletion = false; // set to true by SceneTree::queue_delete()
726726
bool is_queued_for_deletion() const;

‎core/plural_rules_builder.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
#### TO MENTORS:
2+
#### I'm not sure what is the right way to write a Python script in Godot? I saw other Python files all have def func_name(target, source, env).
3+
#### Also I'm not sure of the location.
4+
#### Right now I call this script using the "python" command.
5+
6+
# This script is used to generate core/translation_plural_rules.h
7+
# The plural data used in this Python script is from POEdit Github repository - https://github.com/vslavik/poedit/raw/master/src/language_impl_plurals.h
8+
# POEdit in turn parsed and extracted the plural data from Unicode Consortium (see http://www.unicode.org/cldr/charts/supplemental/language_plural_rules.html).
9+
10+
#!/bin/python
11+
12+
import sys
13+
import urllib.request
14+
15+
target_file = "translation_plural_rules.h"
16+
17+
plural_data_url = "https://github.com/vslavik/poedit/raw/master/src/language_impl_plurals.h"
18+
generate_text = "// The rest of the file is generated via plural_rules_builder.py (DO NOT MODIFY THIS LINE).\n"
19+
20+
# Get plural data from POEdit github repository. See above for the link.
21+
text = ""
22+
with urllib.request.urlopen(plural_data_url) as response:
23+
text = response.read().decode("utf-8")
24+
25+
# Build dictionary mapping plural rule to a list of locales from plural data. Also store the number of plural forms each local has.
26+
rules = text.split("\n")
27+
rule_to_locales = {}
28+
locale_nplural = {}
29+
for rule in rules:
30+
if rule.find("plural=") == -1:
31+
continue
32+
33+
first_quote = rule.find('"')
34+
second_quote = rule.find('"', first_quote + 1)
35+
locale = rule[first_quote + 1 : second_quote]
36+
37+
plural_start = rule.find("plural=") + len("plural=")
38+
plural_end = rule.find(";", plural_start)
39+
plural_rule = rule[plural_start:plural_end]
40+
41+
if plural_rule in rule_to_locales:
42+
rule_to_locales[plural_rule].append(locale)
43+
else:
44+
rule_to_locales[plural_rule] = [locale]
45+
46+
nplural_start = rule.find("nplurals=") + len("nplurals=")
47+
locale_nplural[locale] = rule[nplural_start : nplural_start + 1]
48+
49+
# Find the position in translation_plural_rules.h for code generation.
50+
with open(target_file, "r") as f:
51+
text = f.read()
52+
gen_start_pos = text.find(generate_text) + len(generate_text)
53+
pretext = text[:gen_start_pos]
54+
55+
# Generate mapping in build_mapping().
56+
gen_content = "\n"
57+
rule_number = 1
58+
for rule in rule_to_locales:
59+
for locale in rule_to_locales[rule]:
60+
gen_content += (
61+
'\t\ttemp.set("' + locale + '", { ' + locale_nplural[locale] + ", &rule" + str(rule_number) + " });\n"
62+
)
63+
rule_number += 1
64+
gen_content += "\t\treturn temp;\n\t}\n"
65+
66+
# Generate rule functions.
67+
rule_number = 1
68+
for rule in rule_to_locales:
69+
gen_content += "\n\tstatic int rule" + str(rule_number) + "(const int p_n) {\n\t\t// "
70+
gen_content += ", ".join(rule_to_locales[rule]) + ".\n\t\t"
71+
gen_content += "return " + rule.replace("n", "p_n") + ";\n\t}\n"
72+
rule_number += 1
73+
74+
gen_content += "};\n\n#endif // TRANSLATION_PLURAL_RULES_H\n"
75+
76+
# Write to file.
77+
with open(target_file, "w") as f:
78+
f.write(pretext + gen_content)

‎core/translation.cpp

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
#include "core/io/resource_loader.h"
3434
#include "core/os/os.h"
3535
#include "core/project_settings.h"
36+
#include "core/translation_plural_rules.h"
3637

3738
// ISO 639-1 language codes, with the addition of glibc locales with their
3839
// regional identifiers. This list must match the language names (in English)
@@ -845,9 +846,7 @@ void Translation::add_message(const StringName &p_src_text, const StringName &p_
845846
}
846847

847848
void Translation::add_plural_message(const StringName &p_src_text, const Vector<String> &p_plural_xlated_texts, const StringName &p_context) {
848-
WARN_PRINT("Translation class doesn't handle plural messages. Calling add_plural_message() on a Translation instance is probably a mistake. \nUse a derived Translation class that handles plurals, such as TranslationPO class");
849-
ERR_FAIL_COND_MSG(p_plural_xlated_texts.empty(), "Parameter vector p_plural_xlated_texts passed in is empty.");
850-
translation_map[p_src_text] = p_plural_xlated_texts[0];
849+
WARN_PRINT("Translation class doesn't use add_plural_message(), as plural keys are appended with subscript by users. Calling add_plural_message() on a Translation instance is probably a mistake.");
851850
}
852851

853852
StringName Translation::get_message(const StringName &p_src_text, const StringName &p_context) const {
@@ -864,8 +863,24 @@ StringName Translation::get_message(const StringName &p_src_text, const StringNa
864863
}
865864

866865
StringName Translation::get_plural_message(const StringName &p_src_text, const StringName &p_plural_text, int p_n, const StringName &p_context) const {
867-
WARN_PRINT("Translation class doesn't handle plural messages. Calling get_plural_message() on a Translation instance is probably a mistake. \nUse a derived Translation class that handles plurals, such as TranslationPO class");
868-
return get_message(p_src_text);
866+
int index = TranslationPluralRules::get_plural_index(locale, p_n);
867+
String search_src = String(p_src_text) + "[" + String::num_int64(index) + "]";
868+
869+
const Map<StringName, StringName>::Element *E = translation_map.find(search_src);
870+
if (E) {
871+
return E->get();
872+
}
873+
874+
// Fallback to singular translation.
875+
WARN_PRINT("CSV plural translation for \"" + String(search_src) + "\" with locale \"" + locale + "\" is not found. Returning singular translation...");
876+
search_src.set(search_src.length() - 2, '0');
877+
E = translation_map.find(search_src);
878+
if (!E) {
879+
WARN_PRINT("Singular translation \"" + String(search_src) + "\" with locale \"" + locale + "\" is not found.");
880+
return StringName();
881+
}
882+
883+
return E->get();
869884
}
870885

871886
void Translation::erase_message(const StringName &p_src_text, const StringName &p_context) {
@@ -979,6 +994,14 @@ String TranslationServer::get_locale() const {
979994
return locale;
980995
}
981996

997+
int TranslationServer::get_plural_index(const String &p_locale, int p_n) {
998+
return TranslationPluralRules::get_plural_index(p_locale, p_n);
999+
}
1000+
1001+
int TranslationServer::get_plural_forms(const String &p_locale) {
1002+
return TranslationPluralRules::get_plural_forms(p_locale);
1003+
}
1004+
9821005
String TranslationServer::get_locale_name(const String &p_locale) const {
9831006
if (!locale_name_map.has(p_locale)) {
9841007
return String();
@@ -1085,7 +1108,7 @@ StringName TranslationServer::translate(const StringName &p_message, const Strin
10851108

10861109
StringName TranslationServer::translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const {
10871110
if (!enabled) {
1088-
if (p_n == 1) {
1111+
if (p_message_plural == StringName() || p_n == 1) {
10891112
return p_message;
10901113
}
10911114
return p_message_plural;
@@ -1100,7 +1123,7 @@ StringName TranslationServer::translate_plural(const StringName &p_message, cons
11001123
}
11011124

11021125
if (!res) {
1103-
if (p_n == 1) {
1126+
if (p_message_plural == StringName() || p_n == 1) {
11041127
return p_message;
11051128
}
11061129
return p_message_plural;
@@ -1269,6 +1292,8 @@ StringName TranslationServer::doc_translate_plural(const StringName &p_message,
12691292
void TranslationServer::_bind_methods() {
12701293
ClassDB::bind_method(D_METHOD("set_locale", "locale"), &TranslationServer::set_locale);
12711294
ClassDB::bind_method(D_METHOD("get_locale"), &TranslationServer::get_locale);
1295+
ClassDB::bind_method(D_METHOD("get_plural_index", "locale", "n"), &TranslationServer::get_plural_index);
1296+
ClassDB::bind_method(D_METHOD("get_plural_forms", "locale"), &TranslationServer::get_plural_forms);
12721297

12731298
ClassDB::bind_method(D_METHOD("get_locale_name", "locale"), &TranslationServer::get_locale_name);
12741299

‎core/translation.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ class TranslationServer : public Object {
9292

9393
void set_locale(const String &p_locale);
9494
String get_locale() const;
95+
int get_plural_index(const String &p_locale, int p_n);
96+
int get_plural_forms(const String &p_locale);
9597
Ref<Translation> get_translation_object(const String &p_locale);
9698

9799
String get_locale_name(const String &p_locale) const;

‎core/translation_plural_rules.cpp

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*************************************************************************/
2+
/* translation_plural_rules.cpp */
3+
/*************************************************************************/
4+
/* This file is part of: */
5+
/* GODOT ENGINE */
6+
/* https://godotengine.org */
7+
/*************************************************************************/
8+
/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
9+
/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
10+
/* */
11+
/* Permission is hereby granted, free of charge, to any person obtaining */
12+
/* a copy of this software and associated documentation files (the */
13+
/* "Software"), to deal in the Software without restriction, including */
14+
/* without limitation the rights to use, copy, modify, merge, publish, */
15+
/* distribute, sublicense, and/or sell copies of the Software, and to */
16+
/* permit persons to whom the Software is furnished to do so, subject to */
17+
/* the following conditions: */
18+
/* */
19+
/* The above copyright notice and this permission notice shall be */
20+
/* included in all copies or substantial portions of the Software. */
21+
/* */
22+
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23+
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24+
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
25+
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26+
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27+
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28+
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29+
/*************************************************************************/
30+
31+
#include "core/translation_plural_rules.h"
32+
33+
// Initialize static member plural_rule_mapping.
34+
HashMap<String, TranslationPluralRules::PluralData> TranslationPluralRules::plural_rule_mapping = TranslationPluralRules::build_mapping();
There was a problem loading the remainder of the diff.

0 commit comments

Comments
 (0)
Please sign in to comment.