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.

348 lines
11KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-11 by Raw Material Software Ltd.
  5. ------------------------------------------------------------------------------
  6. JUCE can be redistributed and/or modified under the terms of the GNU General
  7. Public License (Version 2), as published by the Free Software Foundation.
  8. A copy of the license is included in the JUCE distribution, or can be found
  9. online at www.gnu.org/licenses.
  10. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  11. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  12. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  13. ------------------------------------------------------------------------------
  14. To release a closed-source product which uses JUCE, commercial licenses are
  15. available: visit www.rawmaterialsoftware.com/juce for more information.
  16. ==============================================================================
  17. */
  18. #ifndef __JUCER_TRANSLATIONTOOL_H_E4B7E119__
  19. #define __JUCER_TRANSLATIONTOOL_H_E4B7E119__
  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. const String content (file.loadFileAsString());
  30. String::CharPointerType 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 = p.findEndOfWhitespace();
  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 = p.findEndOfWhitespace();
  50. if (p.getAndAdvance() == '"')
  51. {
  52. String::CharPointerType start (p);
  53. for (;;)
  54. {
  55. juce_wchar c = *p;
  56. if (c == '"')
  57. {
  58. out << String (start, p);
  59. parseStringLiteral (++p, out);
  60. return;
  61. }
  62. if (c == 0)
  63. break;
  64. if (c == '\\')
  65. {
  66. out << String (start, p);
  67. ++p;
  68. out << String::charToString (readEscapedChar (p));
  69. start = p + 1;
  70. }
  71. ++p;
  72. }
  73. }
  74. }
  75. static juce_wchar readEscapedChar (String::CharPointerType& p)
  76. {
  77. juce_wchar c = *p;
  78. switch (c)
  79. {
  80. case '"':
  81. case '\\':
  82. case '/': break;
  83. case 'b': c = '\b'; break;
  84. case 'f': c = '\f'; break;
  85. case 'n': c = '\n'; break;
  86. case 'r': c = '\r'; break;
  87. case 't': c = '\t'; break;
  88. case 'x':
  89. ++p;
  90. c = 0;
  91. for (int i = 4; --i >= 0;)
  92. {
  93. const int digitValue = CharacterFunctions::getHexDigitValue (*p);
  94. if (digitValue < 0)
  95. break;
  96. ++p;
  97. c = (juce_wchar) ((c << 4) + digitValue);
  98. }
  99. break;
  100. case '0': case '1': case '2': case '3': case '4':
  101. case '5': case '6': case '7': case '8': case '9':
  102. c = 0;
  103. for (int i = 4; --i >= 0;)
  104. {
  105. const int digitValue = *p - '0';
  106. if (digitValue < 0 || digitValue > 7)
  107. break;
  108. ++p;
  109. c = (juce_wchar) ((c << 3) + digitValue);
  110. }
  111. break;
  112. default:
  113. break;
  114. }
  115. return c;
  116. }
  117. static void scanFilesForTranslations (StringArray& strings, const Project::Item& p)
  118. {
  119. if (p.isFile())
  120. {
  121. const File file (p.getFile());
  122. if (file.hasFileExtension (sourceOrHeaderFileExtensions))
  123. scanFileForTranslations (strings, file);
  124. }
  125. for (int i = 0; i < p.getNumChildren(); ++i)
  126. scanFilesForTranslations (strings, p.getChild (i));
  127. }
  128. static void scanProject (StringArray& strings, Project& project)
  129. {
  130. scanFilesForTranslations (strings, project.getMainGroup());
  131. const File modulesFolder (ModuleList::getDefaultModulesFolder (&project));
  132. OwnedArray<LibraryModule> modules;
  133. ModuleList moduleList;
  134. moduleList.rescan (modulesFolder);
  135. project.createRequiredModules (moduleList, modules);
  136. for (int j = 0; j < modules.size(); ++j)
  137. {
  138. const File localFolder (modules.getUnchecked(j)->getLocalFolderFor (project));
  139. Array<File> files;
  140. modules.getUnchecked(j)->findBrowseableFiles (localFolder, files);
  141. for (int i = 0; i < files.size(); ++i)
  142. scanFileForTranslations (strings, files.getReference(i));
  143. }
  144. }
  145. static const char* getMungingSeparator() { return "JCTRIDX"; }
  146. static StringArray breakApart (const String& munged)
  147. {
  148. StringArray lines, result;
  149. lines.addLines (munged);
  150. String currentItem;
  151. for (int i = 0; i < lines.size(); ++i)
  152. {
  153. if (lines[i].contains (getMungingSeparator()))
  154. {
  155. if (currentItem.isNotEmpty())
  156. result.add (currentItem);
  157. currentItem = String::empty;
  158. }
  159. else
  160. {
  161. if (currentItem.isNotEmpty())
  162. currentItem << newLine;
  163. currentItem << lines[i];
  164. }
  165. }
  166. return result;
  167. }
  168. static String escapeString (const String& s)
  169. {
  170. return s.replace ("\"", "\\\"")
  171. .replace ("\'", "\\\'")
  172. .replace ("\t", "\\t")
  173. .replace ("\r", "\\r")
  174. .replace ("\n", "\\n");
  175. }
  176. static String getPreTranslationText (Project& project)
  177. {
  178. StringArray strings;
  179. scanProject (strings, project);
  180. MemoryOutputStream s;
  181. for (int i = 0; i < strings.size(); ++i)
  182. {
  183. s << getMungingSeparator() << i << "." << newLine << strings[i];
  184. if (i < strings.size() - 1)
  185. s << newLine;
  186. }
  187. return s.toString();
  188. }
  189. };
  190. //==============================================================================
  191. class TranslationToolComponent : public Component,
  192. public ButtonListener
  193. {
  194. public:
  195. TranslationToolComponent()
  196. : editorPre (documentPre, nullptr),
  197. editorPost (documentPost, nullptr),
  198. editorResult (documentResult, nullptr)
  199. {
  200. setLookAndFeel (&lf);
  201. instructionsLabel.setText ("This tool scans your project for all TRANS() macros, and "
  202. "assembles them into a blob of text that can be automatically "
  203. "translated and disassembled into a JUCE translation file...", dontSendNotification);
  204. addAndMakeVisible (&instructionsLabel);
  205. label1.setText ("Copy-and-paste this text into Google Translate or some other translator:", dontSendNotification);
  206. addAndMakeVisible (&label1);
  207. label2.setText ("...then, take the translated result and paste it into this box:", dontSendNotification);
  208. addAndMakeVisible (&label2);
  209. label3.setText ("Finally: Click the 'Generate' button, and a valid translation file will be created below...", dontSendNotification);
  210. addAndMakeVisible (&label3);
  211. addAndMakeVisible (&editorPre);
  212. addAndMakeVisible (&editorPost);
  213. addAndMakeVisible (&editorResult);
  214. generateButton.setButtonText (TRANS("Generate"));
  215. addAndMakeVisible (&generateButton);
  216. generateButton.addListener (this);
  217. }
  218. void paint (Graphics& g)
  219. {
  220. IntrojucerLookAndFeel::fillWithBackgroundTexture (*this, g);
  221. }
  222. void resized()
  223. {
  224. Rectangle<int> r (getLocalBounds());
  225. r.removeFromTop (120);
  226. editorPre.setBounds (10, 95, getWidth() - 20, 130);
  227. editorPost.setBounds (10, 271, getWidth() - 20, 114);
  228. editorResult.setBounds (10, 470, getWidth() - 20, getHeight() - 480);
  229. generateButton.setBounds (getWidth() - 150, 413, 140, 30);
  230. label1.setBounds (6, 58, getWidth() - 12, 26);
  231. label2.setBounds (6, 238, getWidth() - 12, 25);
  232. label3.setBounds (6, 402, generateButton.getX() - 20, 54);
  233. instructionsLabel.setBounds (6, 10, getWidth() - 12, 44);
  234. }
  235. void initialiseForProject (Project& project)
  236. {
  237. documentPre.replaceAllContent (TranslationHelpers::getPreTranslationText (project));
  238. editorPre.selectAll();
  239. }
  240. private:
  241. CodeDocument documentPre, documentPost, documentResult;
  242. CodeEditorComponent editorPre, editorPost, editorResult;
  243. juce::Label label1, label2, label3;
  244. juce::TextButton generateButton;
  245. juce::Label instructionsLabel;
  246. IntrojucerLookAndFeel lf;
  247. void buttonClicked (Button*)
  248. {
  249. StringArray mappings;
  250. StringArray preStrings (TranslationHelpers::breakApart (documentPre.getAllContent()));
  251. StringArray postStrings (TranslationHelpers::breakApart (documentPost.getAllContent()));
  252. if (postStrings.size() != preStrings.size())
  253. {
  254. AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
  255. TRANS("Error"),
  256. TRANS("The pre- and post-translation text doesn't match!\n\n"
  257. "Perhaps it got mangled by the translator?"));
  258. return;
  259. }
  260. for (int i = 0; i < preStrings.size(); ++i)
  261. mappings.add (TranslationHelpers::escapeString (preStrings[i]).quoted()
  262. + " = "
  263. + TranslationHelpers::escapeString (postStrings[i]).quoted());
  264. documentResult.replaceAllContent (mappings.joinIntoString (newLine));
  265. }
  266. };
  267. #endif // __JUCER_TRANSLATIONTOOL_H_E4B7E119__