| 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 | } |