From b8b5ab637801311f8be78881bd4a565e0a795d70 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Mon, 20 Aug 2018 15:40:41 +0300
Subject: [PATCH] Improve native name phrases in passport.

Also auto-save *_name to *_name_latin for english countries.
---
 Telegram/Resources/langs/cloud_lang.strings   |  55 +++++++++
 Telegram/Resources/langs/lang.strings         |   4 +-
 .../SourceFiles/lang/lang_cloud_manager.cpp   |   5 +-
 Telegram/SourceFiles/lang/lang_instance.cpp   |  20 +++-
 Telegram/SourceFiles/lang/lang_instance.h     |   1 +
 .../passport/passport_form_controller.cpp     | 105 +++++++++++++++++-
 .../passport/passport_form_controller.h       |  10 ++
 .../passport/passport_panel_controller.cpp    |  47 +++++++-
 .../passport/passport_panel_edit_document.cpp |  86 ++++++++++++--
 .../passport/passport_panel_edit_document.h   |  14 ++-
 Telegram/gyp/Telegram.gyp                     |   1 +
 11 files changed, 323 insertions(+), 25 deletions(-)
 create mode 100644 Telegram/Resources/langs/cloud_lang.strings

diff --git a/Telegram/Resources/langs/cloud_lang.strings b/Telegram/Resources/langs/cloud_lang.strings
new file mode 100644
index 0000000000..ad20ff187a
--- /dev/null
+++ b/Telegram/Resources/langs/cloud_lang.strings
@@ -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";
diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings
index 00053b0080..25abe7f5c6 100644
--- a/Telegram/Resources/langs/lang.strings
+++ b/Telegram/Resources/langs/lang.strings
@@ -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";
diff --git a/Telegram/SourceFiles/lang/lang_cloud_manager.cpp b/Telegram/SourceFiles/lang/lang_cloud_manager.cpp
index 09f1f73662..5a692a366e 100644
--- a/Telegram/SourceFiles/lang/lang_cloud_manager.cpp
+++ b/Telegram/SourceFiles/lang/lang_cloud_manager.cpp
@@ -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();
 }
diff --git a/Telegram/SourceFiles/lang/lang_instance.cpp b/Telegram/SourceFiles/lang/lang_instance.cpp
index 9297b472cd..9bc0a43af4 100644
--- a/Telegram/SourceFiles/lang/lang_instance.cpp
+++ b/Telegram/SourceFiles/lang/lang_instance.cpp
@@ -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);
diff --git a/Telegram/SourceFiles/lang/lang_instance.h b/Telegram/SourceFiles/lang/lang_instance.h
index 586e1d9fc9..4e51494de0 100644
--- a/Telegram/SourceFiles/lang/lang_instance.h
+++ b/Telegram/SourceFiles/lang/lang_instance.h
@@ -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);
diff --git a/Telegram/SourceFiles/passport/passport_form_controller.cpp b/Telegram/SourceFiles/passport/passport_form_controller.cpp
index 114132d211..d43e2dd617 100644
--- a/Telegram/SourceFiles/passport/passport_form_controller.cpp
+++ b/Telegram/SourceFiles/passport/passport_form_controller.cpp
@@ -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;
 	}
diff --git a/Telegram/SourceFiles/passport/passport_form_controller.h b/Telegram/SourceFiles/passport/passport_form_controller.h
index f84b6e5c36..ec6b09cbe9 100644
--- a/Telegram/SourceFiles/passport/passport_form_controller.h
+++ b/Telegram/SourceFiles/passport/passport_form_controller.h
@@ -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;
diff --git a/Telegram/SourceFiles/passport/passport_panel_controller.cpp b/Telegram/SourceFiles/passport/passport_panel_controller.cpp
index 7cc5282491..9daaa5c7bd 100644
--- a/Telegram/SourceFiles/passport/passport_panel_controller.cpp
+++ b/Telegram/SourceFiles/passport/passport_panel_controller.cpp
@@ -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) {
diff --git a/Telegram/SourceFiles/passport/passport_panel_edit_document.cpp b/Telegram/SourceFiles/passport/passport_panel_edit_document.cpp
index 2f0ece3fb6..d560a933cb 100644
--- a/Telegram/SourceFiles/passport/passport_panel_edit_document.cpp
+++ b/Telegram/SourceFiles/passport/passport_panel_edit_document.cpp
@@ -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;
diff --git a/Telegram/SourceFiles/passport/passport_panel_edit_document.h b/Telegram/SourceFiles/passport/passport_panel_edit_document.h
index 99dde2c9d2..bb37adc146 100644
--- a/Telegram/SourceFiles/passport/passport_panel_edit_document.h
+++ b/Telegram/SourceFiles/passport/passport_panel_edit_document.h
@@ -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;
 
diff --git a/Telegram/gyp/Telegram.gyp b/Telegram/gyp/Telegram.gyp
index 43cce710f1..8312147042 100644
--- a/Telegram/gyp/Telegram.gyp
+++ b/Telegram/gyp/Telegram.gyp
@@ -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',