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
8namespace 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, &param[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}