The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

325 lines
9.3KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2022 - Raw Material Software Limited
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 7 End-User License
  8. Agreement and JUCE Privacy Policy.
  9. End User License Agreement: www.juce.com/juce-7-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. #pragma once
  19. //==============================================================================
  20. struct TranslationHelpers
  21. {
  22. static void addString (StringArray& strings, const String& s)
  23. {
  24. if (s.isNotEmpty() && ! strings.contains (s))
  25. strings.add (s);
  26. }
  27. static void scanFileForTranslations (StringArray& strings, const File& file)
  28. {
  29. auto content = file.loadFileAsString();
  30. auto p = content.getCharPointer();
  31. for (;;)
  32. {
  33. p = CharacterFunctions::find (p, CharPointer_ASCII ("TRANS"));
  34. if (p.isEmpty())
  35. break;
  36. p += 5;
  37. p.incrementToEndOfWhitespace();
  38. if (*p == '(')
  39. {
  40. ++p;
  41. MemoryOutputStream text;
  42. parseStringLiteral (p, text);
  43. addString (strings, text.toString());
  44. }
  45. }
  46. }
  47. static void parseStringLiteral (String::CharPointerType& p, MemoryOutputStream& out) noexcept
  48. {
  49. p.incrementToEndOfWhitespace();
  50. if (p.getAndAdvance() == '"')
  51. {
  52. auto start = p;
  53. for (;;)
  54. {
  55. auto c = *p;
  56. if (c == '"')
  57. {
  58. out << String (start, p);
  59. ++p;
  60. parseStringLiteral (p, out);
  61. return;
  62. }
  63. if (c == 0)
  64. break;
  65. if (c == '\\')
  66. {
  67. out << String (start, p);
  68. ++p;
  69. out << String::charToString (readEscapedChar (p));
  70. start = p + 1;
  71. }
  72. ++p;
  73. }
  74. }
  75. }
  76. static juce_wchar readEscapedChar (String::CharPointerType& p)
  77. {
  78. auto c = *p;
  79. switch (c)
  80. {
  81. case '"':
  82. case '\\':
  83. case '/': break;
  84. case 'b': c = '\b'; break;
  85. case 'f': c = '\f'; break;
  86. case 'n': c = '\n'; break;
  87. case 'r': c = '\r'; break;
  88. case 't': c = '\t'; break;
  89. case 'x':
  90. ++p;
  91. c = 0;
  92. for (int i = 4; --i >= 0;)
  93. {
  94. const int digitValue = CharacterFunctions::getHexDigitValue (*p);
  95. if (digitValue < 0)
  96. break;
  97. ++p;
  98. c = (c << 4) + (juce_wchar) digitValue;
  99. }
  100. break;
  101. case '0': case '1': case '2': case '3': case '4':
  102. case '5': case '6': case '7': case '8': case '9':
  103. c = 0;
  104. for (int i = 4; --i >= 0;)
  105. {
  106. const auto digitValue = (int) (*p - '0');
  107. if (digitValue < 0 || digitValue > 7)
  108. break;
  109. ++p;
  110. c = (c << 3) + (juce_wchar) digitValue;
  111. }
  112. break;
  113. default:
  114. break;
  115. }
  116. return c;
  117. }
  118. static void scanFilesForTranslations (StringArray& strings, const Project::Item& p)
  119. {
  120. if (p.isFile())
  121. {
  122. const File file (p.getFile());
  123. if (file.hasFileExtension (sourceOrHeaderFileExtensions))
  124. scanFileForTranslations (strings, file);
  125. }
  126. for (int i = 0; i < p.getNumChildren(); ++i)
  127. scanFilesForTranslations (strings, p.getChild (i));
  128. }
  129. static void scanFolderForTranslations (StringArray& strings, const File& root)
  130. {
  131. for (const auto& i : RangedDirectoryIterator (root, true))
  132. {
  133. const auto file = i.getFile();
  134. if (file.hasFileExtension (sourceOrHeaderFileExtensions))
  135. scanFileForTranslations (strings, file);
  136. }
  137. }
  138. static void scanProject (StringArray& strings, Project& project)
  139. {
  140. scanFilesForTranslations (strings, project.getMainGroup());
  141. OwnedArray<LibraryModule> modules;
  142. project.getEnabledModules().createRequiredModules (modules);
  143. for (int j = 0; j < modules.size(); ++j)
  144. {
  145. const File localFolder (modules.getUnchecked (j)->getFolder());
  146. Array<File> files;
  147. modules.getUnchecked (j)->findBrowseableFiles (localFolder, files);
  148. for (int i = 0; i < files.size(); ++i)
  149. scanFileForTranslations (strings, files.getReference (i));
  150. }
  151. }
  152. static const char* getMungingSeparator() { return "JCTRIDX"; }
  153. static StringArray breakApart (const String& munged)
  154. {
  155. StringArray lines, result;
  156. lines.addLines (munged);
  157. String currentItem;
  158. for (int i = 0; i < lines.size(); ++i)
  159. {
  160. if (lines[i].contains (getMungingSeparator()))
  161. {
  162. if (currentItem.isNotEmpty())
  163. result.add (currentItem);
  164. currentItem = String();
  165. }
  166. else
  167. {
  168. if (currentItem.isNotEmpty())
  169. currentItem << newLine;
  170. currentItem << lines[i];
  171. }
  172. }
  173. if (currentItem.isNotEmpty())
  174. result.add (currentItem);
  175. return result;
  176. }
  177. static StringArray withTrimmedEnds (StringArray array)
  178. {
  179. for (auto& s : array)
  180. s = s.trimEnd().removeCharacters ("\r\n");
  181. return array;
  182. }
  183. static String escapeString (const String& s)
  184. {
  185. return s.replace ("\"", "\\\"")
  186. .replace ("\'", "\\\'")
  187. .replace ("\t", "\\t")
  188. .replace ("\r", "\\r")
  189. .replace ("\n", "\\n");
  190. }
  191. static String getPreTranslationText (Project& project)
  192. {
  193. StringArray strings;
  194. scanProject (strings, project);
  195. return mungeStrings (strings);
  196. }
  197. static String getPreTranslationText (const LocalisedStrings& strings)
  198. {
  199. return mungeStrings (strings.getMappings().getAllKeys());
  200. }
  201. static String mungeStrings (const StringArray& strings)
  202. {
  203. MemoryOutputStream s;
  204. for (int i = 0; i < strings.size(); ++i)
  205. {
  206. s << getMungingSeparator() << i << "." << newLine << strings[i];
  207. if (i < strings.size() - 1)
  208. s << newLine;
  209. }
  210. return s.toString();
  211. }
  212. static String createLine (const String& preString, const String& postString)
  213. {
  214. return "\"" + escapeString (preString)
  215. + "\" = \""
  216. + escapeString (postString) + "\"";
  217. }
  218. static String createFinishedTranslationFile (StringArray preStrings,
  219. StringArray postStrings,
  220. const LocalisedStrings& original)
  221. {
  222. const StringPairArray& originalStrings (original.getMappings());
  223. StringArray lines;
  224. if (originalStrings.size() > 0)
  225. {
  226. lines.add ("language: " + original.getLanguageName());
  227. lines.add ("countries: " + original.getCountryCodes().joinIntoString (" "));
  228. lines.add (String());
  229. const StringArray& originalKeys (originalStrings.getAllKeys());
  230. const StringArray& originalValues (originalStrings.getAllValues());
  231. for (int i = preStrings.size(); --i >= 0;)
  232. {
  233. if (originalKeys.contains (preStrings[i]))
  234. {
  235. preStrings.remove (i);
  236. postStrings.remove (i);
  237. }
  238. }
  239. for (int i = 0; i < originalStrings.size(); ++i)
  240. lines.add (createLine (originalKeys[i], originalValues[i]));
  241. }
  242. else
  243. {
  244. lines.add ("language: [enter full name of the language here!]");
  245. lines.add ("countries: [enter list of 2-character country codes here!]");
  246. lines.add (String());
  247. }
  248. for (int i = 0; i < preStrings.size(); ++i)
  249. lines.add (createLine (preStrings[i], postStrings[i]));
  250. return lines.joinIntoString (newLine);
  251. }
  252. };