1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-11 23:54:18 +00:00
JUCE/extras/Introjucer/Source/Utility/jucer_TranslationTool.h

407 lines
13 KiB
C++

/*
==============================================================================
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 createFinishedTranslationFile (const StringArray& preStrings,
const StringArray& postStrings)
{
StringArray lines;
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 ("\"" + escapeString (preStrings[i])
+ "\" = \""
+ escapeString (postStrings[i]) + "\"");
return lines.joinIntoString (newLine);
}
};
//==============================================================================
class TranslationToolComponent : public Component,
public ButtonListener
{
public:
TranslationToolComponent()
: 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);
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()
{
Rectangle<int> r (getLocalBounds());
r.removeFromTop (120);
editorPre.setBounds (10, 165, getWidth() - 20, 130);
editorPost.setBounds (10, 338, getWidth() - 20, 114);
editorResult.setBounds (10, 503, getWidth() - 20, getHeight() - 510);
generateButton.setBounds (getWidth() - 152, 462, 140, 30);
label1.setBounds (10, 128, getWidth() - 20, 26);
label2.setBounds (10, 303, getWidth() - 20, 25);
label3.setBounds (10, 459, generateButton.getX() - 20, 38);
instructionsLabel.setBounds (6, 10, getWidth() - 14, 70);
scanButton.setBounds (27, 86, 257, 30);
loadButton.setBounds (304, 86, 260, 30);
}
private:
CodeDocument documentPre, documentPost, documentResult;
CodeEditorComponent editorPre, editorPost, editorResult;
juce::Label label1, label2, label3;
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;
}
documentResult.replaceAllContent (TranslationHelpers::createFinishedTranslationFile (preStrings, postStrings));
}
void loadFile()
{
FileChooser fc ("Choose a translation file to load",
File::nonexistent,
"*");
if (fc.browseForFileToOpen())
setPreTranslationText (TranslationHelpers::getPreTranslationText (LocalisedStrings (fc.getResult(), false)));
}
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__