|
24 | 24 | #include <windows.h>
|
25 | 25 | #include <map>
|
26 | 26 |
|
| 27 | +namespace { |
| 28 | + |
| 29 | + // Build a cache key from id + placeholders. |
| 30 | + static std::wstring makeKey(const std::wstring& id, |
| 31 | + const std::vector<std::wstring>& repl) |
| 32 | + { |
| 33 | + // Use a rarely used unit separator as delimiter. |
| 34 | + std::wstring key = id; |
| 35 | + key.push_back(L'\x1F'); |
| 36 | + for (const auto& r : repl) { |
| 37 | + key += r; |
| 38 | + key.push_back(L'\x1F'); |
| 39 | + } |
| 40 | + return key; |
| 41 | + } |
| 42 | + |
| 43 | + // Global cache holder for getLPCW(); single instance per process. |
| 44 | + static std::unordered_map<std::wstring, std::wstring>& lpcwCache() |
| 45 | + { |
| 46 | + static std::unordered_map<std::wstring, std::wstring> cache; |
| 47 | + return cache; |
| 48 | + } |
| 49 | + |
| 50 | +} |
| 51 | + |
27 | 52 | // -----------------------------------------------------------------
|
28 | 53 | // Singleton
|
29 | 54 | // -----------------------------------------------------------------
|
@@ -59,69 +84,99 @@ bool LanguageManager::loadFromIni(const std::wstring& iniFile,
|
59 | 84 | return false;
|
60 | 85 |
|
61 | 86 | const auto& data = _cache.raw();
|
62 |
| - // 2) override with requested language |
63 | 87 | if (auto it = data.find(languageCode); it != data.end())
|
64 | 88 | for (const auto& kv : it->second)
|
65 | 89 | _table[kv.first] = kv.second;
|
66 | 90 |
|
| 91 | + invalidateCaches(); // Clear derived caches after language change |
67 | 92 | return true;
|
68 | 93 | }
|
69 | 94 |
|
| 95 | +void LanguageManager::invalidateCaches() |
| 96 | +{ |
| 97 | + lpcwCache().clear(); |
| 98 | +} |
| 99 | + |
70 | 100 | // -----------------------------------------------------------------
|
71 | 101 | // String getters
|
72 | 102 | // -----------------------------------------------------------------
|
| 103 | +// Replace <br/>, then $REPLACE_n (descending), then $REPLACE. |
73 | 104 | std::wstring LanguageManager::get(const std::wstring& id,
|
74 | 105 | const std::vector<std::wstring>& repl) const
|
75 | 106 | {
|
76 | 107 | auto it = _table.find(id);
|
77 |
| - if (it == _table.end()) return L"Text not found"; |
| 108 | + if (it == _table.end()) |
| 109 | + return id; // developer-friendly fallback: show missing key |
78 | 110 |
|
79 | 111 | std::wstring result = it->second;
|
80 | 112 | const std::wstring base = L"$REPLACE_STRING";
|
81 | 113 |
|
82 |
| - // <br/> → CRLF |
| 114 | + // 1) Replace <br/> with CRLF (all occurrences) |
83 | 115 | for (size_t p = result.find(L"<br/>");
|
84 | 116 | p != std::wstring::npos;
|
85 | 117 | p = result.find(L"<br/>", p))
|
| 118 | + { |
86 | 119 | result.replace(p, 5, L"\r\n");
|
| 120 | + p += 2; // advance beyond inserted CRLF to avoid re-scan at same spot |
| 121 | + } |
| 122 | + |
| 123 | + // 2) Numbered placeholders: $REPLACE_STRING1, $REPLACE_STRING2, ... (highest index first) |
| 124 | + for (size_t i = repl.size(); i > 0; --i) |
| 125 | + { |
| 126 | + const std::wstring ph = base + std::to_wstring(i); |
| 127 | + const std::wstring& val = repl[i - 1]; |
87 | 128 |
|
88 |
| - // numbered placeholders (highest first) |
89 |
| - for (size_t i = repl.size(); i > 0; --i) { |
90 |
| - std::wstring ph = base + std::to_wstring(i); |
91 | 129 | for (size_t p = result.find(ph);
|
92 | 130 | p != std::wstring::npos;
|
93 | 131 | p = result.find(ph, p))
|
94 |
| - result.replace(p, ph.size(), repl[i - 1]); |
| 132 | + { |
| 133 | + result.replace(p, ph.size(), val); |
| 134 | + p += val.size(); // skip over inserted text |
| 135 | + } |
95 | 136 | }
|
96 | 137 |
|
97 |
| - // plain $REPLACE_STRING |
98 |
| - for (size_t p = result.find(base); |
99 |
| - p != std::wstring::npos; |
100 |
| - p = result.find(base, p)) |
101 |
| - result.replace(p, base.size(), |
102 |
| - repl.empty() ? L"" : repl[0]); |
| 138 | + // 3) Plain $REPLACE_STRING -> repl[0] (empty if not provided) |
| 139 | + { |
| 140 | + const std::wstring& val = repl.empty() ? std::wstring() : repl[0]; |
| 141 | + |
| 142 | + for (size_t p = result.find(base); |
| 143 | + p != std::wstring::npos; |
| 144 | + p = result.find(base, p)) |
| 145 | + { |
| 146 | + result.replace(p, base.size(), val); |
| 147 | + p += val.size(); // skip over inserted text |
| 148 | + } |
| 149 | + } |
103 | 150 |
|
104 | 151 | return result;
|
105 | 152 | }
|
106 | 153 |
|
| 154 | + |
107 | 155 | LPCWSTR LanguageManager::getLPCW(const std::wstring& id,
|
108 | 156 | const std::vector<std::wstring>& repl) const
|
109 | 157 | {
|
110 |
| - static std::map<std::wstring, std::wstring> cache; |
111 |
| - auto& ref = cache[id]; |
112 |
| - if (ref.empty()) |
113 |
| - ref = get(id, repl); |
114 |
| - return ref.c_str(); |
| 158 | + // Cache per (id + repl) to avoid wrong reuse for different placeholders. |
| 159 | + auto& cache = lpcwCache(); |
| 160 | + const std::wstring key = makeKey(id, repl); |
| 161 | + |
| 162 | + auto it = cache.find(key); |
| 163 | + if (it == cache.end()) |
| 164 | + it = cache.emplace(key, get(id, repl)).first; |
| 165 | + |
| 166 | + return it->second.c_str(); |
115 | 167 | }
|
116 | 168 |
|
| 169 | + |
117 | 170 | LPWSTR LanguageManager::getLPW(const std::wstring& id,
|
118 | 171 | const std::vector<std::wstring>& repl) const
|
119 | 172 | {
|
120 |
| - static std::wstring buf; |
| 173 | + // Use a thread-local buffer to avoid data races and overwrite issues. |
| 174 | + thread_local std::wstring buf; |
121 | 175 | buf = get(id, repl);
|
122 |
| - return &buf[0]; |
| 176 | + return buf.empty() ? nullptr : &buf[0]; |
123 | 177 | }
|
124 | 178 |
|
| 179 | + |
125 | 180 | // -----------------------------------------------------------------
|
126 | 181 | // Detect active language from Notepad++ nativeLang.xml
|
127 | 182 | // -----------------------------------------------------------------
|
|
0 commit comments