Path: blob/master/editor/translations/editor_translation.cpp
9896 views
/**************************************************************************/1/* editor_translation.cpp */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930#include "editor_translation.h"3132#include "core/io/compression.h"33#include "core/io/file_access_memory.h"34#include "core/io/translation_loader_po.h"35#include "core/string/translation_server.h"36#include "editor/translations/doc_translations.gen.h"37#include "editor/translations/editor_translations.gen.h"38#include "editor/translations/extractable_translations.gen.h"39#include "editor/translations/property_translations.gen.h"4041Vector<String> get_editor_locales() {42Vector<String> locales;4344const EditorTranslationList *etl = _editor_translations;45while (etl->data) {46const String &locale = etl->lang;47locales.push_back(locale);4849etl++;50}5152return locales;53}5455void load_editor_translations(const String &p_locale) {56const Ref<TranslationDomain> domain = TranslationServer::get_singleton()->get_or_add_domain("godot.editor");5758const EditorTranslationList *etl = _editor_translations;59while (etl->data) {60if (etl->lang == p_locale) {61Vector<uint8_t> data;62data.resize(etl->uncomp_size);63const int64_t ret = Compression::decompress(data.ptrw(), etl->uncomp_size, etl->data, etl->comp_size, Compression::MODE_DEFLATE);64ERR_FAIL_COND_MSG(ret == -1, "Compressed file is corrupt.");6566Ref<FileAccessMemory> fa;67fa.instantiate();68fa->open_custom(data.ptr(), data.size());6970Ref<Translation> tr = TranslationLoaderPO::load_translation(fa);7172if (tr.is_valid()) {73tr->set_locale(etl->lang);74domain->add_translation(tr);75break;76}77}7879etl++;80}81}8283void load_property_translations(const String &p_locale) {84const Ref<TranslationDomain> domain = TranslationServer::get_singleton()->get_or_add_domain("godot.properties");8586const PropertyTranslationList *etl = _property_translations;87while (etl->data) {88if (etl->lang == p_locale) {89Vector<uint8_t> data;90data.resize(etl->uncomp_size);91const int64_t ret = Compression::decompress(data.ptrw(), etl->uncomp_size, etl->data, etl->comp_size, Compression::MODE_DEFLATE);92ERR_FAIL_COND_MSG(ret == -1, "Compressed file is corrupt.");9394Ref<FileAccessMemory> fa;95fa.instantiate();96fa->open_custom(data.ptr(), data.size());9798Ref<Translation> tr = TranslationLoaderPO::load_translation(fa);99100if (tr.is_valid()) {101tr->set_locale(etl->lang);102domain->add_translation(tr);103break;104}105}106107etl++;108}109}110111void load_doc_translations(const String &p_locale) {112const Ref<TranslationDomain> domain = TranslationServer::get_singleton()->get_or_add_domain("godot.documentation");113114const DocTranslationList *dtl = _doc_translations;115while (dtl->data) {116if (dtl->lang == p_locale) {117Vector<uint8_t> data;118data.resize(dtl->uncomp_size);119const int64_t ret = Compression::decompress(data.ptrw(), dtl->uncomp_size, dtl->data, dtl->comp_size, Compression::MODE_DEFLATE);120ERR_FAIL_COND_MSG(ret == -1, "Compressed file is corrupt.");121122Ref<FileAccessMemory> fa;123fa.instantiate();124fa->open_custom(data.ptr(), data.size());125126Ref<Translation> tr = TranslationLoaderPO::load_translation(fa);127128if (tr.is_valid()) {129tr->set_locale(dtl->lang);130domain->add_translation(tr);131break;132}133}134135dtl++;136}137}138139void load_extractable_translations(const String &p_locale) {140const Ref<TranslationDomain> domain = TranslationServer::get_singleton()->get_or_add_domain("godot.editor");141142const ExtractableTranslationList *etl = _extractable_translations;143while (etl->data) {144if (etl->lang == p_locale) {145Vector<uint8_t> data;146data.resize(etl->uncomp_size);147const int64_t ret = Compression::decompress(data.ptrw(), etl->uncomp_size, etl->data, etl->comp_size, Compression::MODE_DEFLATE);148ERR_FAIL_COND_MSG(ret == -1, "Compressed file is corrupt.");149150Ref<FileAccessMemory> fa;151fa.instantiate();152fa->open_custom(data.ptr(), data.size());153154Ref<Translation> tr = TranslationLoaderPO::load_translation(fa);155156if (tr.is_valid()) {157tr->set_locale(etl->lang);158domain->add_translation(tr);159break;160}161}162163etl++;164}165}166167Vector<Vector<String>> get_extractable_message_list() {168const ExtractableTranslationList *etl = _extractable_translations;169Vector<Vector<String>> list;170171while (etl->data) {172if (strcmp(etl->lang, "source")) {173etl++;174continue;175}176177Vector<uint8_t> data;178data.resize(etl->uncomp_size);179const int64_t ret = Compression::decompress(data.ptrw(), etl->uncomp_size, etl->data, etl->comp_size, Compression::MODE_DEFLATE);180ERR_FAIL_COND_V_MSG(ret == -1, list, "Compressed file is corrupt.");181182Ref<FileAccessMemory> fa;183fa.instantiate();184fa->open_custom(data.ptr(), data.size());185186// Taken from TranslationLoaderPO, modified to work specifically with POTs.187{188const String path = fa->get_path();189190fa->seek(0);191192enum Status {193STATUS_NONE,194STATUS_READING_ID,195STATUS_READING_STRING,196STATUS_READING_CONTEXT,197STATUS_READING_PLURAL,198};199200Status status = STATUS_NONE;201202String msg_id;203String msg_id_plural;204String msg_context;205206int line = 1;207bool entered_context = false;208bool is_eof = false;209210while (!is_eof) {211String l = fa->get_line().strip_edges();212is_eof = fa->eof_reached();213214// If we reached last line and it's not a content line, break, otherwise let processing that last loop.215if (is_eof && l.is_empty()) {216if (status == STATUS_READING_ID || status == STATUS_READING_CONTEXT || status == STATUS_READING_PLURAL) {217ERR_FAIL_V_MSG(Vector<Vector<String>>(), "Unexpected EOF while reading POT file at: " + path + ":" + itos(line));218} else {219break;220}221}222223if (l.begins_with("msgctxt")) {224ERR_FAIL_COND_V_MSG(status != STATUS_READING_STRING && status != STATUS_READING_PLURAL, Vector<Vector<String>>(),225"Unexpected 'msgctxt', was expecting 'msgid_plural' or 'msgstr' before 'msgctxt' while parsing: " + path + ":" + itos(line));226227// In POT files, "msgctxt" appears before "msgid". If we encounter a "msgctxt", we add what we have read228// and set "entered_context" to true to prevent adding twice.229if (!msg_id.is_empty()) {230Vector<String> msgs;231msgs.push_back(msg_id);232msgs.push_back(msg_context);233msgs.push_back(msg_id_plural);234list.push_back(msgs);235}236msg_context = "";237l = l.substr(7).strip_edges();238status = STATUS_READING_CONTEXT;239entered_context = true;240}241242if (l.begins_with("msgid_plural")) {243if (status != STATUS_READING_ID) {244ERR_FAIL_V_MSG(Vector<Vector<String>>(), "Unexpected 'msgid_plural', was expecting 'msgid' before 'msgid_plural' while parsing: " + path + ":" + itos(line));245}246l = l.substr(12).strip_edges();247status = STATUS_READING_PLURAL;248} else if (l.begins_with("msgid")) {249ERR_FAIL_COND_V_MSG(status == STATUS_READING_ID, Vector<Vector<String>>(), "Unexpected 'msgid', was expecting 'msgstr' while parsing: " + path + ":" + itos(line));250251if (!msg_id.is_empty() && !entered_context) {252Vector<String> msgs;253msgs.push_back(msg_id);254msgs.push_back(msg_context);255msgs.push_back(msg_id_plural);256list.push_back(msgs);257}258259l = l.substr(5).strip_edges();260status = STATUS_READING_ID;261// If we did not encounter msgctxt, we reset context to empty to reset it.262if (!entered_context) {263msg_context = "";264}265msg_id = "";266msg_id_plural = "";267entered_context = false;268}269270if (l.begins_with("msgstr[")) {271ERR_FAIL_COND_V_MSG(status != STATUS_READING_PLURAL, Vector<Vector<String>>(),272"Unexpected 'msgstr[]', was expecting 'msgid_plural' before 'msgstr[]' while parsing: " + path + ":" + itos(line));273l = l.substr(9).strip_edges();274} else if (l.begins_with("msgstr")) {275ERR_FAIL_COND_V_MSG(status != STATUS_READING_ID, Vector<Vector<String>>(),276"Unexpected 'msgstr', was expecting 'msgid' before 'msgstr' while parsing: " + path + ":" + itos(line));277l = l.substr(6).strip_edges();278status = STATUS_READING_STRING;279}280281if (l.is_empty() || l.begins_with("#")) {282line++;283continue; // Nothing to read or comment.284}285286ERR_FAIL_COND_V_MSG(!l.begins_with("\"") || status == STATUS_NONE, Vector<Vector<String>>(), "Invalid line '" + l + "' while parsing: " + path + ":" + itos(line));287288l = l.substr(1);289// Find final quote, ignoring escaped ones (\").290// The escape_next logic is necessary to properly parse things like \\"291// where the backslash is the one being escaped, not the quote.292int end_pos = -1;293bool escape_next = false;294for (int i = 0; i < l.length(); i++) {295if (l[i] == '\\' && !escape_next) {296escape_next = true;297continue;298}299300if (l[i] == '"' && !escape_next) {301end_pos = i;302break;303}304305escape_next = false;306}307308ERR_FAIL_COND_V_MSG(end_pos == -1, Vector<Vector<String>>(), "Expected '\"' at end of message while parsing: " + path + ":" + itos(line));309310l = l.substr(0, end_pos);311l = l.c_unescape();312313if (status == STATUS_READING_ID) {314msg_id += l;315} else if (status == STATUS_READING_CONTEXT) {316msg_context += l;317} else if (status == STATUS_READING_PLURAL) {318msg_id_plural += l;319}320321line++;322}323}324325etl++;326}327328return list;329}330331332