|
- /*
- ==============================================================================
-
- This file is part of the JUCE library.
- Copyright (c) 2013 - Raw Material Software Ltd.
-
- Permission is granted to use this software under the terms of either:
- a) the GPL v2 (or any later version)
- b) the Affero GPL v3
-
- Details of these licenses can be found at: www.gnu.org/licenses
-
- JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- ------------------------------------------------------------------------------
-
- To release a closed-source product which uses JUCE, commercial licenses are
- available: visit www.juce.com for more information.
-
- ==============================================================================
- */
-
- #ifndef __JUCER_TRANSLATIONTOOL_JUCEHEADER__
- #define __JUCER_TRANSLATIONTOOL_JUCEHEADER__
-
-
- struct TranslationHelpers
- {
- static void addString (StringArray& strings, const String& s)
- {
- if (s.isNotEmpty() && ! strings.contains (s))
- strings.add (s);
- }
-
- static void scanFileForTranslations (StringArray& strings, const File& file)
- {
- const String content (file.loadFileAsString());
-
- String::CharPointerType p (content.getCharPointer());
-
- for (;;)
- {
- p = CharacterFunctions::find (p, CharPointer_ASCII ("TRANS"));
-
- if (p.isEmpty())
- break;
-
- p += 5;
- p = p.findEndOfWhitespace();
-
- if (*p == '(')
- {
- ++p;
- MemoryOutputStream text;
- parseStringLiteral (p, text);
-
- addString (strings, text.toString());
- }
- }
- }
-
- static void parseStringLiteral (String::CharPointerType& p, MemoryOutputStream& out) noexcept
- {
- p = p.findEndOfWhitespace();
-
- if (p.getAndAdvance() == '"')
- {
- String::CharPointerType start (p);
-
- for (;;)
- {
- juce_wchar c = *p;
-
- if (c == '"')
- {
- out << String (start, p);
- ++p;
- parseStringLiteral (p, out);
- return;
- }
-
- if (c == 0)
- break;
-
- if (c == '\\')
- {
- out << String (start, p);
- ++p;
- out << String::charToString (readEscapedChar (p));
- start = p + 1;
- }
-
- ++p;
- }
- }
- }
-
- static juce_wchar readEscapedChar (String::CharPointerType& p)
- {
- juce_wchar c = *p;
-
- switch (c)
- {
- case '"':
- case '\\':
- case '/': break;
-
- case 'b': c = '\b'; break;
- case 'f': c = '\f'; break;
- case 'n': c = '\n'; break;
- case 'r': c = '\r'; break;
- case 't': c = '\t'; break;
-
- case 'x':
- ++p;
- c = 0;
-
- for (int i = 4; --i >= 0;)
- {
- const int digitValue = CharacterFunctions::getHexDigitValue (*p);
- if (digitValue < 0)
- break;
-
- ++p;
- c = (juce_wchar) ((c << 4) + digitValue);
- }
-
- break;
-
- case '0': case '1': case '2': case '3': case '4':
- case '5': case '6': case '7': case '8': case '9':
- c = 0;
-
- for (int i = 4; --i >= 0;)
- {
- const int digitValue = *p - '0';
- if (digitValue < 0 || digitValue > 7)
- break;
-
- ++p;
- c = (juce_wchar) ((c << 3) + digitValue);
- }
-
- break;
-
- default:
- break;
- }
-
- return c;
- }
-
- static void scanFilesForTranslations (StringArray& strings, const Project::Item& p)
- {
- if (p.isFile())
- {
- const File file (p.getFile());
-
- if (file.hasFileExtension (sourceOrHeaderFileExtensions))
- scanFileForTranslations (strings, file);
- }
-
- for (int i = 0; i < p.getNumChildren(); ++i)
- scanFilesForTranslations (strings, p.getChild (i));
- }
-
- static void scanProject (StringArray& strings, Project& project)
- {
- scanFilesForTranslations (strings, project.getMainGroup());
-
- OwnedArray<LibraryModule> modules;
- project.getModules().createRequiredModules (modules);
-
- for (int j = 0; j < modules.size(); ++j)
- {
- const File localFolder (modules.getUnchecked(j)->getFolder());
-
- Array<File> files;
- modules.getUnchecked(j)->findBrowseableFiles (localFolder, files);
-
- for (int i = 0; i < files.size(); ++i)
- scanFileForTranslations (strings, files.getReference(i));
- }
- }
-
- static const char* getMungingSeparator() { return "JCTRIDX"; }
-
- static StringArray breakApart (const String& munged)
- {
- StringArray lines, result;
- lines.addLines (munged);
-
- String currentItem;
-
- for (int i = 0; i < lines.size(); ++i)
- {
- if (lines[i].contains (getMungingSeparator()))
- {
- if (currentItem.isNotEmpty())
- result.add (currentItem);
-
- currentItem = String::empty;
- }
- else
- {
- if (currentItem.isNotEmpty())
- currentItem << newLine;
-
- currentItem << lines[i];
- }
- }
-
- if (currentItem.isNotEmpty())
- result.add (currentItem);
-
- return result;
- }
-
- static String escapeString (const String& s)
- {
- return s.replace ("\"", "\\\"")
- .replace ("\'", "\\\'")
- .replace ("\t", "\\t")
- .replace ("\r", "\\r")
- .replace ("\n", "\\n");
- }
-
- static String getPreTranslationText (Project& project)
- {
- StringArray strings;
- scanProject (strings, project);
- return mungeStrings (strings);
- }
-
- static String getPreTranslationText (const LocalisedStrings& strings)
- {
- return mungeStrings (strings.getMappings().getAllKeys());
- }
-
- static String mungeStrings (const StringArray& strings)
- {
- MemoryOutputStream s;
-
- for (int i = 0; i < strings.size(); ++i)
- {
- s << getMungingSeparator() << i << "." << newLine << strings[i];
-
- if (i < strings.size() - 1)
- s << newLine;
- }
-
- return s.toString();
- }
-
- static String createLine (const String& preString, const String& postString)
- {
- return "\"" + escapeString (preString)
- + "\" = \""
- + escapeString (postString) + "\"";
- }
-
- static String createFinishedTranslationFile (StringArray preStrings,
- StringArray postStrings,
- const LocalisedStrings& original)
- {
- const StringPairArray& originalStrings (original.getMappings());
-
- StringArray lines;
-
- if (originalStrings.size() > 0)
- {
- lines.add ("language: " + original.getLanguageName());
- lines.add ("countries: " + original.getCountryCodes().joinIntoString (" "));
- lines.add (String::empty);
-
- const StringArray& originalKeys (originalStrings.getAllKeys());
- const StringArray& originalValues (originalStrings.getAllValues());
- int numRemoved = 0;
-
- for (int i = preStrings.size(); --i >= 0;)
- {
- if (originalKeys.contains (preStrings[i]))
- {
- preStrings.remove (i);
- postStrings.remove (i);
- ++numRemoved;
- }
- }
-
- for (int i = 0; i < originalStrings.size(); ++i)
- lines.add (createLine (originalKeys[i], originalValues[i]));
- }
- else
- {
- lines.add ("language: [enter full name of the language here!]");
- lines.add ("countries: [enter list of 2-character country codes here!]");
- lines.add (String::empty);
- }
-
- for (int i = 0; i < preStrings.size(); ++i)
- lines.add (createLine (preStrings[i], postStrings[i]));
-
- return lines.joinIntoString (newLine);
- }
- };
-
- //==============================================================================
- class TranslationToolComponent : public Component,
- public ButtonListener
- {
- public:
- TranslationToolComponent()
- : editorOriginal (documentOriginal, nullptr),
- editorPre (documentPre, nullptr),
- editorPost (documentPost, nullptr),
- editorResult (documentResult, nullptr)
- {
- setLookAndFeel (&lf);
-
- instructionsLabel.setText (
- "This utility converts translation files to/from a format that can be passed to automatic translation tools."
- "\n\n"
- "First, choose whether to scan the current project for all TRANS() macros, or "
- "pick an existing translation file to load:", dontSendNotification);
- addAndMakeVisible (instructionsLabel);
-
- label1.setText ("..then copy-and-paste this annotated text into Google Translate or some other translator:", dontSendNotification);
- addAndMakeVisible (label1);
-
- label2.setText ("...then, take the translated result and paste it into the box below:", dontSendNotification);
- addAndMakeVisible (label2);
-
- label3.setText ("Finally, click the 'Generate' button, and a translation file will be created below. "
- "Remember to update its language code at the top!", dontSendNotification);
- addAndMakeVisible (label3);
-
- label4.setText ("If you load an existing file the already translated strings will be removed. Ensure this box is empty to create a fresh translation", dontSendNotification);
- addAndMakeVisible (label4);
-
- addAndMakeVisible (editorOriginal);
- addAndMakeVisible (editorPre);
- addAndMakeVisible (editorPost);
- addAndMakeVisible (editorResult);
-
- generateButton.setButtonText (TRANS("Generate"));
- addAndMakeVisible (generateButton);
- scanButton.setButtonText ("Scan Project for TRANS macros");
- addAndMakeVisible (scanButton);
- loadButton.setButtonText ("Load existing translation File...");
- addAndMakeVisible (loadButton);
- generateButton.addListener (this);
-
- scanButton.addListener (this);
- loadButton.addListener (this);
- }
-
- void paint (Graphics& g)
- {
- IntrojucerLookAndFeel::fillWithBackgroundTexture (*this, g);
- }
-
- void resized()
- {
- const int m = 6;
- const int textH = 44;
- const int extraH = (7 * textH);
- const int editorH = (getHeight() - extraH) / 4;
-
- Rectangle<int> r (getLocalBounds().withTrimmedBottom (m));
- instructionsLabel.setBounds (r.removeFromTop (textH * 2).reduced (m));
- r.removeFromTop (m);
- Rectangle<int> r2 (r.removeFromTop (textH - (2 * m)));
- scanButton.setBounds (r2.removeFromLeft (r.getWidth() / 2).reduced (m, 0));
- loadButton.setBounds (r2.reduced (m, 0));
-
- label1.setBounds (r.removeFromTop (textH).reduced (m));
- editorPre.setBounds (r.removeFromTop (editorH).reduced (m, 0));
-
- label2.setBounds (r.removeFromTop (textH).reduced (m));
- editorPost.setBounds (r.removeFromTop (editorH).reduced (m, 0));
-
- r2 = r.removeFromTop (textH);
- generateButton.setBounds (r2.removeFromRight (152).reduced (m));
- label3.setBounds (r2.reduced (m));
- editorResult.setBounds (r.removeFromTop (editorH).reduced (m, 0));
-
- label4.setBounds (r.removeFromTop (textH).reduced (m));
- editorOriginal.setBounds (r.reduced (m, 0));
- }
-
- private:
- CodeDocument documentOriginal, documentPre, documentPost, documentResult;
- CodeEditorComponent editorOriginal, editorPre, editorPost, editorResult;
- juce::Label label1, label2, label3, label4;
- juce::TextButton generateButton;
- juce::Label instructionsLabel;
- juce::TextButton scanButton;
- juce::TextButton loadButton;
-
- IntrojucerLookAndFeel lf;
-
- void buttonClicked (Button* b)
- {
- if (b == &generateButton) generate();
- else if (b == &loadButton) loadFile();
- else if (b == &scanButton) scanProject();
- }
-
- void generate()
- {
- StringArray preStrings (TranslationHelpers::breakApart (documentPre.getAllContent()));
- StringArray postStrings (TranslationHelpers::breakApart (documentPost.getAllContent()));
-
- if (postStrings.size() != preStrings.size())
- {
- AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
- TRANS("Error"),
- TRANS("The pre- and post-translation text doesn't match!\n\n"
- "Perhaps it got mangled by the translator?"));
- return;
- }
-
- const LocalisedStrings originalTranslation (documentOriginal.getAllContent(), false);
- documentResult.replaceAllContent (TranslationHelpers::createFinishedTranslationFile (preStrings, postStrings, originalTranslation));
- }
-
- void loadFile()
- {
- FileChooser fc ("Choose a translation file to load",
- File::nonexistent,
- "*");
-
- if (fc.browseForFileToOpen())
- {
- const LocalisedStrings loadedStrings (fc.getResult(), false);
- documentOriginal.replaceAllContent (fc.getResult().loadFileAsString().trim());
- setPreTranslationText (TranslationHelpers::getPreTranslationText (loadedStrings));
- }
- }
-
- void scanProject()
- {
- if (Project* project = IntrojucerApp::getApp().mainWindowList.getFrontmostProject())
- setPreTranslationText (TranslationHelpers::getPreTranslationText (*project));
- else
- AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, "Translation Tool",
- "This will only work when you have a project open!");
- }
-
- void setPreTranslationText (const String& text)
- {
- documentPre.replaceAllContent (text);
- editorPre.grabKeyboardFocus();
- editorPre.selectAll();
- }
- };
-
-
- #endif // __JUCER_TRANSLATIONTOOL_JUCEHEADER__
|