Improve native name phrases in passport.

Also auto-save *_name to *_name_latin for english countries.
This commit is contained in:
John Preston 2018-08-20 15:40:41 +03:00
parent 5b88f4d3d2
commit b8b5ab6378
11 changed files with 323 additions and 25 deletions

View File

@ -0,0 +1,55 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
"cloud_lng_passport_in_ar" = "Arabic";
"cloud_lng_passport_in_az" = "Azerbaijani";
"cloud_lng_passport_in_bg" = "Bulgarian";
"cloud_lng_passport_in_bn" = "Bangla";
"cloud_lng_passport_in_cs" = "Czech";
"cloud_lng_passport_in_da" = "Danish";
"cloud_lng_passport_in_de" = "German";
"cloud_lng_passport_in_dv" = "Divehi";
"cloud_lng_passport_in_dz" = "Dzongkha";
"cloud_lng_passport_in_el" = "Greek";
"cloud_lng_passport_in_en" = "English";
"cloud_lng_passport_in_es" = "Spanish";
"cloud_lng_passport_in_et" = "Estonian";
"cloud_lng_passport_in_fa" = "Persian";
"cloud_lng_passport_in_fr" = "French";
"cloud_lng_passport_in_he" = "Hebrew";
"cloud_lng_passport_in_hr" = "Croatian";
"cloud_lng_passport_in_hu" = "Hungarian";
"cloud_lng_passport_in_hy" = "Armenian";
"cloud_lng_passport_in_id" = "Indonesian";
"cloud_lng_passport_in_is" = "Icelandic";
"cloud_lng_passport_in_it" = "Italian";
"cloud_lng_passport_in_ja" = "Japanese";
"cloud_lng_passport_in_ka" = "Georgian";
"cloud_lng_passport_in_km" = "Khmer";
"cloud_lng_passport_in_ko" = "Korean";
"cloud_lng_passport_in_lo" = "Lao";
"cloud_lng_passport_in_lt" = "Lithuanian";
"cloud_lng_passport_in_lv" = "Latvian";
"cloud_lng_passport_in_mk" = "Macedonian";
"cloud_lng_passport_in_mn" = "Mongolian";
"cloud_lng_passport_in_ms" = "Malay";
"cloud_lng_passport_in_my" = "Burmese";
"cloud_lng_passport_in_ne" = "Nepali";
"cloud_lng_passport_in_nl" = "Dutch";
"cloud_lng_passport_in_pl" = "Polish";
"cloud_lng_passport_in_pt" = "Portuguese";
"cloud_lng_passport_in_ro" = "Romanian";
"cloud_lng_passport_in_ru" = "Russian";
"cloud_lng_passport_in_sk" = "Slovak";
"cloud_lng_passport_in_sl" = "Slovenian";
"cloud_lng_passport_in_th" = "Thai";
"cloud_lng_passport_in_tk" = "Turkmen";
"cloud_lng_passport_in_tr" = "Turkish";
"cloud_lng_passport_in_uk" = "Ukrainian";
"cloud_lng_passport_in_uz" = "Uzbek";
"cloud_lng_passport_in_vi" = "Vietnamese";

View File

@ -1553,7 +1553,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_passport_password_wrong" = "The password you entered is not valid.";
"lng_passport_header" = "Requested information";
"lng_passport_identity_title" = "Identity document";
"lng_passport_identity_description" = "Upload a scan of your passport or another ID";
"lng_passport_identity_description" = "Upload proof of your identity";
"lng_passport_identity_passport" = "Passport";
"lng_passport_identity_passport_upload" = "Upload a scan of your passport";
"lng_passport_identity_card" = "Identity card";
@ -1632,6 +1632,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_passport_expiry_date" = "Expiry date";
"lng_passport_native_name_title" = "Name in document language";
"lng_passport_native_name_about" = "Your name in the language of the country ({country}) that issued the document.";
"lng_passport_native_name_language" = "Your name in {language}";
"lng_passport_native_name_language_about" = "Your name in the language of the country that issued the document.";
"lng_passport_address" = "Address";
"lng_passport_address_enter" = "Please provide your address";
"lng_passport_street" = "Street";

View File

