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) 2015 - ROLI Ltd.
  5. Permission is granted to use this software under the terms of either:
  6. a) the GPL v2 (or any later version)
  7. b) the Affero GPL v3
  8. Details of these licenses can be found at: www.gnu.org/licenses
  9. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  10. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  11. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  12. ------------------------------------------------------------------------------
  13. To release a closed-source product which uses JUCE, commercial licenses are
  14. available: visit www.juce.com for more information.
  15. ==============================================================================
  16. */
  17. #pragma once
  18. struct TranslationHelpers
  19. {
  20. static void addString (StringArray& strings, const String& s)
  21. {
  22. if (s.isNotEmpty() && ! strings.contains (s))
  23. strings.add (s);
  24. }
  25. static void scanFileForTranslations (StringArray& strings, const File& file)
  26. {
  27. const String content (file.loadFileAsString());
  28. String::CharPointerType p (content.getCharPointer());
  29. for (;;)
  30. {
  31. p = CharacterFunctions::find (p, CharPointer_ASCII ("TRANS"));
  32. if (p.isEmpty())
  33. break;
  34. p += 5;
  35. p = p.findEndOfWhitespace();
  36. if (*p == '(')
  37. {
  38. ++p;
  39. MemoryOutputStream text;
  40. parseStringLiteral (p, text);
  41. addString (strings, text.toString());
  42. }
  43. }
  44. }
  45. static void parseStringLiteral (String::CharPointerType& p, MemoryOutputStream& out) noexcept
  46. {
  47. p = p.findEndOfWhitespace();
  48. if (p.getAndAdvance() == '"')
  49. {
  50. String::CharPointerType start (p);
  51. for (;;)
  52. {
  53. juce_wchar c = *p;
  54. if (c == '"')
  55. {
  56. out << String (start, p);
  57. ++p;
  58. parseStringLiteral (p, out);
  59. return;
  60. }
  61. if (c == 0)
  62. break;
  63. if (c == '\\')
  64. {
  65. out << String (start, p);
  66. ++p;
  67. out << String::charToString (readEscapedChar (p));
  68. start = p + 1;
  69. }
  70. ++p;
  71. }
  72. }
  73. }
  74. static juce_wchar readEscapedChar (String::CharPointerType& p)
  75. {
  76. juce_wchar c = *p;
  77. switch (c)
  78. {
  79. case '"':
  80. case '\\':
  81. case '/': break;
  82. case 'b': c = '\b'; break;
  83. case 'f': c = '\f'; break;
  84. case 'n': c = '\n'; break;
  85. case 'r': c = '\r'; break;
  86. case 't': c = '\t'; break;
  87. case 'x':
  88. ++p;
  89. c = 0;
  90. for (int i = 4; --i >= 0;)
  91. {
  92. const int digitValue = CharacterFunctions::getHexDigitValue (*p);
  93. if (digitValue < 0)
  94. break;
  95. ++p;
  96. c = (juce_wchar) ((c << 4) + digitValue);
  97. }
  98. break;
  99. case '0': case '1': case '2': case '3': case '4':
  100. case '5': case '6': case '7': case '8': case '9':
  101. c = 0;
  102. for (int i = 4; --i >= 0;)
  103. {
  104. const int digitValue = *p - '0';
  105. if (digitValue < 0 || digitValue > 7)
  106. break;
  107. ++p;
  108. c = (juce_wchar) ((c << 3) + digitValue);
  109. }
  110. break;
  111. default:
  112. break;
  113. }
  114. return c;
  115. }
  116. static void scanFilesForTranslations (StringArray& strings, const Project::Item& p)
  117. {
  118. if (p.isFile())
  119. {
  120. const File file (p.getFile());
  121. if (file.hasFileExtension (sourceOrHeaderFileExtensions))
  122. scanFileForTranslations (strings, file);
  123. }
  124. for (int i = 0; i < p.getNumChildren(); ++i)
  125. scanFilesForTranslations (strings, p.getChild (i));
  126. }
  127. static void scanFolderForTranslations (StringArray& strings, const File& root)
  128. {
  129. for (DirectoryIterator i (root, true); i.next();)
  130. {
  131. const auto file (i.getFile());
  132. if (file.hasFileExtension (sourceOrHeaderFileExtensions))
  133. scanFileForTranslations(strings, file);
  134. }
  135. }
  136. static void scanProject (StringArray& strings, Project& project)
  137. {
  138. scanFilesForTranslations (strings, project.getMainGroup());
  139. OwnedArray<LibraryModule> modules;
  140. project.getModules().createRequiredModules (modules);
  141. for (int j = 0; j < modules.size(); ++j)
  142. {
  143. const File localFolder (modules.getUnchecked(j)->getFolder());
  144. Array<File> files;
  145. modules.getUnchecked(j)->findBrowseableFiles (localFolder, files);
  146. for (int i = 0; i < files.size(); ++i)
  147. scanFileForTranslations (strings, files.getReference(i));
  148. }
  149. }
  150. static const char* getMungingSeparator() { return "JCTRIDX"; }
  151. static StringArray breakApart (const String& munged)
  152. {
  153. StringArray lines, result;
  154. lines.addLines (munged);
  155. String currentItem;
  156. for (int i = 0; i < lines.size(); ++i)
  157. {
  158. if (lines[i].contains (getMungingSeparator()))
  159. {
  160. if (currentItem.isNotEmpty())
  161. result.add (currentItem);
  162. currentItem = String();
  163. }
  164. else
  165. {
  166. if (currentItem.isNotEmpty())
  167. currentItem << newLine;
  168. currentItem << lines[i];
  169. }
  170. }
  171. if (currentItem.isNotEmpty())
  172. result.add (currentItem);
  173. return result;
  174. }
  175. static StringArray withTrimmedEnds (StringArray array)
  176. {
  177. for (auto& s : array)
  178. s = s.trimEnd().removeCharacters ("\r\n");
  179. return array;
  180. }
  181. static String escapeString (const String& s)
  182. {
  183. return s.replace ("\"", "\\\"")
  184. .replace ("\'", "\\\'")
  185. .replace ("\t", "\\t")
  186. .replace ("\r", "\\r")
  187. .replace ("\n", "\\n");
  188. }
  189. static String getPreTranslationText (Project& project)
  190. {
  191. StringArray strings;
  192. scanProject (strings, project);
  193. return mungeStrings (strings);
  194. }
  195. static String getPreTranslationText (const LocalisedStrings& strings)
  196. {
  197. return mungeStrings (strings.getMappings().getAllKeys());
  198. }
  199. static String mungeStrings (const StringArray& strings)
  200. {
  201. MemoryOutputStream s;
  202. for (int i = 0; i < strings.size(); ++i)
  203. {
  204. s << getMungingSeparator() << i << "." << newLine << strings[i];
  205. if (i < strings.size() - 1)
  206. s << newLine;
  207. }
  208. return s.toString();
  209. }
  210. static String createLine (const String& preString, const String& postString)
  211. {
  212. return "\"" + escapeString (preString)
  213. + "\" = \""
  214. + escapeString (postString) + "\"";
  215. }
  216. static String createFinishedTranslationFile (StringArray preStrings,
  217. StringArray postStrings,
  218. const LocalisedStrings& original)
  219. {
  220. const StringPairArray& originalStrings (original.getMappings());
  221. StringArray lines;
  222. if (originalStrings.size() > 0)
  223. {
  224. lines.add ("language: " + original.getLanguageName());
  225. lines.add ("countries: " + original.getCountryCodes().joinIntoString (" "));
  226. lines.add (String());
  227. const StringArray& originalKeys (originalStrings.getAllKeys());
  228. const StringArray& originalValues (originalStrings.getAllValues());
  229. int numRemoved = 0;
  230. for (int i = preStrings.size(); --i >= 0;)
  231. {
  232. if (originalKeys.contains (preStrings[i]))
  233. {
  234. preStrings.remove (i);
  235. postStrings.remove (i);
  236. ++numRemoved;
  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. };