Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/core/string/translation.cpp
20841 views
1
/**************************************************************************/
2
/* translation.cpp */
3
/**************************************************************************/
4
/* This file is part of: */
5
/* GODOT ENGINE */
6
/* https://godotengine.org */
7
/**************************************************************************/
8
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
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 "translation.h"
32
33
#include "core/os/thread.h"
34
#include "core/string/plural_rules.h"
35
#include "core/string/translation_server.h"
36
37
void _check_for_incompatibility(const String &p_msgctxt, const String &p_msgid) {
38
// Gettext PO and MO files use an empty untranslated string without context
39
// to store metadata.
40
if (p_msgctxt.is_empty() && p_msgid.is_empty()) {
41
WARN_PRINT("Both context and the untranslated string are empty. This may cause issues with the translation system and external tools.");
42
}
43
44
// The EOT character (0x04) is used as a separator between context and
45
// untranslated string in the MO file format. This convention is also used
46
// by `get_message_list()`.
47
//
48
// It's unusual to have this character in the context or untranslated
49
// string. But it doesn't do any harm as long as you are aware of this when
50
// using the relevant APIs and tools.
51
if (p_msgctxt.contains_char(0x04)) {
52
WARN_PRINT(vformat("Found EOT character (0x04) within context '%s'. This may cause issues with the translation system and external tools.", p_msgctxt));
53
}
54
if (p_msgid.contains_char(0x04)) {
55
WARN_PRINT(vformat("Found EOT character (0x04) within untranslated string '%s'. This may cause issues with the translation system and external tools.", p_msgid));
56
}
57
}
58
59
Dictionary Translation::_get_messages() const {
60
Dictionary d;
61
for (const KeyValue<MessageKey, Vector<StringName>> &E : translation_map) {
62
const Array &storage_key = { E.key.msgctxt, E.key.msgid };
63
64
Array storage_value;
65
storage_value.resize(E.value.size());
66
for (int i = 0; i < E.value.size(); i++) {
67
storage_value[i] = E.value[i];
68
}
69
d[storage_key] = storage_value;
70
}
71
return d;
72
}
73
74
void Translation::_set_messages(const Dictionary &p_messages) {
75
translation_map.clear();
76
77
for (const KeyValue<Variant, Variant> &kv : p_messages) {
78
switch (kv.key.get_type()) {
79
// Old version, no context or plural support.
80
case Variant::STRING:
81
case Variant::STRING_NAME: {
82
const MessageKey msg_key = { StringName(), kv.key };
83
_check_for_incompatibility(msg_key.msgctxt, msg_key.msgid);
84
translation_map[msg_key] = { kv.value };
85
} break;
86
87
// Current version.
88
case Variant::ARRAY: {
89
const Array &storage_key = kv.key;
90
const MessageKey msg_key = { storage_key[0], storage_key[1] };
91
92
const Array &storage_value = kv.value;
93
ERR_CONTINUE_MSG(storage_value.is_empty(), vformat("No translated strings for untranslated string '%s' with context '%s'.", msg_key.msgid, msg_key.msgctxt));
94
95
Vector<StringName> msgstrs;
96
msgstrs.resize(storage_value.size());
97
for (int i = 0; i < storage_value.size(); i++) {
98
msgstrs.write[i] = storage_value[i];
99
}
100
101
_check_for_incompatibility(msg_key.msgctxt, msg_key.msgid);
102
translation_map[msg_key] = msgstrs;
103
} break;
104
105
default: {
106
WARN_PRINT(vformat("Invalid key type in messages dictionary: %s.", Variant::get_type_name(kv.key.get_type())));
107
continue;
108
}
109
}
110
}
111
}
112
113
Vector<String> Translation::_get_message_list() const {
114
List<StringName> msgstrs;
115
get_message_list(&msgstrs);
116
117
Vector<String> keys;
118
keys.resize(msgstrs.size());
119
int idx = 0;
120
for (const StringName &msgstr : msgstrs) {
121
keys.write[idx++] = msgstr;
122
}
123
return keys;
124
}
125
126
Vector<String> Translation::get_translated_message_list() const {
127
Vector<String> msgstrs;
128
for (const KeyValue<MessageKey, Vector<StringName>> &E : translation_map) {
129
for (const StringName &msgstr : E.value) {
130
msgstrs.push_back(msgstr);
131
}
132
}
133
return msgstrs;
134
}
135
136
void Translation::set_locale(const String &p_locale) {
137
locale = TranslationServer::get_singleton()->standardize_locale(p_locale);
138
139
if (plural_rules_cache && plural_rules_override.is_empty()) {
140
memdelete(plural_rules_cache);
141
plural_rules_cache = nullptr;
142
}
143
}
144
145
void Translation::add_message(const StringName &p_src_text, const StringName &p_xlated_text, const StringName &p_context) {
146
_check_for_incompatibility(p_context, p_src_text);
147
translation_map[{ p_context, p_src_text }] = { p_xlated_text };
148
}
149
150
void Translation::add_plural_message(const StringName &p_src_text, const Vector<String> &p_plural_xlated_texts, const StringName &p_context) {
151
ERR_FAIL_COND_MSG(p_plural_xlated_texts.is_empty(), "Parameter vector p_plural_xlated_texts passed in is empty.");
152
153
Vector<StringName> msgstrs;
154
msgstrs.resize(p_plural_xlated_texts.size());
155
for (int i = 0; i < p_plural_xlated_texts.size(); i++) {
156
msgstrs.write[i] = p_plural_xlated_texts[i];
157
}
158
159
_check_for_incompatibility(p_context, p_src_text);
160
translation_map[{ p_context, p_src_text }] = msgstrs;
161
}
162
163
StringName Translation::get_message(const StringName &p_src_text, const StringName &p_context) const {
164
StringName ret;
165
if (GDVIRTUAL_CALL(_get_message, p_src_text, p_context, ret)) {
166
return ret;
167
}
168
169
const Vector<StringName> *msgstrs = translation_map.getptr({ p_context, p_src_text });
170
if (msgstrs == nullptr) {
171
return StringName();
172
}
173
174
DEV_ASSERT(!msgstrs->is_empty()); // Should be prevented when adding messages.
175
return msgstrs->get(0);
176
}
177
178
StringName Translation::get_plural_message(const StringName &p_src_text, const StringName &p_plural_text, int p_n, const StringName &p_context) const {
179
StringName ret;
180
if (GDVIRTUAL_CALL(_get_plural_message, p_src_text, p_plural_text, p_n, p_context, ret)) {
181
return ret;
182
}
183
184
ERR_FAIL_COND_V_MSG(p_n < 0, StringName(), "N passed into translation to get a plural message should not be negative. For negative numbers, use singular translation please. Search \"gettext PO Plural Forms\" online for details on translating negative numbers.");
185
186
const Vector<StringName> *msgstrs = translation_map.getptr({ p_context, p_src_text });
187
if (msgstrs == nullptr) {
188
return StringName();
189
}
190
191
const int index = _get_plural_rules()->evaluate(p_n);
192
ERR_FAIL_INDEX_V_MSG(index, msgstrs->size(), StringName(), "Plural index returned or number of plural translations is not valid.");
193
return msgstrs->get(index);
194
}
195
196
void Translation::erase_message(const StringName &p_src_text, const StringName &p_context) {
197
translation_map.erase({ p_context, p_src_text });
198
}
199
200
void Translation::get_message_list(List<StringName> *r_messages) const {
201
for (const KeyValue<MessageKey, Vector<StringName>> &E : translation_map) {
202
if (E.key.msgctxt.is_empty()) {
203
r_messages->push_back(E.key.msgid);
204
} else {
205
// Separated by the EOT character. Compatible with the MO file format.
206
r_messages->push_back(vformat("%s\x04%s", E.key.msgctxt, E.key.msgid));
207
}
208
}
209
}
210
211
int Translation::get_message_count() const {
212
return translation_map.size();
213
}
214
215
PluralRules *Translation::_get_plural_rules() const {
216
if (plural_rules_cache) {
217
return plural_rules_cache;
218
}
219
220
if (!plural_rules_override.is_empty()) {
221
plural_rules_cache = PluralRules::parse(plural_rules_override);
222
}
223
224
if (!plural_rules_cache) {
225
// Locale's default plural rules.
226
const String &default_rule = TranslationServer::get_singleton()->get_plural_rules(locale);
227
if (!default_rule.is_empty()) {
228
plural_rules_cache = PluralRules::parse(default_rule);
229
}
230
231
// Use English plural rules as a fallback.
232
if (!plural_rules_cache) {
233
plural_rules_cache = PluralRules::parse("nplurals=2; plural=(n != 1);");
234
}
235
}
236
237
DEV_ASSERT(plural_rules_cache != nullptr);
238
return plural_rules_cache;
239
}
240
241
void Translation::set_plural_rules_override(const String &p_rules) {
242
plural_rules_override = p_rules;
243
if (plural_rules_cache) {
244
memdelete(plural_rules_cache);
245
plural_rules_cache = nullptr;
246
}
247
}
248
249
String Translation::get_plural_rules_override() const {
250
return plural_rules_override;
251
}
252
253
int Translation::get_nplurals() const {
254
return _get_plural_rules()->get_nplurals();
255
}
256
257
void Translation::_bind_methods() {
258
ClassDB::bind_method(D_METHOD("set_locale", "locale"), &Translation::set_locale);
259
ClassDB::bind_method(D_METHOD("get_locale"), &Translation::get_locale);
260
ClassDB::bind_method(D_METHOD("add_message", "src_message", "xlated_message", "context"), &Translation::add_message, DEFVAL(StringName()));
261
ClassDB::bind_method(D_METHOD("add_plural_message", "src_message", "xlated_messages", "context"), &Translation::add_plural_message, DEFVAL(StringName()));
262
ClassDB::bind_method(D_METHOD("get_message", "src_message", "context"), &Translation::get_message, DEFVAL(StringName()));
263
ClassDB::bind_method(D_METHOD("get_plural_message", "src_message", "src_plural_message", "n", "context"), &Translation::get_plural_message, DEFVAL(StringName()));
264
ClassDB::bind_method(D_METHOD("erase_message", "src_message", "context"), &Translation::erase_message, DEFVAL(StringName()));
265
ClassDB::bind_method(D_METHOD("get_message_list"), &Translation::_get_message_list);
266
ClassDB::bind_method(D_METHOD("get_translated_message_list"), &Translation::get_translated_message_list);
267
ClassDB::bind_method(D_METHOD("get_message_count"), &Translation::get_message_count);
268
ClassDB::bind_method(D_METHOD("_set_messages", "messages"), &Translation::_set_messages);
269
ClassDB::bind_method(D_METHOD("_get_messages"), &Translation::_get_messages);
270
ClassDB::bind_method(D_METHOD("set_plural_rules_override", "rules"), &Translation::set_plural_rules_override);
271
ClassDB::bind_method(D_METHOD("get_plural_rules_override"), &Translation::get_plural_rules_override);
272
273
GDVIRTUAL_BIND(_get_plural_message, "src_message", "src_plural_message", "n", "context");
274
GDVIRTUAL_BIND(_get_message, "src_message", "context");
275
276
ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "messages", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_messages", "_get_messages");
277
ADD_PROPERTY(PropertyInfo(Variant::STRING, "locale", PROPERTY_HINT_LOCALE_ID), "set_locale", "get_locale");
278
ADD_PROPERTY(PropertyInfo(Variant::STRING, "plural_rules_override"), "set_plural_rules_override", "get_plural_rules_override");
279
}
280
281
Translation::~Translation() {
282
if (plural_rules_cache) {
283
memdelete(plural_rules_cache);
284
plural_rules_cache = nullptr;
285
}
286
}
287
288