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.

328 lines
9.3KB

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