NDEVR
API Documentation
TranslationTools.h
1#pragma once
2#include <NDEVR/Buffer.h>
3#include <NDEVR/String.h>
4#include <NDEVR/File.h>
5#include <NDEVR/Scanner.h>
6#include <NDEVR/INIFactory.h>
7#include <NDEVR/ApplicationResource.h>
8#include <NDEVR/Translator.h>
9namespace NDEVR
10{
14 {
15 public:
16 File translation_folder = File("$(NDEVR_SOURCE_DIR)\\Resources\\Translations\\");
17 Buffer<String> source_roots = { String("$(NDEVR_SOURCE_DIR)"), String("$(NDEVR_SOURCE_DIR)\\..\\Carlson\\") };
18 Buffer<String> extensions = { String(".h"), String(".hpp"), String(".cpp") };
19 Buffer<StringView> simple_tr = { "_t(\"" ,"_tq(\"","_tqa(\"","_tqs(\"", "_to(\"", "_ti(\"" };
20 Buffer<StringView> complex_tr = { "_td(\"" ,"_tdq(\"", "_tid(\"" };
21 Buffer<StringView> complex_utf8_tr = { "_td(\"" ,"_tdq(\"","_tqo(\"" };
22 Buffer<StringView> qt_tr = { "QApplication::translate(\"", "QCoreApplication::translate(\"" };
23 StringView utf8 = "utf8(\"";
27 {
28 s.replace("\\\\", "\\");
29 s.replace("\\\"", "\"");
30 s.replace("\\n", "\n");
31 s.replace("\\", "\n");
32 s.replace("\\t", "\t");
33 s.replace("\\r", "\r");
34 }
35
40 void processString(String& s, Scanner& scan, uint04 start_index, const char* end_seq)
41 {
42 bool is_utf8 = s.beginsWith(utf8);
43 if (is_utf8)
44 {
45 s = s.substr(utf8.size());
46 start_index = 0;
47 }
48 uint04 end_index = s.indexOf(end_seq, '\\', false, start_index);
49 if (s.size() > 0)
50 {
51 s = s.substr(start_index, end_index);
53 }
54 while (IsInvalid(end_index) && scan.nextLine())
55 {
56 s.trimWhiteSpace();// remove all whitespace
57 if (s.size() > 0 && s.last() == '"')
58 s.removeLast();//remove end "
59 String s2 = scan.currentLine();
60 s2.trimWhiteSpace();
61 if (s2.size() != 0)
62 {
63 end_index = s2.indexOf(end_seq, false, start_index);
64 s += s2.substr(1, end_index);
65 }
66 }
67 lib_assert(IsValid(end_index), "Unexpected end of file found");
68 }
69
73 void addOption(INIFactory& translator, const String& s_label, const String& value)
74 {
76
77#ifdef _DEBUG
78 if (translator.hasOption(label))
79 {
80 String old_option = translator.getOption(label);
81 lib_assert(old_option == value, "duplicate translation with differing definition");
82 }
83 else
84#endif
85 {
86 translator.addManagedOption(label, value);
87 }
88 }
89
92 void setupFile(INIFactory& translator, File file)
93 {
94 String translation;
95 Scanner scan(file);
96 while (scan.nextLine())
97 {
98 String s = scan.currentLine();
99 for (uint04 i = 0; i < simple_tr.size(); i++)
100 {
101 uint04 start_index = s.indexOf(simple_tr[i]);
102 if (IsValid(start_index))
103 {
104 processString(s, scan, start_index + simple_tr[i].size(), "\")");
105 addOption(translator, s, s);
106
107 }
108 }
109 for (uint04 i = 0; i < complex_tr.size(); i++)
110 {
111 uint04 start_index = s.indexOf(complex_tr[i]);
112 if (IsValid(start_index))
113 {
114 uint04 end_index = s.indexOf("\"", false, start_index);
115
116 StringView label = s.substr(start_index + complex_tr[i].size(), end_index);
117 Buffer<String> parts = StringView::Convert(label.split(','));
118 parts[0].trimWhiteSpace();
119 parts[1].trimWhiteSpace();
120 if (parts.size() > 2)
121 {
122 while (!parts[0].contains('\"', 1))
123 {
124 parts[0] += "," + parts[1];
125 parts.removeIndex(1);
126 }
127 while (!parts[1].contains('\"', 1))
128 {
129 parts[1] += "," + parts[2];
130 parts.removeIndex(2);
131 }
132 }
133 parts[0].trimWhiteSpace();
134 parts[0].removeLast();
135 parts[1].trimWhiteSpace();
136 processString(parts[1], scan, 1, "\")");
137 addOption(translator, parts[0], parts[1]);
138 }
139 }
140 for (uint04 i = 0; i < qt_tr.size(); i++)
141 {
142 uint04 start_index = s.indexOf(qt_tr[i]);
143 if (IsValid(start_index))
144 {
145 uint04 end_index = s.indexOf("\", nullptr", false, start_index);
146
147 StringView label = s.substr(start_index + qt_tr[i].size(), end_index);
148 Buffer<String> parts = StringView::Convert(label.split(','));
149 parts[0].trimWhiteSpace();
150 parts[1].trimWhiteSpace();
151 if (parts.size() > 2)
152 {
153 while (!parts[0].contains('\"', 1))
154 {
155 parts[0] += "," + parts[1];
156 parts.removeIndex(1);
157 }
158 while (parts[1].size() > 1 && !parts[1].contains('\"', 1) && parts.size() > 2)
159 {
160 parts[1] += "," + parts[2];
161 parts.removeIndex(2);
162 }
163 }
164 parts[0].trimWhiteSpace();
165 parts[0].removeLast();
166 parts[1].trimWhiteSpace();
167 replaceStringVars(parts[0]);
168 parts[1].trimWhiteSpace();
169 parts[1].removeIndex(0);
170 if (!parts[1].isSameNoCase("Form"))
171 {
172 replaceStringVars(parts[1]);
173 addOption(translator, Translator::DefaultReadableLabel(parts[0]) + "_" + Translator::DefaultReadableLabel(parts[1]), parts[1]);
174 }
175 }
176 }
177 }
178 }
179
182 void getFiles(Buffer<File>& files, const File& file)
183 {
184 if (file.isDirectory())
185 {
186 Buffer<File> children = file.getChildren();
187 for (const File& f : children)
188 {
189 getFiles(files, f);
190 }
191 }
192 else
193 {
194 if (extensions.contains(file.getPath(File::e_file_extension)))
195 {
196 files.add(file);
197 }
198 }
199 }
200
203 static void getDirs(Buffer<File>& files, const File& file)
204 {
205 if (file.isDirectory())
206 {
207 if (file.getFolderName() == "Resources" || file.getFolderName() == "Bin" || file.getFolderName() == "libraries" || file.getFolderName() == "ThirdParty")
208 return;
209 files.add(file);
210 Buffer<File> children = file.getChildren();
211 for (const File& f : children)
212 {
213 getDirs(files, f);
214 }
215 }
216 }
217
219 static void convertBrackets(INIFactory& ini)
220 {
221 for (auto iter = ini.extraOptionsRef().begin(); iter != ini.extraOptionsRef().end(); iter++)
222 {
223 uint04 start = iter.value()->indexOf('[');
224 uint04 end = iter.value()->indexOf(']') + 1;
225 while (IsValid(start) && IsValid(end) && start < end)
226 {
227 StringView substring = iter.value()->substr(start, end);
228 iter.value()->replace(substring, "{" + String(substring.hash()) + "}");
229 start = iter.value()->indexOf('[');
230 end = iter.value()->indexOf(']') + 1;
231 }
232 }
233 }
234
237 static void convertFromBrackets(INIFactory& ini, const INIFactory& ref)
238 {
241 for (auto iter = dictionary.begin(); iter != dictionary.end(); iter++)
242 {
243 if (!reference.contains(iter.key()))
244 {
245 continue;
246 }
247 String other_val = reference.get(iter.key());
248 uint04 o_start = other_val.indexOf('[');
249 uint04 o_end = other_val.indexOf(']') + 1;
250
251
252 while (IsValid(o_start) && IsValid(o_end) && o_start < o_end)
253 {
254 StringView search_string = other_val.substr(o_start, o_end);
255 String replace_substring = "{" + String(search_string.hash()) + "}";
256 iter.value()->replace(replace_substring, search_string);
257 other_val.replace(search_string, replace_substring);
258 o_start = other_val.indexOf('[');
259 o_end = other_val.indexOf(']') + 1;
260 }
261 }
262 }
263
266 {
267 ApplicationResource::application_name.set(_t("Translator"));
268 Buffer<File> roots;
269 for (const String& dir : source_roots)
270 {
271 File file(dir);
272 file.expandEnvironmentalVars();
273 getDirs(roots, file);
274 }
275
276 Buffer<File> files;
277 for (File dir : roots)
278 {
279 if (dir.endsWith("\\Headers\\"))
280 getFiles(files, dir);
281 if (dir.endsWith("\\Source\\"))
282 getFiles(files, dir);
283 if (dir.endsWith("\\Generated\\"))
284 getFiles(files, dir);
285 }
286 File master(String(translation_folder) + "en_US.tr");
287 File hash_master(String(translation_folder) + "en_US.hash");
288 INIFactory root_ini;
289 root_ini.setPreserveOrder(true);
290 for (const File& f : files)
291 {
292 setupFile(root_ini, f);
293 }
294 root_ini.writeToAsciiFile(master);
295
296
297 Buffer<File> other_translations = translation_folder.getChildren();
298 for (uint04 i = 0; i < other_translations.size(); i++)
299 {
300 if (other_translations[i].getPath(File::e_file_extension).isSameNoCase(".tr")
301 || other_translations[i].getPath(File::e_file_extension).isSameNoCase(".hash"))
302 {
303 if (other_translations[i] != master && other_translations[i] != hash_master)
304 {
305 INIFactory ini;
306 ini.setPreserveOrder(true);
307 ini.readAsciiFile(master);
308 if (other_translations[i].endsWith(".hash"))
309 ini.setUseHashLabels(true);
310 ini.readAsciiFile(other_translations[i]);
311 if (other_translations[i].endsWith(".hash"))
312 {
313 ini.setUseHashLabels(false);
314 convertFromBrackets(ini, root_ini);
315 File f = other_translations[i];
317 if (f.exists())
318 ini.readAsciiFile(f);
319 ini.writeToAsciiFile(f);
320 }
321 else
322 {
323 ini.writeToAsciiFile(other_translations[i]);
324 }
325 }
326 }
327 }
328 convertBrackets(root_ini);
329 root_ini.setUseHashLabels(true);
330 root_ini.writeToAsciiFile(hash_master);//write hash translation for easy web translation
331 }
332 };
333}
static Resource< TranslatedString > application_name
The display name of the application.
The equivelent of std::vector but with a bit more control.
Definition Buffer.hpp:58
void add(t_type &&object)
Adds object to the end of the buffer.
Definition Buffer.hpp:190
A hash-based key-value store, useful for quick associative lookups.
Definition Dictionary.h:64
const t_value & get(const t_key_type &key) const
Retrieves a const reference to the value associated with the given key.
Definition Dictionary.h:124
Logic for reading or writing to a file as well as navigating filesystems or other common file operati...
Definition File.h:53
@ e_file_extension
The file extension (e.g., ".txt").
Definition File.h:78
bool exists() const
Checks whether this file or directory exists on disk.
void setPath(const StringView &path, uint01 part)
Sets a specific part of the file path.
Contains methods for easily reading and writing to an INI file including efficient casting,...
Definition INIReader.h:107
void writeToAsciiFile(File &file, bool include_end_comment=false)
Writes all registered options to an ASCII-formatted INI file.
void setPreserveOrder(bool preserve_order)
Sets whether to preserve the order in which options appear in the file.
Definition INIReader.h:260
void setUseHashLabels(bool use_hash_labels)
Enables or disables using hashed labels for option lookup.
void readAsciiFile(File &file)
Reads all options from an ASCII-formatted INI file.
Dictionary< String, String * > & extraOptionsRef()
Returns a mutable reference to the extra options dictionary.
Definition INIReader.h:305
bool hasOption(const StringView &option) const
Checks whether an option with the given label has been registered.
Dictionary< String, String > extraOptions() const
Returns a copy of all extra (unregistered) options as key-value pairs.
bool addManagedOption(const StringView &option_label, const StringView &option, bool replace=true)
Adds or updates a managed string option that is stored internally by the factory.
String getOption(const StringView &option, const StringView &default_value=StringView()) const
Retrieves the string value of a registered or extra option by label.
Contains methods for easily reading objects in an ascii stream using set deliminators and line logic.
Definition Scanner.h:47
const String & currentLine() const
Returns a const reference to the current line being parsed.
Definition Scanner.h:108
virtual bool nextLine()
Advances the scanner to the next line in the stream.
The core String View class for the NDEVR API.
Definition StringView.h:58
StringView substr(uint04 start) const
Creates a substring from a given start position, to the end of the string.
static StringViewBuffer Convert(const StringBuffer &strings)
Converts a StringBuffer into a StringViewBuffer.
uint04 indexOf(const StringView &sub_string, bool ignore_case=false, uint04 start_index=0) const
Given a substring specified by the input, returns the first index of that string, if it exists.
constexpr uint08 hash() const
Creates a simple, quick hash of the object.
Definition StringView.h:330
StringViewBuffer split(char delimiter, bool preserve_empty=true) const
Given a delimiter, breaks the string into subsections, returning an array of each subsection.
The core String class for the NDEVR API.
Definition String.h:95
StringView substr(uint04 start) const
Creates a substring from a given start position, to the end of the string.
uint04 indexOf(const StringView &sub_string, bool ignore_case=false, uint04 start_index=0) const
Given a substring specified by the input, returns the first index of that string, if it exists.
String & trimWhiteSpace()
Trims any white space (tabs, spaces, etc) from the beginning and end of the string.
bool beginsWith(const StringView &s, bool ignore_case=false) const
Tests if this String starts with the specified prefix.
String & replace(const StringView &sub_string, const StringView &replace_sub_string, bool ignore_case=false, uint04 start_index=0)
Replaces ALL instances of a given substring with the provided replacement.
Scans source code for translation macros (_t, _td, etc.) and generates .tr translation files for all ...
File translation_folder
Directory containing translation files.
void processString(String &s, Scanner &scan, uint04 start_index, const char *end_seq)
Extracts a translation string from source code, handling multi-line strings.
StringView utf8
UTF-8 string literal prefix pattern.
Buffer< String > extensions
File extensions to scan.
static void getDirs(Buffer< File > &files, const File &file)
Recursively collects directories, excluding known non-source directories.
void setupFile(INIFactory &translator, File file)
Scans a single source file for translation macros and adds entries to the translator.
void getFiles(Buffer< File > &files, const File &file)
Recursively collects source files with matching extensions.
Buffer< StringView > complex_utf8_tr
Complex UTF-8 translation macro patterns.
Buffer< String > source_roots
Root directories to scan for source files.
void replaceStringVars(String &s)
Replaces escape sequences in a string with their actual characters.
void makeTranslation()
Performs the full translation generation process: scans all source files, generates the master Englis...
Buffer< StringView > complex_tr
Complex translation macro patterns with label-value pairs.
void addOption(INIFactory &translator, const String &s_label, const String &value)
Adds a translation entry to the INI factory, checking for duplicates in debug builds.
Buffer< StringView > simple_tr
Simple translation macro patterns.
Buffer< StringView > qt_tr
Qt translation function patterns.
static void convertFromBrackets(INIFactory &ini, const INIFactory &ref)
Converts hash-based placeholders back to square bracket sequences using a reference.
static void convertBrackets(INIFactory &ini)
Converts square bracket sequences to hash-based placeholders for safe INI storage.
static String DefaultReadableLabel(const StringView &text_string)
Generates a default human-readable label from a text string.
The primary namespace for the NDEVR SDK.
static constexpr bool IsValid(const Angle< t_type > &value)
Checks whether the given Angle holds a valid value.
Definition Angle.h:398
uint32_t uint04
-Defines an alias representing a 4 byte, unsigned integer -Can represent exact integer values 0 throu...
static constexpr bool IsInvalid(const Angle< t_type > &value)
Checks whether the given Angle holds an invalid value.
Definition Angle.h:388
@ file
The source file path associated with this object.