/* ============================================================================== This file is part of the JUCE library. Copyright (c) 2020 - Raw Material Software Limited JUCE is an open source library subject to commercial or open-source licensing. By using JUCE, you agree to the terms of both the JUCE 6 End-User License Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). End User License Agreement: www.juce.com/juce-6-licence Privacy Policy: www.juce.com/juce-privacy-policy Or: You may also use this code under the terms of the GPL v3 (see www.gnu.org/licenses). JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE DISCLAIMED. ============================================================================== */ #pragma once //============================================================================== 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) { auto content = file.loadFileAsString(); auto p = content.getCharPointer(); for (;;) { p = CharacterFunctions::find (p, CharPointer_ASCII ("TRANS")); if (p.isEmpty()) break; p += 5; p.incrementToEndOfWhitespace(); if (*p == '(') { ++p; MemoryOutputStream text; parseStringLiteral (p, text); addString (strings, text.toString()); } } } static void parseStringLiteral (String::CharPointerType& p, MemoryOutputStream& out) noexcept { p.incrementToEndOfWhitespace(); if (p.getAndAdvance() == '"') { auto start = p; for (;;) { auto 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) { auto 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 = (c << 4) + (juce_wchar) 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 auto digitValue = (int) (*p - '0'); if (digitValue < 0 || digitValue > 7) break; ++p; c = (c << 3) + (juce_wchar) 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 scanFolderForTranslations (StringArray& strings, const File& root) { for (const auto& i : RangedDirectoryIterator (root, true)) { const auto file = i.getFile(); if (file.hasFileExtension (sourceOrHeaderFileExtensions)) scanFileForTranslations(strings, file); } } static void scanProject (StringArray& strings, Project& project) { scanFilesForTranslations (strings, project.getMainGroup()); OwnedArray modules; project.getEnabledModules().createRequiredModules (modules); for (int j = 0; j < modules.size(); ++j) { const File localFolder (modules.getUnchecked(j)->getFolder()); Array 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(); } else { if (currentItem.isNotEmpty()) currentItem << newLine; currentItem << lines[i]; } } if (currentItem.isNotEmpty()) result.add (currentItem); return result; } static StringArray withTrimmedEnds (StringArray array) { for (auto& s : array) s = s.trimEnd().removeCharacters ("\r\n"); return array; } 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()); 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()); } for (int i = 0; i < preStrings.size(); ++i) lines.add (createLine (preStrings[i], postStrings[i])); return lines.joinIntoString (newLine); } };