@ -19,7 +19,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Lang {
CloudManager::CloudManager(Instance &langpack, not_null<MTP::Instance*> mtproto) : MTP::Sender()
CloudManager::CloudManager(
Instance &langpack,
not_null<MTP::Instance*> mtproto)
: MTP::Sender()
, _langpack(langpack) {
requestLangPackDifference();
}

View File

@ -433,7 +433,8 @@ void Instance::applyDifference(const MTPDlangPackDifference &difference) {
_updated.notify();
}
std::map<LangKey, QString> Instance::ParseStrings(const MTPVector<MTPLangPackString> &strings) {
std::map<LangKey, QString> Instance::ParseStrings(
const MTPVector<MTPLangPackString> &strings) {
auto result = std::map<LangKey, QString>();
for (auto &mtpString : strings.v) {
HandleString(mtpString, [&result](auto &&key, auto &&value) {
@ -449,10 +450,16 @@ std::map<LangKey, QString> Instance::ParseStrings(const MTPVector<MTPLangPackStr
}
template <typename Result>
LangKey Instance::ParseKeyValue(const QByteArray &key, const QByteArray &value, Result &result) {
LangKey Instance::ParseKeyValue(
const QByteArray &key,
const QByteArray &value,
Result &result) {
auto keyIndex = GetKeyIndex(QLatin1String(key));
if (keyIndex == kLangKeysCount) {
LOG(("Lang Error: Unknown key '%1'").arg(QString::fromLatin1(key)));
if (!key.startsWith("cloud_")) {
LOG(("Lang Warning: Unknown key '%1'"
).arg(QString::fromLatin1(key)));
}
return kLangKeysCount;
}
@ -464,6 +471,13 @@ LangKey Instance::ParseKeyValue(const QByteArray &key, const QByteArray &value,
return kLangKeysCount;
}
QString Instance::getNonDefaultValue(const QByteArray &key) const {
const auto i = _nonDefaultValues.find(key);
return (i != end(_nonDefaultValues))
? QString::fromUtf8(i->second)
: QString();
}
void Instance::applyValue(const QByteArray &key, const QByteArray &value) {
_nonDefaultValues[key] = value;
auto index = ParseKeyValue(key, value, _values);

View File

@ -85,6 +85,7 @@ public:
return _values[key];
}
QString getNonDefaultValue(const QByteArray &key) const;
bool isNonDefaultPlural(LangKey key) const {
Expects(key >= 0 && key < kLangKeysCount);
Expects(_nonDefaultSet.size() == kLangKeysCount);

View File

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "passport/passport_encryption.h"
#include "passport/passport_panel_controller.h"
#include "passport/passport_panel_edit_document.h"
#include "boxes/confirm_box.h"
#include "boxes/passcode_box.h"
#include "lang/lang_keys.h"
@ -34,6 +35,8 @@ constexpr auto kTranslationScansLimit = 20;
constexpr auto kShortPollTimeout = TimeMs(3000);
constexpr auto kRememberCredentialsDelay = TimeMs(1800 * 1000);
Config GlobalConfig;
bool ForwardServiceErrorRequired(const QString &error) {
return (error == qstr("BOT_INVALID"))
|| (error == qstr("PUBLIC_KEY_REQUIRED"))
@ -228,6 +231,44 @@ QString ValidateUrl(const QString &url) {
} // namespace
Config &ConfigInstance() {
return GlobalConfig;
}
Config ParseConfig(const MTPhelp_PassportConfig &data) {
return data.match([](const MTPDhelp_passportConfig &data) {
auto result = Config();
result.hash = data.vhash.v;
auto error = QJsonParseError{ 0, QJsonParseError::NoError };
const auto document = QJsonDocument::fromJson(
data.vcountries_langs.c_dataJSON().vdata.v,
&error);
if (error.error != QJsonParseError::NoError) {
LOG(("API Error: Failed to parse passport config, error: %1."
).arg(error.errorString()));
return result;
} else if (!document.isObject()) {
LOG(("API Error: Not an object received in passport config."));
return result;
}
const auto object = document.object();
for (auto i = object.constBegin(); i != object.constEnd(); ++i) {
const auto countryCode = i.key();
const auto language = i.value();
if (!language.isString()) {
LOG(("API Error: Not a string in passport config item."));
continue;
}
result.languagesByCountryCode.emplace(
countryCode,
language.toString());
}
return result;
}, [](const MTPDhelp_passportConfigNotModified &data) {
return ConfigInstance();
});
}
QString NonceNameByScope(const QString &scope) {
if (scope.startsWith('{') && scope.endsWith('}')) {
return qsl("nonce");
@ -579,6 +620,7 @@ FormController::FormController(
void FormController::show() {
requestForm();
requestPassword();
requestConfig();
}
UserData *FormController::bot() const {
@ -1082,6 +1124,7 @@ void FormController::decryptValues() {
decryptValue(value);
}
fillErrors();
fillNativeFromFallback();
}
void FormController::fillErrors() {
@ -1178,6 +1221,42 @@ void FormController::fillErrors() {
}
}
void FormController::fillNativeFromFallback() {
const auto i = _form.values.find(Value::Type::PersonalDetails);
if (i == end(_form.values) || !i->second.nativeNames) {
return;
}
const auto scheme = GetDocumentScheme(
Scope::Type::PersonalDetails,
base::none,
true);
auto changed = false;
auto values = i->second.data.parsed;
using Scheme = EditDocumentScheme;
for (const auto &row : scheme.rows) {
if (row.valueClass == Scheme::ValueClass::Additional) {
auto &field = values.fields[row.key];
if (!field.text.isEmpty() || !field.error.isEmpty()) {
return;
}
const auto i = values.fields.find(row.additionalFallbackKey);
const auto value = (i == end(values.fields))
? QString()
: i->second.text;
if (row.error(value).has_value()) {
return;
} else if (field.text != value) {
field.text = value;
changed = true;
}
}
}
if (changed) {
startValueEdit(&i->second);
saveValueEdit(&i->second, std::move(values));
}
}
void FormController::decryptValue(Value &value) const {
Expects(!_secret.empty());
@ -2375,11 +2454,29 @@ auto FormController::findFile(const FileKey &key)
void FormController::formDone(const MTPaccount_AuthorizationForm &result) {
if (!parseForm(result)) {
_view->showCriticalError(lang(lng_passport_form_error));
} else if (!_passwordRequestId) {
} else {
showForm();
}
}
void FormController::requestConfig() {
const auto i = _form.values.find(Value::Type::PersonalDetails);
if (i == end(_form.values) || !i->second.nativeNames) {
return;
}
const auto hash = ConfigInstance().hash;
_configRequestId = request(MTPhelp_GetPassportConfig(
MTP_int(hash)
)).done([=](const MTPhelp_PassportConfig &result) {
_configRequestId = 0;
ConfigInstance() = ParseConfig(result);
showForm();
}).fail([=](const RPCError &error) {
_configRequestId = 0;
showForm();
}).send();
}
bool FormController::parseForm(const MTPaccount_AuthorizationForm &result) {
Expects(result.type() == mtpc_account_authorizationForm);
@ -2458,7 +2555,7 @@ void FormController::passwordDone(const MTPaccount_Password &result) {
Expects(result.type() == mtpc_account_password);
const auto changed = applyPassword(result.c_account_password());
if (changed && !_formRequestId) {
if (changed) {
showForm();
}
shortPollEmailConfirmation();
@ -2473,7 +2570,9 @@ void FormController::shortPollEmailConfirmation() {
}
void FormController::showForm() {
if (!_bot) {
if (_formRequestId || _passwordRequestId || _configRequestId) {
return;
} else if (!_bot) {
formFail(Lang::Hard::NoAuthorizationBot());
return;
}

View File

@ -25,6 +25,13 @@ class Controller;
namespace Passport {
struct Config {
int32 hash = 0;
std::map<QString, QString> languagesByCountryCode;
};
Config &ConfigInstance();
Config ParseConfig(const MTPhelp_PassportConfig &data);
struct SavedCredentials {
bytes::vector hashForAuth;
bytes::vector hashForSecret;
@ -387,6 +394,7 @@ private:
void requestForm();
void requestPassword();
void requestConfig();
void formDone(const MTPaccount_AuthorizationForm &result);
void formFail(const QString &error);
@ -436,6 +444,7 @@ private:
bool validateValueSecrets(Value &value) const;
void resetValue(Value &value) const;
void fillErrors();
void fillNativeFromFallback();
void loadFile(File &file);
void fileLoadDone(FileKey key, const QByteArray &bytes);
@ -505,6 +514,7 @@ private:
mtpRequestId _formRequestId = 0;
mtpRequestId _passwordRequestId = 0;
mtpRequestId _passwordCheckRequestId = 0;
mtpRequestId _configRequestId = 0;
PasswordSettings _password;
TimeMs _lastSrpIdInvalidTime = 0;

View File

@ -32,6 +32,7 @@ constexpr auto kMaxStreetSize = 64;
constexpr auto kMinCitySize = 2;
constexpr auto kMaxCitySize = 64;
constexpr auto kMaxPostcodeSize = 10;
const auto kLanguageNamePrefix = "cloud_lng_passport_in_";
ScanInfo CollectScanInfo(const EditFile &file) {
const auto status = [&] {
@ -282,8 +283,33 @@ EditDocumentScheme GetDocumentScheme(
};
if (nativeNames) {
result.additionalDependencyKey = qsl("residence_country_code");
result.additionalHeader = lang(lng_passport_native_name_title);
result.additionalDescription = [](const QString &countryCode) {
const auto languageValue = [](const QString &countryCode) {
if (countryCode.isEmpty()) {
return QString();
}
const auto &config = ConfigInstance();
const auto i = config.languagesByCountryCode.find(
countryCode);
if (i == end(config.languagesByCountryCode)) {
return QString();
}
return Lang::Current().getNonDefaultValue(
kLanguageNamePrefix + i->second.toUtf8());
};
result.additionalHeader = [=](const QString &countryCode) {
const auto language = languageValue(countryCode);
return language.isEmpty()
? lang(lng_passport_native_name_title)
: lng_passport_native_name_language(
lt_language,
language);
};
result.additionalDescription = [=](const QString &countryCode) {
const auto language = languageValue(countryCode);
if (!language.isEmpty()) {
return lang(lng_passport_native_name_language_about);
}
const auto name = CountrySelectBox::NameByISO(countryCode);
Assert(!name.isEmpty());
return lng_passport_native_name_about(
@ -291,7 +317,18 @@ EditDocumentScheme GetDocumentScheme(
name);
};
result.additionalShown = [](const QString &countryCode) {
return !countryCode.isEmpty();
using Result = EditDocumentScheme::AdditionalVisibility;
if (countryCode.isEmpty()) {
return Result::Hidden;
}
const auto &config = ConfigInstance();
const auto i = config.languagesByCountryCode.find(
countryCode);
if (i != end(config.languagesByCountryCode)
&& i->second == "en") {
return Result::OnlyIfError;
}
return Result::Shown;
};
using Row = EditDocumentScheme::Row;
auto additional = std::initializer_list<Row>{
@ -303,6 +340,8 @@ EditDocumentScheme GetDocumentScheme(
NativeNameValidate,
DontFormat,
kMaxNameSize,
QString(),
qsl("first_name"),
},
{
ValueClass::Additional,
@ -313,6 +352,7 @@ EditDocumentScheme GetDocumentScheme(
DontFormat,
kMaxNameSize,
qsl("first_name_native"),
qsl("middle_name"),
},
{
ValueClass::Additional,
@ -323,6 +363,7 @@ EditDocumentScheme GetDocumentScheme(
DontFormat,
kMaxNameSize,
qsl("first_name_native"),
qsl("last_name"),
},
};
for (auto &row : additional) {

View File

@ -404,11 +404,47 @@ not_null<Ui::RpWidget*> PanelEditDocument::setupContent(
inner,
object_ptr<Ui::VerticalLayout>(inner)));
const auto added = wrap->entity();
auto showIfError = false;
enumerateRows([&](
int i,
const Scheme::Row &row,
const ValueMap &fields) {
if (row.valueClass != Scheme::ValueClass::Additional) {
return;
}
const auto it = fields.fields.find(row.key);
if (it == end(fields.fields)) {
return;
} else if (!it->second.error.isEmpty()) {
showIfError = true;
} else if (it->second.text.isEmpty()) {
return;
}
const auto fallbackIt = fields.fields.find(
row.additionalFallbackKey);
if (fallbackIt != end(fields.fields)
&& fallbackIt->second.text != it->second.text) {
showIfError = true;
}
});
const auto shown = [=](const QString &code) {
using Result = Scheme::AdditionalVisibility;
const auto value = _scheme.additionalShown(code);
return (value == Result::Shown)
|| (value == Result::OnlyIfError && showIfError);
};
auto title = row->value(
) | rpl::filter(
shown
) | rpl::map([=](const QString &code) {
return _scheme.additionalHeader(code);
});
added->add(
object_ptr<Ui::FlatLabel>(
added,
_scheme.additionalHeader,
Ui::FlatLabel::InitType::Simple,
std::move(title),
st::passportFormHeader),
st::passportNativeNameHeaderPadding);
@ -422,9 +458,9 @@ not_null<Ui::RpWidget*> PanelEditDocument::setupContent(
});
auto description = row->value(
) | rpl::filter([=](const QString &code) {
return _scheme.additionalShown(code);
}) | rpl::map([=](const QString &code) {
) | rpl::filter(
shown
) | rpl::map([=](const QString &code) {
return _scheme.additionalDescription(code);
});
added->add(
@ -437,11 +473,15 @@ not_null<Ui::RpWidget*> PanelEditDocument::setupContent(
st::passportFormLabelPadding),
st::passportNativeNameAboutMargin);
wrap->toggleOn(row->value(
) | rpl::map([=](const QString &code) {
return _scheme.additionalShown(code);
}));
wrap->toggleOn(row->value() | rpl::map(shown));
wrap->finishAnimating();
row->value(
) | rpl::map(
shown
) | rpl::start_with_next([=](bool visible) {
_additionalShown = visible;
}, lifetime());
}
inner->add(
@ -469,8 +509,8 @@ void PanelEditDocument::createDetailsRow(
const ValueMap &fields,
int maxLabelWidth) {
const auto valueOrEmpty = [&](
const ValueMap &values,
const QString &key) {
const ValueMap &values,
const QString &key) {
const auto &fields = values.fields;
if (const auto i = fields.find(key); i != fields.end()) {
return i->second;
@ -561,11 +601,31 @@ PanelEditDocument::Result PanelEditDocument::collect() const {
auto &fields = (row.valueClass == Scheme::ValueClass::Scans)
? result.filesData
: result.data;
if (row.valueClass == Scheme::ValueClass::Additional
&& !_additionalShown) {
continue;
}
fields.fields[row.key].text = field->valueCurrent();
}
if (!_additionalShown) {
fillAdditionalFromFallbacks(result);
}
return result;
}
void PanelEditDocument::fillAdditionalFromFallbacks(Result &result) const {
for (const auto &row : _scheme.rows) {
if (row.valueClass != Scheme::ValueClass::Additional) {
continue;
}
Assert(!row.additionalFallbackKey.isEmpty());
auto &fields = result.data;
const auto j = fields.fields.find(row.additionalFallbackKey);
Assert(j != end(fields.fields));
fields.fields[row.key] = j->second;
}
}
bool PanelEditDocument::validate() {
auto error = _editScans
? _editScans->validateGetErrorTop()
@ -585,6 +645,10 @@ bool PanelEditDocument::validate() {
auto first = QPointer<PanelDetailsRow>();
for (const auto [i, field] : base::reversed(_details)) {
const auto &row = _scheme.rows[i];
if (row.valueClass == Scheme::ValueClass::Additional
&& !_additionalShown) {
continue;
}
if (field->errorShown()) {
field->showError();
first = field;

View File

@ -44,6 +44,11 @@ struct EditDocumentScheme {
Additional,
Scans,
};
enum class AdditionalVisibility {
Hidden,
OnlyIfError,
Shown,
};
struct Row {
using Validator = Fn<base::optional<QString>(const QString &value)>;
using Formatter = Fn<QString(const QString &value)>;
@ -54,7 +59,8 @@ struct EditDocumentScheme {
Validator error;
Formatter format;
int lengthLimit = 0;
QString keyForAttachmentTo; // attach [last|middle]_name to first_*
QString keyForAttachmentTo; // Attach [last|middle]_name to first_*.
QString additionalFallbackKey; // *_name_native from *_name.
};
std::vector<Row> rows;
QString fieldsHeader;
@ -62,8 +68,8 @@ struct EditDocumentScheme {
QString scansHeader;
QString additionalDependencyKey;
Fn<bool(const QString &dependency)> additionalShown;
QString additionalHeader;
Fn<AdditionalVisibility(const QString &dependency)> additionalShown;
Fn<QString(const QString &dependency)> additionalHeader;
Fn<QString(const QString &dependency)> additionalDescription;
};
@ -127,6 +133,7 @@ private:
void updateCommonError();
Result collect() const;
void fillAdditionalFromFallbacks(Result &result) const;
bool validate();
void save();
@ -149,6 +156,7 @@ private:
QPointer<Ui::SlideWrap<Ui::FlatLabel>> _commonError;
std::map<int, QPointer<PanelDetailsRow>> _details;
bool _fieldsChanged = false;
bool _additionalShown = false;
QPointer<Info::Profile::Button> _delete;

View File

@ -109,6 +109,7 @@
'<@(style_files)',
'<!@(<(list_sources_command) <(qt_moc_list_sources_arg))',
'telegram_sources.txt',
'<(res_loc)/langs/cloud_lang.strings',
'<(res_loc)/export_html/css/style.css',
'<(res_loc)/export_html/js/script.js',
'<(res_loc)/export_html/images/back.png',