diff --git a/src/catalog/ts/tsstorage.cpp b/src/catalog/ts/tsstorage.cpp index 5140b15..ff8c590 100644 --- a/src/catalog/ts/tsstorage.cpp +++ b/src/catalog/ts/tsstorage.cpp @@ -1,554 +1,605 @@ /* Copyright 2008-2014 Nick Shaforostoff 2018-2019 by Simon Depiets This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "tsstorage.h" #include "lokalize_debug.h" #include "gettextheader.h" #include "project.h" #include "version.h" #include "prefs_lokalize.h" #include #include #include #include #include #include #include #include #include #ifdef Q_OS_WIN #define U QLatin1String #else #define U QStringLiteral #endif //static const char* const noyes[]={"no","yes"}; static const QString names[] = {U("source"), U("translation"), U("oldsource"), U("translatorcomment"), U("comment"), U("name"), U("numerus")}; enum TagNames {SourceTag, TargetTag, OldSourceTag, NoteTag, DevNoteTag, NameTag, PluralTag}; static const QString attrnames[] = {U("location"), U("type"), U("obsolete")}; enum AttrNames {LocationAttr, TypeAttr, ObsoleteAttr}; static const QString attrvalues[] = {U("obsolete"), U("vanished")}; enum AttValues {ObsoleteVal, VanishedVal}; TsStorage::TsStorage() : CatalogStorage() { } +QString TsStorage::protect(const QString & str) const +{ + QString p = str; + p.replace(QLatin1Char('\"'), QLatin1String(""")); + p.replace(QLatin1Char('>'), QLatin1String(">")); + p.replace(QLatin1Char('<'), QLatin1String("<")); + p.replace(QLatin1Char('\''), QLatin1String("'")); + return p; +} int TsStorage::capabilities() const { return 0;//MultipleNotes; } //BEGIN OPEN/SAVE int TsStorage::load(QIODevice* device) { QElapsedTimer chrono; chrono.start(); - QXmlSimpleReader reader; reader.setFeature(QStringLiteral("http://qt-project.org/xml/features/report-whitespace-only-CharData"), true); reader.setFeature(QStringLiteral("http://xml.org/sax/features/namespaces"), false); QXmlInputSource source(device); QString errorMsg; int errorLine;//+errorColumn; bool success = m_doc.setContent(&source, &reader, &errorMsg, &errorLine/*,errorColumn*/); if (!success) { qCWarning(LOKALIZE_LOG) << "parse error" << errorMsg << errorLine; return errorLine + 1; } QDomElement file = m_doc.elementsByTagName(QStringLiteral("TS")).at(0).toElement(); m_sourceLangCode = file.attribute(QStringLiteral("sourcelanguage")); m_targetLangCode = file.attribute(QStringLiteral("language")); m_numberOfPluralForms = numberOfPluralFormsForLangCode(m_targetLangCode); //Create entry mapping. //Along the way: for langs with more than 2 forms //we create any form-entries additionally needed entries = m_doc.elementsByTagName(QStringLiteral("message")); + if (!Settings::self()->convertXMLChars()) { + for (int pos = 0; pos < entries.size(); pos++) { + QDomElement refNode = entries.at(pos).firstChildElement(names[SourceTag]).toElement(); + QDomNode firstRef = refNode.firstChild(); + refNode.appendChild(m_doc.createCDATASection(protect(refNode.text()))); + refNode.removeChild(firstRef); + QDomElement targetEl = unitForPos(pos).firstChildElement(names[TargetTag]).toElement(); + if (!targetEl.isNull()) { + QDomNode firstTarget = targetEl.firstChild(); + targetEl.appendChild(m_doc.createCDATASection(protect(targetEl.text()))); + targetEl.removeChild(firstTarget); + } + + QDomElement noteEl = unitForPos(pos).firstChildElement(names[NoteTag]).toElement(); + if (!noteEl.isNull()) { + QDomNode firstNote = noteEl.firstChild(); + qCWarning(LOKALIZE_LOG) << noteEl.text() << " is note EL"; + noteEl.appendChild(m_doc.createCDATASection(protect(noteEl.text()))); + noteEl.removeChild(firstNote); + } + } + } qCWarning(LOKALIZE_LOG) << chrono.elapsed() << "secs, " << entries.size() << "entries"; return 0; } bool TsStorage::save(QIODevice* device, bool belongsToProject) { Q_UNUSED(belongsToProject) QTextStream stream(device); - m_doc.save(stream, 4); + if (Settings::self()->convertXMLChars()) { + m_doc.save(stream, 4); + } else { + QString tempString; + QTextStream tempStream(&tempString); + + m_doc.save(tempStream, 4); + stream << tempString.replace(QStringLiteral("")) + .replace(QStringLiteral("")) + .replace(QStringLiteral("")) + .replace(QStringLiteral("]]>"), QStringLiteral("")) + .replace(QStringLiteral("]]>"), QStringLiteral("")) + .replace(QStringLiteral("]]>"), QStringLiteral("")); + } return true; } //END OPEN/SAVE //BEGIN STORAGE TRANSLATION int TsStorage::size() const { - //return m_map.size(); - return entries.size(); } - - - /** * helper structure used during XLIFF XML walk-through */ struct TsContentEditingData { enum ActionType {Get, DeleteText, InsertText, CheckLength}; QString stringToInsert; int pos; int lengthOfStringToRemove; ActionType actionType; ///Get TsContentEditingData(ActionType type = Get) : pos(-1) , lengthOfStringToRemove(-1) , actionType(type) {} ///DeleteText TsContentEditingData(int p, int l) : pos(p) , lengthOfStringToRemove(l) , actionType(DeleteText) {} ///InsertText TsContentEditingData(int p, const QString& s) : stringToInsert(s) , pos(p) , lengthOfStringToRemove(-1) , actionType(InsertText) {} }; static QString doContent(QDomElement elem, int startingPos, TsContentEditingData* data); /** * walks through XLIFF XML and performs actions depending on TsContentEditingData: * - reads content * - deletes content, or * - inserts content */ static QString content(QDomElement elem, TsContentEditingData* data = 0) { return doContent(elem, 0, data); } static QString doContent(QDomElement elem, int startingPos, TsContentEditingData* data) { //actually startingPos is current pos QString result; if (elem.isNull() || (!result.isEmpty() && data && data->actionType == TsContentEditingData::CheckLength)) return QString(); - bool seenCharacterDataAfterElement = false; + bool seenCDataAfterElement = false; QDomNode n = elem.firstChild(); while (!n.isNull()) { - if (n.isCharacterData()) { - seenCharacterDataAfterElement = true; + if (Settings::self()->convertXMLChars() ? n.isCharacterData() : n.isCDATASection()) { + seenCDataAfterElement = true; - QDomCharacterData c = n.toCharacterData(); + QDomCharacterData c = Settings::self()->convertXMLChars() ? n.toCharacterData() : n.toCDATASection(); QString cData = c.data(); - if (data && data->pos != -1 && data->pos >= startingPos && data->pos <= startingPos + cData.size()) { // time to do some action! ;) int localStartPos = data->pos - startingPos; //BEGIN DELETE TEXT if (data->actionType == TsContentEditingData::DeleteText) { //(data->lengthOfStringToRemove!=-1) if (localStartPos + data->lengthOfStringToRemove > cData.size()) { - //text is fragmented into several QDomCharacterData + //text is fragmented into several QDomCData int localDelLen = cData.size() - localStartPos; - //qCWarning(LOKALIZE_LOG)<<"text is fragmented into several QDomCharacterData. localDelLen:"<lengthOfStringToRemove = data->lengthOfStringToRemove - localDelLen; //data->pos=startingPos; //qCWarning(LOKALIZE_LOG)<<"\tsetup:"<pos<lengthOfStringToRemove; } else { //qCWarning(LOKALIZE_LOG)<<"simple delete"<lengthOfStringToRemove; c.deleteData(localStartPos, data->lengthOfStringToRemove); data->actionType = TsContentEditingData::CheckLength; return QString('a');//so it exits 100% } } //END DELETE TEXT //INSERT else if (data->actionType == TsContentEditingData::InsertText) { c.insertData(localStartPos, data->stringToInsert); data->actionType = TsContentEditingData::CheckLength; return QString('a');//so it exits 100% } cData = c.data(); } - //else - // if (data&&data->pos!=-1/*&& n.nextSibling().isNull()*/) - // qCWarning(LOKALIZE_LOG)<<"arg!"<pos"<pos; result += cData; startingPos += cData.size(); } n = n.nextSibling(); } - if (!seenCharacterDataAfterElement) { - //add empty charData child so that user could add some text - elem.appendChild(elem.ownerDocument().createTextNode(QString())); + if (!seenCDataAfterElement) { + //add empty cDATA child so that user could add some text + elem.appendChild(Settings::self()->convertXMLChars() ? + elem.ownerDocument().createTextNode(QString()) : + elem.ownerDocument().createCDATASection(QString()) + ); } return result; } //flat-model interface (ignores XLIFF grouping) CatalogString TsStorage::catalogString(QDomElement contentElement) const { CatalogString catalogString; TsContentEditingData data(TsContentEditingData::Get); catalogString.string = content(contentElement, &data); return catalogString; } CatalogString TsStorage::catalogString(const DocPosition& pos) const { return catalogString(pos.part == DocPosition::Target ? targetForPos(pos) : sourceForPos(pos.entry)); } CatalogString TsStorage::targetWithTags(DocPosition pos) const { return catalogString(targetForPos(pos)); } CatalogString TsStorage::sourceWithTags(DocPosition pos) const { return catalogString(sourceForPos(pos.entry)); } QString TsStorage::source(const DocPosition& pos) const { return content(sourceForPos(pos.entry)); } QString TsStorage::target(const DocPosition& pos) const { return content(targetForPos(pos)); } QString TsStorage::sourceWithPlurals(const DocPosition& pos, bool truncateFirstLine) const { QString str = source(pos); if (truncateFirstLine) { int truncatePos = str.indexOf("\n"); if (truncatePos != -1) str.truncate(truncatePos); } return str; } QString TsStorage::targetWithPlurals(const DocPosition& pos, bool truncateFirstLine) const { QString str = target(pos); if (truncateFirstLine) { int truncatePos = str.indexOf("\n"); if (truncatePos != -1) str.truncate(truncatePos); } return str; } void TsStorage::targetDelete(const DocPosition& pos, int count) { TsContentEditingData data(pos.offset, count); content(targetForPos(pos), &data); } void TsStorage::targetInsert(const DocPosition& pos, const QString& arg) { - qCWarning(LOKALIZE_LOG) << pos.entry << arg; QDomElement targetEl = targetForPos(pos); //BEGIN add <*target> if (targetEl.isNull()) { QDomNode unitEl = unitForPos(pos.entry); QDomNode refNode = unitEl.firstChildElement(names[SourceTag]); targetEl = unitEl.insertAfter(m_doc.createElement(names[TargetTag]), refNode).toElement(); if (pos.entry < size()) { - targetEl.appendChild(m_doc.createTextNode(arg));//i bet that pos.offset is 0 ;) + targetEl.appendChild(Settings::self()->convertXMLChars() ? + m_doc.createTextNode(arg) : + m_doc.createCDATASection(arg));//i bet that pos.offset is 0 ;) return; } } //END add <*target> - if (arg.isEmpty()) return; //means we were called just to add tag + if (arg.isEmpty()) return; //means we were called just to add tag TsContentEditingData data(pos.offset, arg); content(targetEl, &data); } void TsStorage::setTarget(const DocPosition& pos, const QString& arg) { Q_UNUSED(pos); Q_UNUSED(arg); //TODO } QVector TsStorage::altTrans(const DocPosition& pos) const { QVector result; QString oldsource = content(unitForPos(pos.entry).firstChildElement(names[OldSourceTag])); if (!oldsource.isEmpty()) result << AltTrans(CatalogString(oldsource), i18n("Previous source value, saved by lupdate tool")); return result; } QStringList TsStorage::sourceFiles(const DocPosition& pos) const { QStringList result; QDomElement elem = unitForPos(pos.entry).firstChildElement(attrnames[LocationAttr]); while (!elem.isNull()) { QString sourcefile = elem.attribute(QStringLiteral("filename")); QString linenumber = elem.attribute(QStringLiteral("line")); if (!(sourcefile.isEmpty() && linenumber.isEmpty())) result.append(sourcefile + ':' + linenumber); elem = elem.nextSiblingElement(attrnames[LocationAttr]); } //qSort(result); return result; } QVector TsStorage::notes(const DocPosition& pos) const { QVector result; QDomElement elem = unitForPos(pos.entry).firstChildElement(names[NoteTag]); while (!elem.isNull()) { - Note note; - note.content = elem.text(); - result.append(note); + QDomNode n = elem.firstChild(); + if (!n.isNull()) { + QDomCharacterData c = Settings::self()->convertXMLChars() ? n.toCharacterData() : n.toCDATASection(); + Note note; + note.content = c.data(); + result.append(note); + } elem = elem.nextSiblingElement(names[NoteTag]); } return result; } QVector TsStorage::developerNotes(const DocPosition& pos) const { QVector result; QDomElement elem = unitForPos(pos.entry).firstChildElement(names[DevNoteTag]); while (!elem.isNull()) { Note note; note.content = elem.text(); result.append(note); elem = elem.nextSiblingElement(names[DevNoteTag]); } return result; } Note TsStorage::setNote(DocPosition pos, const Note& note) { - //qCWarning(LOKALIZE_LOG)<convertXMLChars() ? + m_doc.createTextNode(QString()) : + m_doc.createCDATASection(QString())); } else { QDomNodeList list = unit.elementsByTagName(names[NoteTag]); //if (pos.form==-1) pos.form=list.size()-1; if (pos.form < list.size()) { elem = unit.elementsByTagName(names[NoteTag]).at(pos.form).toElement(); - oldNote.content = elem.text(); + QDomNode n = elem.firstChild(); + if (!n.isNull()) { + QDomCharacterData c = Settings::self()->convertXMLChars() ? n.toCharacterData() : n.toCDATASection(); + + oldNote.content = c.data(); + } } } if (elem.isNull()) return oldNote; - if (!elem.text().isEmpty()) { - TsContentEditingData data(0, elem.text().size()); - content(elem, &data); + QDomNode n = elem.firstChild(); + if (!n.isNull()) { + QDomCharacterData c = Settings::self()->convertXMLChars() ? n.toCharacterData() : n.toCDATASection(); + if (!c.data().isEmpty()) { + TsContentEditingData data(0, c.data().size()); + content(elem, &data); + } } if (!note.content.isEmpty()) { TsContentEditingData data(0, note.content); content(elem, &data); } else unit.removeChild(elem); return oldNote; } QStringList TsStorage::context(const DocPosition& pos) const { QStringList result; QDomElement unit = unitForPos(pos.entry); QDomElement context = unit.parentNode().toElement(); //if (context.isNull()) // return result; QDomElement name = context.firstChildElement(names[NameTag]); if (name.isNull()) return result; result.append(name.text()); return result; } QStringList TsStorage::matchData(const DocPosition& pos) const { Q_UNUSED(pos); return QStringList(); } QString TsStorage::id(const DocPosition& pos) const { QString result = source(pos); result.remove('\n'); QStringList ctxt = context(pos); if (ctxt.size()) result.prepend(ctxt.first()); return result; } bool TsStorage::isPlural(const DocPosition& pos) const { QDomElement unit = unitForPos(pos.entry); return unit.hasAttribute(names[PluralTag]); } void TsStorage::setApproved(const DocPosition& pos, bool approved) { targetInsert(pos, QString()); //adds if needed QDomElement target = unitForPos(pos.entry).firstChildElement(names[TargetTag]); //asking directly to bypass plural state detection if (target.attribute(attrnames[TypeAttr]) == attrvalues[ObsoleteVal]) return; if (approved) target.removeAttribute(attrnames[TypeAttr]); else target.setAttribute(attrnames[TypeAttr], QStringLiteral("unfinished")); } bool TsStorage::isApproved(const DocPosition& pos) const { QDomElement target = unitForPos(pos.entry).firstChildElement(names[TargetTag]); return !target.hasAttribute(attrnames[TypeAttr]) || target.attribute(attrnames[TypeAttr]) == attrvalues[VanishedVal]; } bool TsStorage::isObsolete(int entry) const { QDomElement target = unitForPos(entry).firstChildElement(names[TargetTag]); QString v = target.attribute(attrnames[TypeAttr]); return v == attrvalues[ObsoleteVal] || v == attrvalues[VanishedVal]; } bool TsStorage::isEmpty(const DocPosition& pos) const { TsContentEditingData data(TsContentEditingData::CheckLength); return content(targetForPos(pos), &data).isEmpty(); } bool TsStorage::isEquivTrans(const DocPosition& pos) const { Q_UNUSED(pos) return true;//targetForPos(pos.entry).attribute("equiv-trans")!="no"; } void TsStorage::setEquivTrans(const DocPosition& pos, bool equivTrans) { Q_UNUSED(pos) Q_UNUSED(equivTrans) //targetForPos(pos.entry).setAttribute("equiv-trans",noyes[equivTrans]); } QDomElement TsStorage::unitForPos(int pos) const { return entries.at(pos).toElement(); } QDomElement TsStorage::targetForPos(DocPosition pos) const { QDomElement unit = unitForPos(pos.entry); QDomElement translation = unit.firstChildElement(names[TargetTag]); if (!unit.hasAttribute(names[PluralTag])) return translation; if (pos.form == -1) pos.form = 0; QDomNodeList forms = translation.elementsByTagName(QStringLiteral("numerusform")); while (pos.form >= forms.size()) translation.appendChild(unit.ownerDocument().createElement(QStringLiteral("numerusform"))); return forms.at(pos.form).toElement(); } QDomElement TsStorage::sourceForPos(int pos) const { return unitForPos(pos).firstChildElement(names[SourceTag]); } void TsStorage::setTargetLangCode(const QString& langCode) { m_targetLangCode = langCode; QDomElement file = m_doc.elementsByTagName(QStringLiteral("TS")).at(0).toElement(); if (m_targetLangCode != file.attribute(QStringLiteral("language")).replace('-', '_')) { QString l = langCode; file.setAttribute(QStringLiteral("language"), l.replace('_', '-')); } } //END STORAGE TRANSLATION diff --git a/src/catalog/ts/tsstorage.h b/src/catalog/ts/tsstorage.h index cd45979..87c627a 100644 --- a/src/catalog/ts/tsstorage.h +++ b/src/catalog/ts/tsstorage.h @@ -1,122 +1,122 @@ /* Copyright 2008-2014 Nick Shaforostoff 2018-2019 by Simon Depiets This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef TSSTORAGE_H #define TSSTORAGE_H #include "catalogstorage.h" #include #include #include #include // class QDomDocument; class TsStorage: public CatalogStorage { public: TsStorage(); ~TsStorage() override = default; int capabilities() const override; int load(QIODevice* device) override; bool save(QIODevice* device, bool belongsToProject = false) override; int size() const override; bool isEmpty() const; //flat-model interface (ignores TS grouping) QString source(const DocPosition& pos) const override; QString target(const DocPosition& pos) const override; QString sourceWithPlurals(const DocPosition& pos, bool truncateFirstLine) const override; QString targetWithPlurals(const DocPosition& pos, bool truncateFirstLine) const override; CatalogString targetWithTags(DocPosition pos) const override; CatalogString sourceWithTags(DocPosition pos) const override; CatalogString catalogString(const DocPosition& pos) const override; /// all plural forms. pos.form doesn't matter TODO QStringList sourceAllForms(const DocPosition& pos, bool stripNewLines = false) const override { Q_UNUSED(pos) Q_UNUSED(stripNewLines) return QStringList(); } QStringList targetAllForms(const DocPosition& pos, bool stripNewLines = false) const override { Q_UNUSED(pos) Q_UNUSED(stripNewLines) return QStringList(); } void targetDelete(const DocPosition& pos, int count) override; void targetInsert(const DocPosition& pos, const QString& arg) override; void setTarget(const DocPosition& pos, const QString& arg) override;//called for mergeCatalog QStringList sourceFiles(const DocPosition& pos) const override; QVector altTrans(const DocPosition& pos) const override; ///@a pos.form is note number Note setNote(DocPosition pos, const Note& note) override; QVector notes(const DocPosition& pos) const override; QVector developerNotes(const DocPosition& pos) const override; QStringList context(const DocPosition& pos) const override; QStringList matchData(const DocPosition& pos) const override; QString id(const DocPosition& pos) const override; bool isPlural(const DocPosition& pos) const override; bool isEmpty(const DocPosition& pos) const override; bool isEquivTrans(const DocPosition& pos) const override; void setEquivTrans(const DocPosition& pos, bool equivTrans) override; bool isApproved(const DocPosition& pos) const override; void setApproved(const DocPosition& pos, bool approved) override; bool isObsolete(int entry) const override; QString mimetype() const override { return QStringLiteral("application/x-linguist"); } QString fileType() const override { return QStringLiteral("Qt Linguist (*.ts)"); } CatalogType type() const override { return Ts; } void setTargetLangCode(const QString& langCode) override; private: QDomElement unitForPos(int pos) const; QDomElement targetForPos(DocPosition pos) const; QDomElement sourceForPos(int pos) const; CatalogString catalogString(QDomElement contentElement) const; - + QString protect(const QString & str) const; private: mutable QDomDocument m_doc; QDomNodeList entries; }; #endif diff --git a/src/prefs/lokalize.kcfg b/src/prefs/lokalize.kcfg index 07fbed6..573f89a 100644 --- a/src/prefs/lokalize.kcfg +++ b/src/prefs/lokalize.kcfg @@ -1,177 +1,180 @@ QLocale QFontDatabase kemailsettings.h kde-i18n-lists.h KEMailSettings().getSetting(KEMailSettings::RealName) KEMailSettings().getSetting(KEMailSettings::RealName) false KEMailSettings().getSetting(KEMailSettings::EmailAddress) QLocale::system().name() getMailingList() #99CCFF #FF9999 true QFontDatabase::systemFont(QFontDatabase::GeneralFont) false 0 true false kate %1:%2 true true false + + false + false 4 false false 7 0 true false false false posieve -u %u check-rules %f posieve -s lokalize check-rules %f false 0 diff --git a/src/prefs/prefs_editor.ui b/src/prefs/prefs_editor.ui index f959048..5343773 100644 --- a/src/prefs/prefs_editor.ui +++ b/src/prefs/prefs_editor.ui @@ -1,106 +1,113 @@ prefs_editor 0 0 612 375 11 11 11 11 6 - - - - Qt::Vertical + + + + If set, mouse wheel goes to previous or next unit, otherwise it scrolls text - - - 20 - 40 - + + <html>When this option is enabled, the mouse wheel is used to go to the previous or next translation unit (without modifier keys). Modifier keys can be used to change this behavior. Use:<ul><li><b>Shift</b> to scroll within the text of the current unit,</li><li><b>Ctrl+Shift</b> to go to previous or next non-ready unit,</li><li><b>Ctrl</b> to go to previous or next non-ready not empty unit,</li><li><b>Alt</b> to go to previous or next untranslated unit.</li></ul>When the option is disabled, the mouse wheel scrolls within the text of the current translation unit.</html> - + + Mouse wheel goes to previous or next translation unit + + Set 'approved' status automatically when editing started - - - - If set, mouse wheel goes to previous or next unit, otherwise it scrolls text + + + + Qt::Vertical - - <html>When this option is enabled, the mouse wheel is used to go to the previous or next translation unit (without modifier keys). Modifier keys can be used to change this behavior. Use:<ul><li><b>Shift</b> to scroll within the text of the current unit,</li><li><b>Ctrl+Shift</b> to go to previous or next non-ready unit,</li><li><b>Ctrl</b> to go to previous or next non-ready not empty unit,</li><li><b>Alt</b> to go to previous or next untranslated unit.</li></ul>When the option is disabled, the mouse wheel scrolls within the text of the current translation unit.</html> + + + 20 + 30 + + + + + - Mouse wheel goes to previous or next translation unit + Visualize separators such as spaces, tabs and new lines in the editor Set to 2 to disable word completion Minimum word length for word completion kcfg_WordCompletionLength Set to 2 to disable word completion Disable word completion 2 20 - - + + - Visualize separators such as spaces, tabs and new lines in the editor + Automatically convert XML special characters in TS files