1 | //************************************ bs::framework - Copyright 2018 Marko Pintera **************************************// |
2 | //*********** Licensed under the MIT license. See LICENSE.md for full terms. This notice is not to be removed. ***********// |
3 | #include "Localization/BsStringTable.h" |
4 | #include "Error/BsException.h" |
5 | #include "Resources/BsResources.h" |
6 | #include "Private/RTTI/BsStringTableRTTI.h" |
7 | |
8 | namespace bs |
9 | { |
10 | const Language StringTable::DEFAULT_LANGUAGE = Language::EnglishUS; |
11 | |
12 | LocalizedStringData::~LocalizedStringData() |
13 | { |
14 | if(parameterOffsets != nullptr) |
15 | bs_deleteN(parameterOffsets, numParameters); |
16 | } |
17 | |
18 | void LocalizedStringData::concatenateString(String& outputString, String* parameters, UINT32 numParameterValues) const |
19 | { |
20 | // A safeguard in case translated strings have different number of parameters |
21 | UINT32 actualNumParameters = std::min(numParameterValues, numParameters); |
22 | |
23 | if(parameters != nullptr) |
24 | { |
25 | UINT32 totalNumChars = 0; |
26 | UINT32 prevIdx = 0; |
27 | for(UINT32 i = 0; i < actualNumParameters; i++) |
28 | { |
29 | totalNumChars += (parameterOffsets[i].location - prevIdx) + (UINT32)parameters[parameterOffsets[i].paramIdx].size();; |
30 | |
31 | prevIdx = parameterOffsets[i].location; |
32 | } |
33 | |
34 | totalNumChars += (UINT32)string.size() - prevIdx; |
35 | |
36 | outputString.resize(totalNumChars); |
37 | char* strData = &outputString[0]; // String contiguity required by C++11, but this should work elsewhere as well |
38 | |
39 | prevIdx = 0; |
40 | for(UINT32 i = 0; i < actualNumParameters; i++) |
41 | { |
42 | UINT32 strSize = parameterOffsets[i].location - prevIdx; |
43 | memcpy(strData, &string[prevIdx], strSize * sizeof(char)); |
44 | strData += strSize; |
45 | |
46 | String& param = parameters[parameterOffsets[i].paramIdx]; |
47 | memcpy(strData, ¶m[0], param.size() * sizeof(char)); |
48 | strData += param.size(); |
49 | |
50 | prevIdx = parameterOffsets[i].location; |
51 | } |
52 | |
53 | memcpy(strData, &string[prevIdx], (string.size() - prevIdx) * sizeof(char)); |
54 | } |
55 | else |
56 | { |
57 | outputString.resize(string.size()); |
58 | char* strData = &outputString[0]; // String contiguity required by C++11, but this should work elsewhere as well |
59 | |
60 | memcpy(strData, &string[0], string.size() * sizeof(char)); |
61 | } |
62 | } |
63 | |
64 | void LocalizedStringData::updateString(const String& _string) |
65 | { |
66 | if(parameterOffsets != nullptr) |
67 | bs_deleteN(parameterOffsets, numParameters); |
68 | |
69 | Vector<ParamOffset> paramOffsets; |
70 | |
71 | INT32 lastBracket = -1; |
72 | StringStream bracketChars; |
73 | StringStream cleanString; |
74 | bool escaped = false; |
75 | UINT32 numRemovedChars = 0; |
76 | for(UINT32 i = 0; i < (UINT32)_string.size(); i++) |
77 | { |
78 | if(_string[i] == '^' && !escaped) |
79 | { |
80 | numRemovedChars++; |
81 | escaped = true; |
82 | continue; |
83 | } |
84 | |
85 | if(lastBracket == -1) |
86 | { |
87 | // If current char is non-escaped opening bracket start parameter definition |
88 | if(_string[i] == '{' && !escaped) |
89 | lastBracket = i; |
90 | else |
91 | cleanString<<_string[i]; |
92 | } |
93 | else |
94 | { |
95 | if(isdigit(_string[i])) |
96 | bracketChars<<_string[i]; |
97 | else |
98 | { |
99 | // If current char is non-escaped closing bracket end parameter definition |
100 | UINT32 numParamChars = (UINT32)bracketChars.tellp(); |
101 | if(_string[i] == '}' && numParamChars > 0 && !escaped) |
102 | { |
103 | numRemovedChars += numParamChars + 2; // +2 for open and closed brackets |
104 | |
105 | UINT32 paramIdx = parseUINT32(bracketChars.str()); |
106 | paramOffsets.push_back(ParamOffset(paramIdx, i + 1 - numRemovedChars)); |
107 | } |
108 | else |
109 | { |
110 | // Last bracket wasn't really a parameter |
111 | for(UINT32 j = lastBracket; j <= i; j++) |
112 | cleanString<<_string[j]; |
113 | } |
114 | |
115 | lastBracket = -1; |
116 | |
117 | bracketChars.str(u8"" ); |
118 | bracketChars.clear(); |
119 | } |
120 | } |
121 | |
122 | escaped = false; |
123 | } |
124 | |
125 | string = cleanString.str(); |
126 | numParameters = (UINT32)paramOffsets.size(); |
127 | |
128 | // Try to find out of order param offsets and fix them |
129 | std::sort(begin(paramOffsets), end(paramOffsets), |
130 | [&] (const ParamOffset& a, const ParamOffset& b) { return a.paramIdx < b.paramIdx; } ); |
131 | |
132 | if(paramOffsets.size() > 0) |
133 | { |
134 | UINT32 sequentialIdx = 0; |
135 | UINT32 lastParamIdx = paramOffsets[0].paramIdx; |
136 | for(UINT32 i = 0; i < numParameters; i++) |
137 | { |
138 | if(paramOffsets[i].paramIdx == lastParamIdx) |
139 | { |
140 | paramOffsets[i].paramIdx = sequentialIdx; |
141 | continue; |
142 | } |
143 | |
144 | lastParamIdx = paramOffsets[i].paramIdx; |
145 | sequentialIdx++; |
146 | |
147 | paramOffsets[i].paramIdx = sequentialIdx; |
148 | } |
149 | } |
150 | |
151 | // Re-sort based on location since we find that more useful at runtime |
152 | std::sort(begin(paramOffsets), end(paramOffsets), |
153 | [&] (const ParamOffset& a, const ParamOffset& b) { return a.location < b.location; } ); |
154 | |
155 | parameterOffsets = bs_newN<ParamOffset>(numParameters); |
156 | for(UINT32 i = 0; i < numParameters; i++) |
157 | parameterOffsets[i] = paramOffsets[i]; |
158 | } |
159 | |
160 | StringTable::StringTable() |
161 | :Resource(false), mActiveLanguageData(nullptr), mDefaultLanguageData(nullptr), mAllLanguages(nullptr) |
162 | { |
163 | mAllLanguages = bs_newN<LanguageData>((UINT32)Language::Count); |
164 | |
165 | mDefaultLanguageData = &(mAllLanguages[(UINT32)DEFAULT_LANGUAGE]); |
166 | mActiveLanguageData = mDefaultLanguageData; |
167 | mActiveLanguage = DEFAULT_LANGUAGE; |
168 | } |
169 | |
170 | StringTable::~StringTable() |
171 | { |
172 | bs_deleteN(mAllLanguages, (UINT32)Language::Count); |
173 | } |
174 | |
175 | void StringTable::setActiveLanguage(Language language) |
176 | { |
177 | if(language == mActiveLanguage) |
178 | return; |
179 | |
180 | mActiveLanguageData = &(mAllLanguages[(UINT32)language]); |
181 | mActiveLanguage = language; |
182 | } |
183 | |
184 | bool StringTable::contains(const String& identifier) |
185 | { |
186 | return mIdentifiers.find(identifier) == mIdentifiers.end(); |
187 | } |
188 | |
189 | Vector<String> StringTable::getIdentifiers() const |
190 | { |
191 | Vector<String> output; |
192 | for (auto& entry : mIdentifiers) |
193 | output.push_back(entry); |
194 | |
195 | return output; |
196 | } |
197 | |
198 | void StringTable::setString(const String& identifier, Language language, const String& value) |
199 | { |
200 | LanguageData* curLanguage = &(mAllLanguages[(UINT32)language]); |
201 | |
202 | auto iterFind = curLanguage->strings.find(identifier); |
203 | |
204 | SPtr<LocalizedStringData> stringData; |
205 | if(iterFind == curLanguage->strings.end()) |
206 | { |
207 | stringData = bs_shared_ptr_new<LocalizedStringData>(); |
208 | curLanguage->strings[identifier] = stringData; |
209 | } |
210 | else |
211 | { |
212 | stringData = iterFind->second; |
213 | } |
214 | |
215 | mIdentifiers.insert(identifier); |
216 | stringData->updateString(value); |
217 | } |
218 | |
219 | String StringTable::getString(const String& identifier, Language language) |
220 | { |
221 | LanguageData* curLanguage = &(mAllLanguages[(UINT32)language]); |
222 | |
223 | auto iterFind = curLanguage->strings.find(identifier); |
224 | if (iterFind != curLanguage->strings.end()) |
225 | return iterFind->second->string; |
226 | |
227 | return identifier; |
228 | } |
229 | |
230 | void StringTable::removeString(const String& identifier) |
231 | { |
232 | for(UINT32 i = 0; i < (UINT32)Language::Count; i++) |
233 | { |
234 | mAllLanguages[i].strings.erase(identifier); |
235 | } |
236 | |
237 | mIdentifiers.erase(identifier); |
238 | } |
239 | |
240 | SPtr<LocalizedStringData> StringTable::getStringData(const String& identifier, bool insertIfNonExisting) |
241 | { |
242 | return getStringData(identifier, mActiveLanguage, insertIfNonExisting); |
243 | } |
244 | |
245 | SPtr<LocalizedStringData> StringTable::getStringData(const String& identifier, Language language, bool insertIfNonExisting) |
246 | { |
247 | LanguageData* curLanguage = &(mAllLanguages[(UINT32)language]); |
248 | |
249 | auto iterFind = curLanguage->strings.find(identifier); |
250 | if(iterFind != curLanguage->strings.end()) |
251 | return iterFind->second; |
252 | |
253 | auto defaultIterFind = mDefaultLanguageData->strings.find(identifier); |
254 | if(defaultIterFind != mDefaultLanguageData->strings.end()) |
255 | return defaultIterFind->second; |
256 | |
257 | if(insertIfNonExisting) |
258 | { |
259 | setString(identifier, DEFAULT_LANGUAGE, identifier); |
260 | |
261 | auto defaultIterFind = mDefaultLanguageData->strings.find(identifier); |
262 | if(defaultIterFind != mDefaultLanguageData->strings.end()) |
263 | return defaultIterFind->second; |
264 | } |
265 | |
266 | BS_EXCEPT(InvalidParametersException, "There is no string data for the provided identifier." ); |
267 | return nullptr; |
268 | } |
269 | |
270 | HStringTable StringTable::create() |
271 | { |
272 | return static_resource_cast<StringTable>(gResources()._createResourceHandle(_createPtr())); |
273 | } |
274 | |
275 | SPtr<StringTable> StringTable::_createPtr() |
276 | { |
277 | SPtr<StringTable> scriptCodePtr = bs_core_ptr<StringTable>( |
278 | new (bs_alloc<StringTable>()) StringTable()); |
279 | scriptCodePtr->_setThisPtr(scriptCodePtr); |
280 | scriptCodePtr->initialize(); |
281 | |
282 | return scriptCodePtr; |
283 | } |
284 | |
285 | RTTITypeBase* StringTable::getRTTIStatic() |
286 | { |
287 | return StringTableRTTI::instance(); |
288 | } |
289 | |
290 | RTTITypeBase* StringTable::getRTTI() const |
291 | { |
292 | return StringTable::getRTTIStatic(); |
293 | } |
294 | } |