Editor: Add localized string support

alut-include-path
Sven Eberhardt 2017-05-07 14:25:03 -04:00
parent 52caf696e6
commit 4fac960cf4
15 changed files with 734 additions and 32 deletions

View File

@ -758,6 +758,9 @@ if(WITH_QT_EDITOR)
src/editor/C4ConsoleQtNewScenario.cpp
src/editor/C4ConsoleQtNewScenario.h
src/editor/C4ConsoleQtNewScenario.ui
src/editor/C4ConsoleQtLocalizeString.cpp
src/editor/C4ConsoleQtLocalizeString.h
src/editor/C4ConsoleQtLocalizeString.ui
src/editor/C4ConsoleQtMainWindow.ui
src/editor/resource.qrc
${qt_editor_resources}

View File

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE funcs
SYSTEM '../../../clonk.dtd'>
<?xml-stylesheet type="text/xsl" href="../../../clonk.xsl"?>
<funcs>
<func>
<title>GetTranslatedString</title>
<category>Script</category>
<subcat>Strings</subcat>
<version>8.0 OC</version>
<syntax>
<rtype>string</rtype>
<params>
<param>
<type>any</type>
<name>string_data</name>
<desc>Either a string or a proplist containing multiple translations of a string. If a string or <code>nil</code> is passed, the parameter is returned directly. If a proplist is passed, the value corresponding to the selected language (or a fallback) is returned.</desc>
</param>
</params>
</syntax>
<desc>Returns a string corresponding to the user's selected language. For <code>string_data</code>, the expected format is <code>{ Function="Translate", DE="Hallo, Welt", US="Hello, World"}</code>. If no matching entry is found or it is <code>nil</code>, then another language string is returned as a fallback.</desc>
<examples>
<example>
<code>Log(GetTranslatedString({ Function="Translate", DE="Dies ist ein Test.", US="This is a test."}));</code>
<text>Logs either "Dies ist ein Test." or "This is a test." depending on the player's language setting.</text>
</example>
<example>
<code>local inscription = "";
// Players can read the sign via the interaction bar.
public func IsInteractable() { return true; }
// Called on player interaction.
public func Interact(object clonk)
{
if (!clonk) return false;
Dialogue->MessageBox(GetTranslatedString(inscription), clonk, this, clonk->GetController(), true);
return true;
}
public func SetInscription(to_text)
{
inscription = to_text ?? "";
return true;
}
public func Definition(def)
{
// Inscription props
if (!def.EditorProps) def.EditorProps = {};
def.EditorProps.inscription = { Name="Inscription", Type="string", Set="SetInscription", Save="Inscription", Translatable=true };
}</code>
<text>Code for a signpost. The string editor property with setting <code>Translatable=true</code> provides a translation proplist in the correct format automatically.</text>
</example>
</examples>
<related><funclink>Translate</funclink></related>
</func>
<author>Sven2</author><date>2017-05</date>
</funcs>

View File

@ -34,6 +34,7 @@ MsgOnFire3=Oops, I dropped my lighter!</code>
<text>When the clonk catches fire, the engine calls Incineration() in the clonk and in this example, one of the four above messages is displayed at random.</text>
</example>
</examples>
<related><funclink>GetTranslatedString</funclink></related>
</func>
<author>Isilkor</author><date>2009-11</date>
<author>Newton</author><date>2011-06</date>

View File

