Line data Source code
1 : /**
2 : Copyright (c) 2023 Stappler LLC <admin@stappler.dev>
3 :
4 : Permission is hereby granted, free of charge, to any person obtaining a copy
5 : of this software and associated documentation files (the "Software"), to deal
6 : in the Software without restriction, including without limitation the rights
7 : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 : copies of the Software, and to permit persons to whom the Software is
9 : furnished to do so, subject to the following conditions:
10 :
11 : The above copyright notice and this permission notice shall be included in
12 : all copies or substantial portions of the Software.
13 :
14 : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 : IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 : FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 : AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 : LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 : OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 : THE SOFTWARE.
21 : **/
22 :
23 : #include "XLFontLocale.h"
24 :
25 : namespace STAPPLER_VERSIONIZED stappler::xenolith::locale {
26 :
27 : class LocaleManager {
28 : public:
29 : using StringMap = memory::dict<memory::u16string, memory::u16string>;
30 : using LocaleMap = memory::map<memory::string, StringMap>;
31 :
32 : using StringIndexMap = memory::dict<size_t, memory::u16string>;
33 : using LocaleIndexMap = memory::map<memory::string, StringIndexMap>;
34 :
35 : using Interface = memory::PoolInterface;
36 :
37 : static LocaleManager *s_sharedInstance;
38 :
39 12171 : static LocaleManager *getInstance() {
40 12171 : if (!s_sharedInstance) {
41 12 : auto p = memory::pool::createTagged(nullptr, "LocaleManager");
42 12 : memory::pool::push(p);
43 12 : s_sharedInstance = new LocaleManager(p);
44 12 : memory::pool::pop();
45 : }
46 12171 : return s_sharedInstance;
47 : }
48 :
49 12 : LocaleManager(memory::pool_t *p) : _defaultTime{
50 : "today",
51 : "yesterday",
52 : "jan", "feb", "mar", "apr", "may", "jun",
53 : "jul", "aug", "sep", "oct", "nov", "dec"
54 12 : }, _pool(p) {
55 204 : define("ru-ru", {
56 12 : pair("SystemSearch", "Найти"),
57 12 : pair("SystemFontSize", "Размер шрифта"),
58 12 : pair("SystemTheme", "Оформление"),
59 12 : pair("SystemThemeLight", "Светлая тема"),
60 12 : pair("SystemThemeNeutral", "Нейтральная тема"),
61 12 : pair("SystemThemeDark", "Темная тема"),
62 12 : pair("SystemMore", "Ещё"),
63 12 : pair("SystemRestore", "Восстановить"),
64 12 : pair("SystemRemoved", "Удалено"),
65 12 : pair("SystemCopy", "Копировать"),
66 12 : pair("SystemCut", "Вырезать"),
67 12 : pair("SystemPaste", "Вставить"),
68 12 : pair("SystemTapExit", "Нажмите ещё раз для выхода"),
69 :
70 12 : pair("SystemErrorOverflowChars", "Слишком много символов"),
71 12 : pair("SystemErrorInvalidChar", "Недопустимый символ"),
72 :
73 12 : pair("Shortcut:Megabytes", "Мб"),
74 12 : pair("Shortcut:Pages", "с"),
75 : });
76 :
77 204 : define("en-us", {
78 12 : pair("SystemSearch", "Search"),
79 12 : pair("SystemFontSize", "Font size"),
80 12 : pair("SystemTheme", "Theme"),
81 12 : pair("SystemThemeLight", "Light theme"),
82 12 : pair("SystemThemeNeutral", "Neutral theme"),
83 12 : pair("SystemThemeDark", "Dark theme"),
84 12 : pair("SystemMore", "More"),
85 12 : pair("SystemRestore", "Restore"),
86 12 : pair("SystemRemoved", "Removed"),
87 12 : pair("SystemCopy", "Copy"),
88 12 : pair("SystemCut", "Cut"),
89 12 : pair("SystemPaste", "Paste"),
90 12 : pair("SystemTapExit", "Tap one more time to exit"),
91 :
92 12 : pair("SystemErrorOverflowChars", "Too many characters"),
93 12 : pair("SystemErrorInvalidChar", "Invalid character"),
94 :
95 12 : pair("Shortcut:Megabytes", "Mb"),
96 12 : pair("Shortcut:Pages", "p"),
97 : });
98 12 : }
99 :
100 48 : void define(const StringView &locale, LocaleInitList &&init) {
101 48 : memory::pool::push(_pool);
102 48 : auto it = _strings.find(locale);
103 48 : if (it == _strings.end()) {
104 24 : it = _strings.emplace(locale.str<Interface>(), StringMap()).first;
105 : }
106 768 : for (auto &iit : init) {
107 720 : it->second.emplace(string::toUtf16<Interface>(iit.first), string::toUtf16<Interface>(iit.second));
108 : }
109 48 : memory::pool::pop();
110 48 : }
111 :
112 24 : void define(const StringView &locale, LocaleIndexList &&init) {
113 24 : memory::pool::push(_pool);
114 24 : auto it = _indexes.find(locale);
115 24 : if (it == _indexes.end()) {
116 24 : it = _indexes.emplace(locale.str<Interface>(), StringIndexMap()).first;
117 : }
118 288 : for (auto &iit : init) {
119 264 : it->second.emplace(iit.first, string::toUtf16<Interface>(iit.second));
120 : }
121 24 : memory::pool::pop();
122 24 : }
123 :
124 24 : void define(const StringView &locale, const std::array<StringView, toInt(TimeTokens::Max)> &arr) {
125 24 : memory::pool::push(_pool);
126 24 : auto it = _timeTokens.find(locale);
127 24 : if (it == _timeTokens.end()) {
128 24 : it = _timeTokens.emplace(locale.str<Interface>(), std::array<memory::string, toInt(TimeTokens::Max)>()).first;
129 : }
130 :
131 24 : size_t i = 0;
132 360 : for (auto &arr_it : arr) {
133 336 : it->second[i] = arr_it.str<Interface>();
134 336 : ++ i;
135 : }
136 24 : memory::pool::pop();
137 24 : }
138 :
139 1021 : WideStringView string(const WideStringView &str) {
140 1021 : auto it = _strings.find(StringView(_locale));
141 1021 : if (it == _strings.end()) {
142 793 : it = _strings.find(StringView(_default));
143 : }
144 1021 : if (it == _strings.end()) {
145 793 : it = _strings.begin();
146 : }
147 :
148 1021 : if (it != _strings.end()) {
149 1021 : auto sit = it->second.find(str);
150 1021 : if (sit != it->second.end()) {
151 180 : return sit->second;
152 : }
153 : }
154 :
155 841 : return WideStringView();
156 : }
157 :
158 55 : WideStringView string(size_t index) {
159 55 : auto it = _indexes.find(StringView(_locale));
160 55 : if (it == _indexes.end()) {
161 19 : it = _indexes.find(StringView(_default));
162 : }
163 55 : if (it == _indexes.end()) {
164 19 : it = _indexes.begin();
165 : }
166 :
167 55 : if (it != _indexes.end()) {
168 48 : auto sit = it->second.find(index);
169 48 : if (sit != it->second.end()) {
170 36 : return sit->second;
171 : }
172 : }
173 :
174 19 : return WideStringView();
175 : }
176 :
177 144 : WideStringView numeric(const WideStringView &str, uint32_t num) {
178 144 : auto ruleIt = _numRules.find(StringView(_locale));
179 144 : if (ruleIt == _numRules.end()) {
180 48 : return string(str);
181 : } else {
182 96 : uint8_t numEq = ruleIt->second(num);
183 96 : auto fmt = string(str);
184 96 : WideStringView r(fmt);
185 96 : WideStringView def = r.readUntil<WideStringView::Chars<':'>>();
186 96 : if (r.empty() || numEq == 0) {
187 60 : return def;
188 : }
189 :
190 72 : while (!r.empty()) {
191 60 : if (r.is(':')) {
192 60 : ++ r;
193 : }
194 :
195 60 : WideStringView res = r.readUntil<WideStringView::Chars<':'>>();
196 60 : if (numEq == 1) {
197 24 : return res;
198 : } else {
199 36 : -- numEq;
200 : }
201 : }
202 :
203 12 : return def;
204 : }
205 : }
206 :
207 12 : void setDefault(const String &def) {
208 12 : _default = def;
209 12 : }
210 12 : const String &getDefault() {
211 12 : return _default;
212 : }
213 :
214 12 : void setLocale(const String &loc) {
215 12 : if (_locale != loc) {
216 12 : _locale = loc;
217 12 : onLocale(nullptr, loc);
218 : }
219 12 : }
220 12 : const String &getLocale() {
221 12 : return _locale;
222 : }
223 :
224 24 : void setNumRule(const StringView &locale, const NumRule &rule) {
225 24 : _numRules.emplace(locale.str<Interface>(), rule);
226 24 : }
227 :
228 8834 : bool hasLocaleTagsFast(WideStringView r) {
229 8834 : if (r.empty()) {
230 24 : return false;
231 : }
232 :
233 8810 : if (r.is(u"@Locale:")) { // raw locale string
234 12 : return true;
235 8798 : } else if (r.is(u"%=")) {
236 12 : r += 2;
237 12 : auto tmp = r.readChars<WideStringView::CharGroup<CharGroupId::Numbers>>();
238 12 : if (!tmp.empty() && r.is('%')) {
239 12 : return true;
240 : }
241 : } else {
242 8786 : constexpr size_t maxChars = config::MaxFastLocaleChars;
243 8786 : WideStringView shortView(r.data(), std::min(r.size(), maxChars));
244 8786 : shortView.skipUntil<WideStringView::Chars<'%'>>();
245 8786 : if (shortView.is('%')) {
246 75 : ++ shortView;
247 75 : if (shortView.is('=')) {
248 24 : ++ shortView;
249 24 : shortView = WideStringView(shortView.data(), std::min(maxChars, r.size() - (shortView.data() - r.data())));
250 24 : shortView.skipChars<WideStringView::CharGroup<CharGroupId::Numbers>>();
251 24 : if (shortView.is('%')) {
252 39 : return true;
253 : }
254 : } else {
255 51 : shortView = WideStringView(shortView.data(), std::min(maxChars, r.size() - (shortView.data() - r.data())));
256 : shortView.skipChars<
257 : WideStringView::CharGroup<CharGroupId::Alphanumeric>,
258 51 : WideStringView::Chars<':', '.', '-', '_', '[', ']', '+', '='>>();
259 51 : if (shortView.is('%')) {
260 27 : return true;
261 : }
262 : }
263 : }
264 : }
265 8747 : return false;
266 : }
267 :
268 1908 : bool hasLocaleTags(WideStringView r) {
269 1908 : if (r.empty()) {
270 12 : return false;
271 : }
272 :
273 1896 : if (r.is(u"@Locale:")) { // raw locale string
274 12 : return true;
275 1884 : } else if (r.is(u"%=")) {
276 19 : r += 2;
277 19 : auto tmp = r.readChars<WideStringView::CharGroup<CharGroupId::Numbers>>();
278 19 : if (!tmp.empty() && r.is('%')) {
279 19 : return true;
280 : }
281 : } else {
282 3061 : while (!r.empty()) {
283 1865 : r.skipUntil<WideStringView::Chars<'%'>>();
284 1865 : if (r.is('%')) {
285 937 : ++ r;
286 937 : if (r.is('=')) {
287 24 : ++ r;
288 24 : r.skipChars<WideStringView::CharGroup<CharGroupId::Numbers>>();
289 24 : if (r.is('%')) {
290 12 : return true;
291 : }
292 : } else {
293 : r.skipChars<
294 : WideStringView::CharGroup<CharGroupId::Alphanumeric>,
295 913 : WideStringView::Chars<':', '.', '-', '_', '[', ']', '+', '='>>();
296 913 : if (r.is('%')) {
297 657 : return true;
298 : }
299 : }
300 : }
301 : }
302 : }
303 :
304 1196 : return false;
305 : }
306 :
307 889 : WideString resolveLocaleTags(WideStringView r) {
308 889 : if (r.is(u"@Locale:")) { // raw locale string
309 12 : r += "@Locale:"_len;
310 24 : return string(r).str<memory::StandartInterface>();
311 : } else {
312 877 : WideString ret; ret.reserve(r.size());
313 2595 : while (!r.empty()) {
314 1718 : auto tmp = r.readUntil<WideStringView::Chars<'%'>>();
315 1718 : ret.append(tmp.data(), tmp.size());
316 1718 : if (r.is('%')) {
317 884 : ++ r;
318 : auto token = r.readChars<
319 : WideStringView::CharGroup<CharGroupId::Alphanumeric>,
320 884 : WideStringView::Chars<':', '.', '-', '_', '[', ']', '+', '='>>();
321 884 : if (r.is('%')) {
322 860 : ++ r;
323 860 : WideStringView replacement;
324 860 : if (token.is('=')) {
325 31 : auto numToken = token;
326 31 : ++ numToken;
327 31 : if (numToken.is<WideStringView::CharGroup<CharGroupId::Numbers>>()) {
328 31 : numToken.readInteger().unwrap([&, this] (int64_t id) {
329 31 : if (numToken.empty()) {
330 31 : replacement = string(size_t(id));
331 : }
332 31 : });
333 : }
334 829 : } else if (token.is(u"Num:")) {
335 36 : WideStringView splitMaster(token);
336 36 : WideStringView num;
337 144 : while (!splitMaster.empty()) {
338 108 : num = splitMaster.readUntil<WideStringView::Chars<':'>>();
339 108 : if (splitMaster.is(':')) {
340 72 : ++ splitMaster;
341 : }
342 : }
343 :
344 36 : if (!num.empty()) {
345 36 : WideStringView validate(num);
346 36 : if (validate.is('-')) {
347 12 : ++ validate;
348 : }
349 36 : validate.skipChars<WideStringView::CharGroup<CharGroupId::Numbers>>();
350 36 : if (validate.empty()) {
351 36 : WideStringView vtoken(token.data(), token.size() - num.size() - 1);
352 36 : num.readInteger().unwrap([&, this] (int64_t id) {
353 36 : replacement = numeric(vtoken, id);
354 36 : });
355 : }
356 : }
357 : }
358 :
359 860 : if (replacement.empty() && !token.is('=')) {
360 817 : replacement = string(token);
361 : }
362 :
363 860 : if (replacement.empty()) {
364 812 : ret.push_back(u'%');
365 812 : ret.append(token.data(), token.size());
366 812 : ret.push_back(u'%');
367 : } else {
368 48 : ret.append(replacement.str<memory::StandartInterface>());
369 : }
370 : } else {
371 24 : ret.push_back(u'%');
372 24 : ret.append(token.data(), token.size());
373 : }
374 : }
375 : }
376 877 : return ret;
377 877 : }
378 : return WideString();
379 : }
380 :
381 36 : String language(const StringView &locale) {
382 36 : if (locale == "ru-ru") {
383 12 : return "Русский";
384 24 : } else if (locale.starts_with("en-")) {
385 12 : return "English";
386 : }
387 12 : return String();
388 : }
389 :
390 36 : String common(const StringView &locale) {
391 36 : String ret = string::StringTraits<memory::StandartInterface>::tolower(locale);
392 36 : if (ret.size() == 2) {
393 24 : if (ret == "en") {
394 12 : ret.reserve(5);
395 12 : ret.append("-gb");
396 : } else {
397 12 : ret.reserve(5);
398 12 : ret.append("-").append(string::StringTraits<memory::StandartInterface>::tolower(locale));
399 : }
400 : }
401 :
402 36 : return ret;
403 0 : }
404 :
405 48 : StringView timeToken(TimeTokens tok) {
406 48 : auto it = _timeTokens.find(StringView(_locale));
407 48 : if (it == _timeTokens.end()) {
408 24 : it = _timeTokens.find(StringView(_default));
409 : }
410 :
411 48 : auto &table = it == _timeTokens.end()?_defaultTime:it->second;
412 48 : return table[toInt(tok)];
413 : }
414 :
415 96 : const std::array<memory::string, toInt(TimeTokens::Max)> &timeTokenTable() {
416 96 : auto it = _timeTokens.find(StringView(_locale));
417 96 : if (it == _timeTokens.end()) {
418 12 : it = _timeTokens.find(StringView(_default));
419 : }
420 :
421 96 : return it == _timeTokens.end()?_defaultTime:it->second;
422 : }
423 :
424 : protected:
425 : String _default;
426 : String _locale;
427 :
428 : LocaleMap _strings;
429 : LocaleIndexMap _indexes;
430 : memory::map<memory::string, NumRule> _numRules;
431 : memory::map<memory::string, std::array<memory::string, toInt(TimeTokens::Max)>> _timeTokens;
432 : std::array<memory::string, toInt(TimeTokens::Max)> _defaultTime;
433 :
434 : memory::pool_t *_pool;
435 : };
436 :
437 : LocaleManager *LocaleManager::s_sharedInstance = nullptr;
438 :
439 : EventHeader onLocale("Locale", "onLocale");
440 :
441 24 : void define(const StringView &locale, LocaleInitList &&init) {
442 24 : LocaleManager::getInstance()->define(locale, move(init));
443 24 : }
444 :
445 24 : void define(const StringView &locale, LocaleIndexList &&init) {
446 24 : LocaleManager::getInstance()->define(locale, move(init));
447 24 : }
448 :
449 24 : void define(const StringView &locale, const std::array<StringView, toInt(TimeTokens::Max)> &arr) {
450 24 : LocaleManager::getInstance()->define(locale, arr);
451 24 : }
452 :
453 48 : WideStringView string(const WideStringView &str) {
454 48 : return LocaleManager::getInstance()->string(str);
455 : }
456 :
457 24 : WideStringView string(size_t idx) {
458 24 : return LocaleManager::getInstance()->string(idx);
459 : }
460 :
461 108 : WideStringView numeric(const WideStringView &str, uint32_t num) {
462 108 : return LocaleManager::getInstance()->numeric(str, num);
463 : }
464 :
465 12 : void setDefault(const String &def) {
466 12 : LocaleManager::getInstance()->setDefault(def);
467 12 : }
468 12 : const String &getDefault() {
469 12 : return LocaleManager::getInstance()->getDefault();
470 : }
471 :
472 12 : void setLocale(const String &loc) {
473 12 : LocaleManager::getInstance()->setLocale(loc);
474 12 : }
475 12 : const String &getLocale() {
476 12 : return LocaleManager::getInstance()->getLocale();
477 : }
478 :
479 24 : void setNumRule(const String &locale, NumRule &&rule) {
480 24 : LocaleManager::getInstance()->setNumRule(locale, rule);
481 24 : }
482 :
483 8834 : bool hasLocaleTagsFast(const WideStringView &r) {
484 8834 : return LocaleManager::getInstance()->hasLocaleTagsFast(r);
485 : }
486 :
487 1908 : bool hasLocaleTags(const WideStringView &r) {
488 1908 : return LocaleManager::getInstance()->hasLocaleTags(r);
489 : }
490 :
491 889 : WideString resolveLocaleTags(const WideStringView &r) {
492 889 : return LocaleManager::getInstance()->resolveLocaleTags(r);
493 : }
494 :
495 36 : String language(const StringView &locale) {
496 36 : return LocaleManager::getInstance()->language(locale);
497 : }
498 :
499 36 : String common(const StringView &locale) {
500 36 : return LocaleManager::getInstance()->common(locale);
501 : }
502 :
503 48 : StringView timeToken(TimeTokens tok) {
504 48 : return LocaleManager::getInstance()->timeToken(tok);
505 : }
506 :
507 24 : const std::array<memory::string, toInt(TimeTokens::Max)> &timeTokenTable() {
508 24 : return LocaleManager::getInstance()->timeTokenTable();
509 : }
510 :
511 144 : static bool isToday(struct tm &tm, struct tm &now) {
512 144 : return tm.tm_year == now.tm_year && tm.tm_yday == now.tm_yday;
513 : }
514 :
515 200 : static uint32_t getNumDaysInYear(int y) {
516 200 : return ((y & 3) || (((y % 100) == 0) && (((y % 400) != 100)))) ? 355 : 356;
517 : }
518 :
519 200 : static uint32_t getYday(struct tm &now, int y) {
520 200 : auto ndays = getNumDaysInYear(y);
521 200 : return ((now.tm_year == y) ? 0 : ndays) + now.tm_yday;
522 : }
523 :
524 100 : static bool isYesterday(struct tm &tm, struct tm &now) {
525 100 : auto n1 = getYday(tm, now.tm_year);
526 100 : auto n2 = getYday(now, now.tm_year);
527 :
528 100 : return n1 + 1 == n2;
529 : }
530 :
531 288 : static void sp_localtime_r(time_t *sec_now, struct tm *tm_now) {
532 : #if WIN32
533 : localtime_s(tm_now, sec_now);
534 : #else
535 288 : localtime_r(sec_now, tm_now);
536 : #endif
537 288 : }
538 :
539 : template <typename T>
540 144 : static String localDate_impl(const std::array<T, toInt(TimeTokens::Max)> &table, Time t) {
541 144 : auto sec_now = time_t(Time::now().toSeconds());
542 : struct tm tm_now;
543 144 : sp_localtime_r(&sec_now, &tm_now);
544 :
545 144 : auto sec_time = time_t(t.toSeconds());
546 : struct tm tm_time;
547 144 : sp_localtime_r(&sec_time, &tm_time);
548 :
549 144 : if (isToday(tm_time, tm_now)) {
550 88 : return String(table[toInt(TimeTokens::Today)].data(), table[toInt(TimeTokens::Today)].size());
551 100 : } else if (isYesterday(tm_time, tm_now)) {
552 56 : return String(table[toInt(TimeTokens::Yesterday)].data(), table[toInt(TimeTokens::Yesterday)].size());
553 : }
554 72 : if (tm_time.tm_year == tm_now.tm_year) {
555 48 : return toString(tm_time.tm_mday, " ", table[tm_time.tm_mon + 2]);
556 : } else {
557 48 : return toString(tm_time.tm_mday, " ", table[tm_time.tm_mon + 2], " ", 1900 + tm_time.tm_year);
558 : }
559 : }
560 :
561 72 : String localDate(Time t) {
562 72 : return localDate_impl(LocaleManager::getInstance()->timeTokenTable(), t);
563 : }
564 :
565 72 : String localDate(const std::array<StringView, toInt(TimeTokens::Max)> &table, Time t) {
566 72 : return localDate_impl(table, t);
567 : }
568 :
569 : }
|