1 /* This file is part of the Amalthea library.
2  *
3  * Copyright (C) 2018-2022, 2024 Eugene 'Vindex' Stulin
4  *
5  * Distributed under the Boost Software License 1.0 or (at your option)
6  * the GNU Lesser General Public License 3.0 or later.
7  */
8 
9 module amalthea.langlocal;
10 
11 import std.array, std.process;
12 
13 public import amalthea.libcore;
14 import amalthea.csv;
15 
16 
17 static string[][] localeStrings;
18 static string[string] langconformity;
19 static string currentLanguage = "en_US";
20 
21 
22 /*******************************************************************************
23  * Current system language.
24  */
25 string getSystemLanguage() {
26     auto lang = environment.get("LANG", "en_US.UTF-8");
27     return lang.split(".")[0];
28 }
29 
30 
31 /*******************************************************************************
32  * Current application language by langlocal settings.
33  */
34 string getCurrentLanguage() {
35     return currentLanguage;
36 }
37 
38 
39 /*******************************************************************************
40  * Initializes localization by a two-dimensional array of strings.
41  * Titles of columns (first line - index 0) must be locales
42  * in Linux style without encoding (en_US, en_GB, eo, fr_CA, ru_RU, etc.),
43  * first locale is en_US always.
44  *
45  * Params:
46  *     stringsWithLocalizations = Two-dimensional array containing columns
47  *                                of strings in different languages.
48  *     language = New current language (system language by default).
49  *
50  */
51 void initLocalization(
52     in string[][] stringsWithLocalizations,
53     string language = ""
54 ) {
55     localeStrings = cast(string[][])stringsWithLocalizations;
56     if (localeStrings[0][0] != "en_US") {
57         throw new LocalizationsTableException(
58             "Error: array in cell [0][0] must consist en_US"
59         );
60     }
61     if (language == "") {
62         language = getSystemLanguage;
63     }
64     chooseLanguage(language);
65 }
66 
67 
68 /*******************************************************************************
69  * Initializes localization by a CSV file with translations.
70  * Titles of columns (first line - index 0) must be locales in Linux style
71  * without encoding (en_US, en_GB, eo, fr_CA, ru_RU, etc.),
72  * first locale is en_US always.
73  *
74  * Params:
75  *     csvPath = A path to a CSV file with translations.
76  *     language = A new current language (system language by default).
77  *
78  */
79 void initLocalization(string csvPath, string language = "") {
80     auto csv = CSV(csvPath);
81     initLocalization(csv.getTable(), language);
82 }
83 
84 
85 /*******************************************************************************
86  * This function allows to choose current locale for application.
87  */
88 void chooseLanguage(string language) {
89     if (localeStrings.empty) return;
90     foreach(i, locale; localeStrings[0]) {
91         if (language != locale) continue;
92         currentLanguage = language;
93         size_t currentTableColumn = i;
94         foreach(row; localeStrings) {
95             if (row[currentTableColumn] == "") {
96                 langconformity[row[0]] = row[0];
97             } else {
98                 langconformity[row[0]] = row[currentTableColumn];
99             }
100         }
101         return;
102     }
103     currentLanguage = "en_US";
104 }
105 
106 
107 /*******************************************************************************
108  * This function returns string corresponding to the English (en_US) version,
109  * according to the current localization selected.
110  */
111 string s_(string englishString) {
112     return langconformity.get(englishString, englishString);
113 }
114 alias _s = s_;
115 
116 
117 unittest {
118     initLocalization([
119         ["en_US",    "ru_RU",     "eo"],
120         ["Hello",    "Привет",    "Saluton"],
121         ["Orange",   "Апельсин",  "Oranĝo"],
122         ["Language", "Язык",      "Lingvo"],
123         ["Computer", "Компьютер", "Komputilo"]
124     ]);
125 
126     chooseLanguage("ru_RU");
127     assert(s_("Orange") == "Апельсин");
128     assert("Untranslated string"._s == "Untranslated string");
129     assert("Other text" == "Other text".s_);
130 
131     chooseLanguage("eo");
132     assert("Language"._s == "Lingvo");
133     chooseLanguage("Quenya");  // invalid
134     assert(getCurrentLanguage == "en_US");
135 }
136 
137 
138 /*******************************************************************************
139  * Exception for incorrect localization tables.
140  */
141 class LocalizationsTableException : Exception {
142     this(string msg) {
143         super(msg);
144     }
145 }
146