@ -32,6 +32,8 @@ IDS_BTN_YES=Ja
IDS_CHAT_NOTCONNECTED=nicht verbunden
IDS_CHAT_SERVER=Server
IDS_CNS_ACTION=Aktivität:
IDS_CNS_ADDLANGUAGE=Sprache hinzufügen
IDS_CNS_ADDLANGUAGEID=Sprach-ID (z.B. DE, US, FR)
IDS_CNS_ALLOBJECTS=Alle Objekte
IDS_CNS_ARRAYADD=Element hinzufügen
IDS_CNS_ARRAYEDIT=Array
@ -100,6 +102,7 @@ IDS_CNS_SHOWHELP=Hilfetexte anzeigen
IDS_CNS_SHOWHELPTIP=Aktiviert oder deaktiviert Objektbeschreibungen und Tooltip-Marker (?) im Objekteigenschaftsdialog.
IDS_CNS_TEMPLATE=Vorlage
IDS_CNS_TITLE=Titel
IDS_CNS_TRANSLATE=Übersetzung
IDS_CNS_TRUE=Ja
IDS_CNS_TYPE=Typ: %s (%s)
IDS_CNS_VALUE=Wert
@ -398,6 +401,7 @@ IDS_ERR_HELPCMD=Grundlegende Befehle im IRC-Chat:|/join [Chatraum] - Neuen Chatr
IDS_ERR_INITFONTS=Fehler bei der Schriftinitialisierung
IDS_ERR_INSUFFICIENTPARAMETERS=/%s: fehlende Parameter
IDS_ERR_INVALIDCHANNELNAME=Kein gültiger Chat-Kanal.
IDS_ERR_INVALIDLANGUAGEID=Ungültige Sprach-ID.
IDS_ERR_INVALIDNICKNAME=Unzulässiger Kurzname.
IDS_ERR_INVALIDNICKNAME2=/%s: unzulässiger Kurzname
IDS_ERR_INVALIDPASSWORDMAX31CHARA=Nicht zulässiges Passwort: maximal 31 Zeichen, keine Leerzeichen.

View File

@ -32,6 +32,8 @@ IDS_BTN_YES=Yes
IDS_CHAT_NOTCONNECTED=not connected
IDS_CHAT_SERVER=Server
IDS_CNS_ACTION=Action:
IDS_CNS_ADDLANGUAGE=Add language
IDS_CNS_ADDLANGUAGEID=Language ID (e.g. DE, US, FR)
IDS_CNS_ALLOBJECTS=All objects
IDS_CNS_ARRAYADD=Add item
IDS_CNS_ARRAYEDIT=Array
@ -100,6 +102,7 @@ IDS_CNS_SHOWHELP=Show help texts
IDS_CNS_SHOWHELPTIP=Activates or deactivates object descriptions and tooltip markers (?) in the object property dialogue.
IDS_CNS_TEMPLATE=Template
IDS_CNS_TITLE=Title
IDS_CNS_TRANSLATE=Translation
IDS_CNS_TRUE=Yes
IDS_CNS_TYPE=Type: %s (%s)
IDS_CNS_VALUE=Value
@ -398,6 +401,7 @@ IDS_ERR_HELPCMD=Basic commands in the IRC-chat:|/join [channel] - Enter a new ch
IDS_ERR_INITFONTS=Error initializing fonts
IDS_ERR_INSUFFICIENTPARAMETERS=/%s: insufficient parameters
IDS_ERR_INVALIDCHANNELNAME=Invalid channel name.
IDS_ERR_INVALIDLANGUAGEID=Invalid language ID.
IDS_ERR_INVALIDNICKNAME=Invalid nickname.
IDS_ERR_INVALIDNICKNAME2=/%s: invalid nick name
IDS_ERR_INVALIDPASSWORDMAX31CHARA=Invalid password. Maximum 31 characters. No spaces allowed.

View File

@ -0,0 +1,171 @@
/*
* OpenClonk, http://www.openclonk.org
*
* Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
* Copyright (c) 2013, The OpenClonk Team and contributors
*
* Distributed under the terms of the ISC license; see accompanying file
* "COPYING" for details.
*
* "Clonk" is a registered trademark of Matthes Bender, used with permission.
* See accompanying file "TRADEMARK" for details.
*
* To redistribute this file separately, substitute the full license texts
* for the above references.
*/
/* String localization editors */
#include "C4Include.h"
#include "script/C4Value.h"
#include "config/C4Config.h"
#include "editor/C4ConsoleQtLocalizeString.h"
#include "c4group/C4Language.h"
/* Single string editor */
C4ConsoleQtLocalizeStringDlg::C4ConsoleQtLocalizeStringDlg(class QMainWindow *parent_window, const C4Value &translations)
: QDialog(parent_window, Qt::WindowSystemMenuHint | Qt::WindowTitleHint | Qt::WindowCloseButtonHint)
, translations(translations)
{
ui.setupUi(this);
// Add language editors
int32_t lang_index = 0;
C4LanguageInfo *lang_info;
while (lang_info = ::Languages.GetInfo(lang_index++))
{
AddEditor(lang_info->Code, lang_info->Name);
}
// Fill in values
C4PropList *translations_proplist = translations.getPropList();
assert(translations_proplist);
for (C4String *lang_str : translations_proplist->GetSortedLocalProperties(false))
{
if (lang_str->GetData().getLength() == 2)
{
C4Value text_val;
if (translations_proplist->GetPropertyByS(lang_str, &text_val))
{
C4String *text = text_val.getStr();
if (text)
{
QLineEdit *editor = GetEditorByLanguage(lang_str->GetCStr());
if (!editor)
{
// Unknown language. Just add an editor without language name.
editor = AddEditor(lang_str->GetCStr(), nullptr);
}
editor->setText(QString(text->GetCStr()));
}
}
}
}
// Size
adjustSize();
setMinimumSize(size());
// Focus on first empty editor
if (edited_languages.size())
{
edited_languages.front().value_editor->setFocus(); // fallback to first editor
for (const auto & langs : edited_languages)
{
if (!langs.value_editor->text().length())
{
langs.value_editor->setFocus();
break;
}
}
}
}
void C4ConsoleQtLocalizeStringDlg::DoError(const char *msg)
{
QMessageBox::critical(this, ::LoadResStr("IDS_ERR_TITLE"), QString(msg));
}
QLineEdit *C4ConsoleQtLocalizeStringDlg::AddEditor(const char *language, const char *language_name)
{
assert(!GetEditorByLanguage(const char *language));
// Add editor widgets
int32_t row = edited_languages.size();
QString language_label_text(language);
if (language_name) language_label_text.append(FormatString(" (%s)", language_name).getData());
QLabel *language_label = new QLabel(language_label_text, this);
ui.mainGrid->addWidget(language_label, row, 0);
QLineEdit *value_editor = new QLineEdit(this);
ui.mainGrid->addWidget(value_editor, row, 1);
// Add to list
EditedLanguage new_editor;
SCopy(language, new_editor.language, 2);
new_editor.value_editor = value_editor;
edited_languages.push_back(new_editor);
return value_editor;
}
QLineEdit *C4ConsoleQtLocalizeStringDlg::GetEditorByLanguage(const char *language)
{
// Search text editor by language ID
for (const auto & langs : edited_languages)
{
if (!strcmp(langs.language, language))
{
return langs.value_editor;
}
}
// Not found
return nullptr;
}
void C4ConsoleQtLocalizeStringDlg::done(int r)
{
if (QDialog::Accepted == r) // ok was pressed
{
C4PropList *translations_proplist = translations.getPropList();
assert(translations_proplist);
// Set all translations
for (const auto & langs : edited_languages)
{
// Empty strings are set to nil, because that allows the user to set it to fallback
QString text = langs.value_editor->text();
if (text.length())
{
C4Value text_val = C4VString(text.toUtf8());
translations_proplist->SetPropertyByS(::Strings.RegString(langs.language), text_val);
}
else
{
translations_proplist->ResetProperty(::Strings.RegString(langs.language));
}
}
}
// Close
QDialog::done(r);
}
void C4ConsoleQtLocalizeStringDlg::AddLanguagePressed()
{
bool lang_ok = false;
QRegExpValidator validator(QRegExp("^[a-zA-Z][a-zA-Z]$"), this);
QString lang_id;
while (!lang_ok)
{
bool ok; int q = 0;
lang_id = QInputDialog::getText(this, LoadResStr("IDS_CNS_ADDLANGUAGE"), LoadResStr("IDS_CNS_ADDLANGUAGEID"), QLineEdit::Normal, QString(), &ok);
if (!ok) return;
lang_ok = (validator.validate(lang_id, q) == QValidator::Acceptable);
if (!lang_ok)
{
DoError(LoadResStr("IDS_ERR_INVALIDLANGUAGEID"));
}
}
// Either add or just focus existing editor
QLineEdit *editor = GetEditorByLanguage(lang_id.toUtf8());
if (!editor)
{
editor = AddEditor(lang_id.toUtf8(), nullptr);
adjustSize();
setMinimumSize(size());
}
editor->setFocus();
}

View File

@ -0,0 +1,57 @@
/*
* OpenClonk, http://www.openclonk.org
*
* Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
* Copyright (c) 2013, The OpenClonk Team and contributors
*
* Distributed under the terms of the ISC license; see accompanying file
* "COPYING" for details.
*
* "Clonk" is a registered trademark of Matthes Bender, used with permission.
* See accompanying file "TRADEMARK" for details.
*
* To redistribute this file separately, substitute the full license texts
* for the above references.
*/
/* String localization editors */
#ifndef INC_C4ConsoleQtLocalizeString
#define INC_C4ConsoleQtLocalizeString
#ifdef WITH_QT_EDITOR
#include "C4Include.h" // needed for automoc
#include "editor/C4ConsoleGUI.h" // for glew.h
#include "editor/C4ConsoleQt.h"
#include "ui_C4ConsoleQtLocalizeString.h"
class C4ConsoleQtLocalizeStringDlg : public QDialog
{
Q_OBJECT
Ui::LocalizeStringDialog ui;
C4Value translations;
struct EditedLanguage
{
char language[3];
QLineEdit *value_editor;
};
std::list<EditedLanguage> edited_languages;
public:
C4ConsoleQtLocalizeStringDlg(class QMainWindow *parent_window, const C4Value &translations);
C4PropList *GetTranslations() const { return translations.getPropList(); }
private:
void DoError(const char *msg);
QLineEdit *AddEditor(const char *language, const char *language_name);
QLineEdit *GetEditorByLanguage(const char *language);
void done(int r) override;
protected slots:
void AddLanguagePressed();
};
#endif // WITH_QT_EDITOR
#endif // INC_C4ConsoleQtLocalizeString

View File

@ -0,0 +1,130 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>LocalizeStringDialog</class>
<widget class="QDialog" name="LocalizeStringDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>863</width>
<height>89</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>500</width>
<height>0</height>
</size>
</property>
<property name="windowTitle">
<string comment="res">IDS_CNS_TRANSLATE</string>
</property>
<property name="sizeGripEnabled">
<bool>true</bool>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout">
<property name="sizeConstraint">
<enum>QLayout::SetDefaultConstraint</enum>
</property>
<item>
<layout class="QGridLayout" name="mainGrid">
<property name="sizeConstraint">
<enum>QLayout::SetDefaultConstraint</enum>
</property>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="addLanguageButton">
<property name="text">
<string comment="res">IDS_CNS_ADDLANGUAGE</string>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>LocalizeStringDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>400</x>
<y>282</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>LocalizeStringDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>addLanguageButton</sender>
<signal>pressed()</signal>
<receiver>LocalizeStringDialog</receiver>
<slot>AddLanguagePressed()</slot>
<hints>
<hint type="sourcelabel">
<x>78</x>
<y>270</y>
</hint>
<hint type="destinationlabel">
<x>242</x>
<y>146</y>
</hint>
</hints>
</connection>
</connections>
<slots>
<slot>AddLanguagePressed()</slot>
</slots>
</ui>

View File

@ -19,6 +19,7 @@
#include "editor/C4ConsoleQtPropListViewer.h"
#include "editor/C4ConsoleQtDefinitionListViewer.h"
#include "editor/C4ConsoleQtState.h"
#include "editor/C4ConsoleQtLocalizeString.h"
#include "editor/C4Console.h"
#include "object/C4Object.h"
#include "object/C4GameObjects.h"
@ -312,44 +313,191 @@ bool C4PropertyDelegateInt::IsPasteValid(const C4Value &val) const
/* String delegate */
C4PropertyDelegateStringEditor::C4PropertyDelegateStringEditor(QWidget *parent, bool has_localization_button)
: QWidget(parent), edit(nullptr), localization_button(nullptr), commit_pending(false), text_edited(false)
{
auto layout = new QHBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
layout->setMargin(0);
layout->setSpacing(0);
edit = new QLineEdit(this);
layout->addWidget(edit);
if (has_localization_button)
{
localization_button = new QPushButton(QString(LoadResStr("IDS_CNS_MORE")), this);
layout->addWidget(localization_button);
connect(localization_button, &QPushButton::pressed, this, [this]() {
// Show dialogue
OpenLocalizationDialogue();
});
}
connect(edit, &QLineEdit::returnPressed, this, [this]() {
text_edited = true;
commit_pending = true;
emit EditingDoneSignal();
});
connect(edit, &QLineEdit::textEdited, this, [this]() {
text_edited = true;
commit_pending = true;
});
}
void C4PropertyDelegateStringEditor::OpenLocalizationDialogue()
{
if (!localization_dialogue)
{
// Make sure we have an updated value
StoreEditedText();
// Make sure we're using a localized string
if (value.GetType() != C4V_PropList)
{
C4PropList *value_proplist = ::Game.AllocateTranslatedString();
if (value.GetType() == C4V_String)
{
C4String *lang = ::Strings.RegString(lang_code);
value_proplist->SetPropertyByS(lang, value);
}
value = C4VPropList(value_proplist);
}
// Open dialogue on value
localization_dialogue.reset(new C4ConsoleQtLocalizeStringDlg(::Console.GetState()->window.get(), value));
connect(localization_dialogue.get(), &C4ConsoleQtLocalizeStringDlg::accepted, this, [this]() {
// Usually, the proplist owned by localization_dialogue is the same as this->value
// However, it may have changed if there was an update call that modified the value while the dialogue was open
// In this case, take the value from the dialogue
SetValue(C4VPropList(localization_dialogue->GetTranslations()));
// Finish editing on the value
CloseLocalizationDialogue();
commit_pending = true;
emit EditingDoneSignal();
});
connect(localization_dialogue.get(), &C4ConsoleQtLocalizeStringDlg::rejected, this, [this]() {
CloseLocalizationDialogue();
});
localization_dialogue->show();
}
}
void C4PropertyDelegateStringEditor::CloseLocalizationDialogue()
{
if (localization_dialogue)
{
localization_dialogue->close();
localization_dialogue.reset();
}
}
void C4PropertyDelegateStringEditor::StoreEditedText()
{
if (text_edited)
{
// TODO: Would be better to handle escaping in the C4Value-to-string code
QString new_value = edit->text();
new_value = new_value.replace("\\", "\\\\").replace("\"", "\\\"");
C4Value text_value = C4VString(new_value.toUtf8());
// If translatable, always store as translation proplist
// This makes it easier to collect strings to be localized in the localization overview
if (localization_button)
{
C4PropList *value_proplist = this->value.getPropList();
if (!value_proplist)
{
value_proplist = ::Game.AllocateTranslatedString();
}
C4String *lang = ::Strings.RegString(lang_code);
value_proplist->SetPropertyByS(lang, text_value);
}
else
{
this->value = text_value;
}
text_edited = false;
}
}
void C4PropertyDelegateStringEditor::SetValue(const C4Value &val)
{
// Set editor text to value
// Resolve text string and default language for localized strings
C4String *s;
C4Value language;
if (localization_button)
{
s = ::Game.GetTranslatedString(val, &language, true);
C4String *language_string = language.getStr();
SCopy(language_string ? language_string->GetCStr() : Config.General.LanguageEx, lang_code, 2);
localization_button->setText(QString(lang_code));
}
else
{
s = val.getStr();
}
edit->setText(QString(s ? s->GetCStr() : ""));
// Remember full value with all localizations
if (val.GetType() == C4V_PropList)
{
if (val != this->value)
{
// Localization proplist: Create a copy (C4Value::Copy() would be nice)
C4PropList *new_value_proplist = new C4PropListScript();
this->value = C4VPropList(new_value_proplist);
C4PropList *val_proplist = val.getPropList();
for (C4String *lang : val_proplist->GetSortedLocalProperties())
{
C4Value lang_string;
val_proplist->GetPropertyByS(lang, &lang_string);
new_value_proplist->SetPropertyByS(lang, lang_string);
}
}
}
else
{
this->value = val;
}
}
C4Value C4PropertyDelegateStringEditor::GetValue()
{
// Flush edits from the text field into value
StoreEditedText();
// Return current value
return this->value;
}
C4PropertyDelegateString::C4PropertyDelegateString(const C4PropertyDelegateFactory *factory, C4PropList *props)
: C4PropertyDelegate(factory, props)
{
}
void C4PropertyDelegateString::SetEditorData(QWidget *editor, const C4Value &val, const C4PropertyPath &property_path) const
{
Editor *line_edit = static_cast<Editor*>(editor);
C4String *s = val.getStr();
line_edit->setText(QString(s ? s->GetCStr() : ""));
}
void C4PropertyDelegateString::SetModelData(QObject *editor, const C4PropertyPath &property_path, C4ConsoleQtShape *prop_shape) const
{
Editor *line_edit = static_cast<Editor*>(editor);
// Only set model data when pressing Enter explicitely; not just when leaving
if (line_edit->commit_pending)
if (props)
{
QString new_value = line_edit->text();
// TODO: Would be better to handle escaping in the C4Value-to-string code
new_value = new_value.replace("\\", "\\\\").replace("\"", "\\\"");
property_path.SetProperty(C4VString(new_value.toUtf8()));
translatable = props->GetPropertyBool(P_Translatable);
}
}
void C4PropertyDelegateString::SetEditorData(QWidget *aeditor, const C4Value &val, const C4PropertyPath &property_path) const
{
Editor *editor = static_cast<Editor*>(aeditor);
editor->SetValue(val);
}
void C4PropertyDelegateString::SetModelData(QObject *aeditor, const C4PropertyPath &property_path, C4ConsoleQtShape *prop_shape) const
{
Editor *editor = static_cast<Editor*>(aeditor);
// Only set model data when pressing Enter explicitely; not just when leaving
if (editor->IsCommitPending())
{
property_path.SetProperty(editor->GetValue());
factory->GetPropertyModel()->DoOnUpdateCall(property_path, this);
line_edit->commit_pending = false;
editor->SetCommitPending(false);
}
}
QWidget *C4PropertyDelegateString::CreateEditor(const C4PropertyDelegateFactory *parent_delegate, QWidget *parent, const QStyleOptionViewItem &option, bool by_selection, bool is_child) const
{
Editor *editor = new Editor(parent);
Editor *editor = new Editor(parent, translatable);
// EditingDone on return or when leaving edit field after a change has been made
connect(editor, &QLineEdit::returnPressed, editor, [this, editor]() {
editor->commit_pending = true;
connect(editor, &Editor::EditingDoneSignal, editor, [this, editor]() {
emit EditingDoneSignal(editor);
});
connect(editor, &QLineEdit::textEdited, this, [editor, this]() {
editor->commit_pending = true;
});
// Selection in child enum: Direct focus
if (by_selection && is_child) editor->setFocus();
return editor;
@ -358,15 +506,23 @@ QWidget *C4PropertyDelegateString::CreateEditor(const C4PropertyDelegateFactory
QString C4PropertyDelegateString::GetDisplayString(const C4Value &v, C4Object *obj, bool short_names) const
{
// Raw string without ""
C4String *s = v.getStr();
C4String *s = translatable ? ::Game.GetTranslatedString(v, nullptr, true) : v.getStr();
return QString(s ? s->GetCStr() : "");
}
bool C4PropertyDelegateString::IsPasteValid(const C4Value &val) const
{
// Check string type
if (val.GetType() != C4V_String) return false;
return true;
// Check string type or translatable proplist
if (val.GetType() == C4V_String) return true;
if (translatable)
{
C4PropList *val_p = val.getPropList();
if (val_p)
{
return val_p->GetPropertyStr(P_Function) == &::Strings.P[P_Translate];
}
}
return false;
}

View File

@ -125,15 +125,35 @@ public:
bool IsPasteValid(const C4Value &val) const override;
};
class C4PropertyDelegateStringEditor : public QLineEdit
class C4PropertyDelegateStringEditor : public QWidget
{
Q_OBJECT
private:
QLineEdit *edit;
QPushButton *localization_button;
bool text_edited, commit_pending;
C4Value value;
char lang_code[3];
C4Value base_proplist;
std::unique_ptr<class C4ConsoleQtLocalizeStringDlg> localization_dialogue;
void OpenLocalizationDialogue();
void CloseLocalizationDialogue();
void StoreEditedText();
public:
C4PropertyDelegateStringEditor(QWidget *parent) : QLineEdit(parent), commit_pending(false) {}
bool commit_pending;
C4PropertyDelegateStringEditor(QWidget *parent, bool has_localization_button);
void SetValue(const C4Value &val);
C4Value GetValue();
bool IsCommitPending() const { return commit_pending; }
void SetCommitPending(bool to_val) { commit_pending = to_val; }
signals:
void EditingDoneSignal() const;
};
class C4PropertyDelegateString : public C4PropertyDelegate
{
private:
bool translatable;
public:
typedef C4PropertyDelegateStringEditor Editor;

View File

@ -3890,3 +3890,83 @@ void C4Game::SetGlobalSoundModifier(C4PropList *new_modifier)
}
::Application.SoundSystem.Modifiers.SetGlobalModifier(mod, NO_OWNER);
}
C4String *C4Game::GetTranslatedString(const C4Value &input_string, C4Value *selected_language, bool fail_silently) const
{
// Resolve a localized string
// If a string is passed, just return it
// If a proplist like { DE="Hallo, Welt!", US="Hello, world!" } is passed, return the string matching the selected language
// Nothing?
if (input_string.GetType() == C4V_Nil)
{
return nullptr;
}
// Non-localized string?
if (input_string.GetType() == C4V_String)
{
return input_string._getStr();
}
// Invalid type for this function?
C4PropList *p = input_string._getPropList();
if (!p || p->GetPropertyStr(P_Function) != &::Strings.P[P_Translate])
{
if (fail_silently)
{
return nullptr;
}
else
{
throw C4AulExecError(FormatString("Invalid value for translation: %s", input_string.GetDataString().getData()).getData());
}
}
// This is a proplist. Resolve the language as the key.
char lang_code[3] = "";
for (int32_t lang_index = 0; SCopySegment(Config.General.LanguageEx, lang_index, lang_code, ',', 2); ++lang_index)
{
C4String *lang_string = ::Strings.FindString(lang_code);
if (lang_string) // If the string is not found, it cannot be the key in a prop list
{
C4Value localized_string_val;
if (p->GetPropertyByS(lang_string, &localized_string_val))
{
C4String *localized_string = localized_string_val.getStr();
if (localized_string)
{
// Found it!
if (selected_language)
{
selected_language->SetString(lang_string);
}
return localized_string;
}
}
}
}
// No language matched. Just use any property and assume it's a language key.
for (C4String *lang_string : p->GetSortedLocalProperties(false))
{
C4Value localized_string_val;
if (p->GetPropertyByS(lang_string, &localized_string_val))
{
C4String *localized_string = localized_string_val.getStr();
if (localized_string)
{
// Found it!
if (selected_language)
{
selected_language->SetString(lang_string);
}
return localized_string;
}
}
}
// No string properties. There's no localized information to be found.
return nullptr;
}
C4PropList *C4Game::AllocateTranslatedString()
{
C4PropListScript *value_proplist = new C4PropListScript();
value_proplist->SetProperty(P_Function, C4VString(&::Strings.P[P_Translate]));
return value_proplist;
}

View File

@ -290,6 +290,10 @@ public:
bool ToggleChart(); // chart dlg on/off
void SetGlobalSoundModifier(C4PropList *modifier_props);
// Localized strings in editor props
C4String *GetTranslatedString(const class C4Value &input_string, C4Value *selected_language, bool fail_silently) const;
C4PropList *AllocateTranslatedString();
static constexpr const char * DirectJoinFilePrefix = "file:";
};

View File

@ -2742,6 +2742,12 @@ static long FnGetPXSCount(C4PropList * _this, Nillable<long> iMaterial, Nillable
}
}
static C4String *FnGetTranslatedString(C4PropList * _this, const C4Value & string_data)
{
// Resolve proplists containing localized strings to the current localization
return ::Game.GetTranslatedString(string_data, nullptr, false);
}
extern C4ScriptConstDef C4ScriptGameConstMap[];
extern C4ScriptFnDef C4ScriptGameFnMap[];
@ -2953,6 +2959,7 @@ void InitGameFunctionMap(C4AulScriptEngine *pEngine)
F(IncinerateLandscape);
F(GetGravity);
F(SetGravity);
F(GetTranslatedString);
#undef F
}

View File

@ -311,6 +311,9 @@ C4StringTable::C4StringTable()
P[P_ForceSerialization] = "ForceSerialization";
P[P_DrawArrows] = "DrawArrows";
P[P_SCENPAR] = "SCENPAR";
P[P_Translatable] = "Translatable";
P[P_Function] = "Function";
P[P_Translate] = "Translate";
P[DFA_WALK] = "WALK";
P[DFA_FLIGHT] = "FLIGHT";
P[DFA_KNEEL] = "KNEEL";

View File

@ -535,6 +535,9 @@ enum C4PropertyName
P_ForceSerialization,
P_DrawArrows,
P_SCENPAR,
P_Translatable,
P_Function,
P_Translate,
// Default Action Procedures
DFA_WALK,
DFA_FLIGHT,