diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp index 6b865fd4705..f60229cbda0 100644 --- a/core/config/project_settings.cpp +++ b/core/config/project_settings.cpp @@ -1633,7 +1633,7 @@ ProjectSettings::ProjectSettings() { #endif GLOBAL_DEF_BASIC("application/config/name", ""); - GLOBAL_DEF_BASIC(PropertyInfo(Variant::DICTIONARY, "application/config/name_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()); + GLOBAL_DEF(PropertyInfo(Variant::DICTIONARY, "application/config/name_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()); GLOBAL_DEF_BASIC(PropertyInfo(Variant::STRING, "application/config/description", PROPERTY_HINT_MULTILINE_TEXT), ""); GLOBAL_DEF_BASIC("application/config/version", ""); GLOBAL_DEF_INTERNAL(PropertyInfo(Variant::STRING, "application/config/tags"), PackedStringArray()); diff --git a/core/string/translation_server.cpp b/core/string/translation_server.cpp index 195788c430e..475c3aaab65 100644 --- a/core/string/translation_server.cpp +++ b/core/string/translation_server.cpp @@ -611,7 +611,10 @@ void TranslationServer::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::Type::BOOL, "pseudolocalization_enabled"), "set_pseudolocalization_enabled", "is_pseudolocalization_enabled"); } -void TranslationServer::load_translations() { +void TranslationServer::load_project_translations(Ref p_domain) { + DEV_ASSERT(p_domain.is_valid()); + + p_domain->clear(); const String prop = "internationalization/locale/translations"; if (!ProjectSettings::get_singleton()->has_setting(prop)) { return; @@ -620,7 +623,7 @@ void TranslationServer::load_translations() { for (const String &path : translations) { Ref tr = ResourceLoader::load(path); if (tr.is_valid()) { - add_translation(tr); + p_domain->add_translation(tr); } } } diff --git a/core/string/translation_server.h b/core/string/translation_server.h index 7e68d8854e6..e2c7e101a68 100644 --- a/core/string/translation_server.h +++ b/core/string/translation_server.h @@ -151,7 +151,7 @@ public: void clear(); - void load_translations(); + void load_project_translations(Ref p_domain); #ifdef TOOLS_ENABLED virtual void get_argument_options(const StringName &p_function, int p_idx, List *r_options) const override; diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index a8078b2a4f2..e37f099cf38 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -338,6 +338,7 @@ Translations of the project's name. This setting is used by OS tools to translate application name on Android, iOS and macOS. + [b]Note:[/b] When left empty, the application name is translated using the project translations. Specifies a file to override project settings. For example: [code]user://custom_settings.cfg[/code]. See "Overriding" in the [ProjectSettings] class description at the top for more information. diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 16350ad1013..2efe5fdd253 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -578,8 +578,7 @@ void EditorNode::_gdextensions_reloaded() { void EditorNode::_update_translations() { Ref main = TranslationServer::get_singleton()->get_main_domain(); - main->clear(); - TranslationServer::get_singleton()->load_translations(); + TranslationServer::get_singleton()->load_project_translations(main); if (main->is_enabled()) { // Check for the exact locale. diff --git a/editor/export/editor_export_platform_apple_embedded.cpp b/editor/export/editor_export_platform_apple_embedded.cpp index ec4480082f5..464c3dce4e8 100644 --- a/editor/export/editor_export_platform_apple_embedded.cpp +++ b/editor/export/editor_export_platform_apple_embedded.cpp @@ -32,7 +32,7 @@ #include "core/io/json.h" #include "core/io/plist.h" -#include "core/string/translation.h" +#include "core/string/translation_server.h" #include "editor/editor_node.h" #include "editor/editor_string_names.h" #include "editor/export/editor_export.h" @@ -1915,42 +1915,51 @@ Error EditorExportPlatformAppleEmbedded::_export_project_helper(const Refget("privacy/camera_usage_description_localized"); Dictionary microphone_usage_descriptions = p_preset->get("privacy/microphone_usage_description_localized"); Dictionary photolibrary_usage_descriptions = p_preset->get("privacy/photolibrary_usage_description_localized"); - Vector translations = get_project_setting(p_preset, "internationalization/locale/translations"); - if (translations.size() > 0) { + const String project_name = get_project_setting(p_preset, "application/config/name"); + const Dictionary appnames = get_project_setting(p_preset, "application/config/name_localized"); + const StringName domain_name = "godot.project_name_localization"; + Ref domain = TranslationServer::get_singleton()->get_or_add_domain(domain_name); + TranslationServer::get_singleton()->load_project_translations(domain); + const Vector locales = domain->get_loaded_locales(); + + if (!locales.is_empty()) { { String fname = binary_dir + "/en.lproj"; tmp_app_path->make_dir_recursive(fname); Ref f = FileAccess::open(fname + "/InfoPlist.strings", FileAccess::WRITE); f->store_line("/* Localized versions of Info.plist keys */"); f->store_line(""); - f->store_line("CFBundleDisplayName = \"" + get_project_setting(p_preset, "application/config/name").operator String() + "\";"); + f->store_line("CFBundleDisplayName = \"" + project_name + "\";"); f->store_line("NSCameraUsageDescription = \"" + p_preset->get("privacy/camera_usage_description").operator String() + "\";"); f->store_line("NSMicrophoneUsageDescription = \"" + p_preset->get("privacy/microphone_usage_description").operator String() + "\";"); f->store_line("NSPhotoLibraryUsageDescription = \"" + p_preset->get("privacy/photolibrary_usage_description").operator String() + "\";"); } - HashSet languages; - for (const String &E : translations) { - Ref tr = ResourceLoader::load(E); - if (tr.is_valid() && tr->get_locale() != "en") { - languages.insert(tr->get_locale()); + for (const String &lang : locales) { + if (lang == "en") { + continue; } - } - for (const String &lang : languages) { String fname = binary_dir + "/" + lang + ".lproj"; tmp_app_path->make_dir_recursive(fname); Ref f = FileAccess::open(fname + "/InfoPlist.strings", FileAccess::WRITE); f->store_line("/* Localized versions of Info.plist keys */"); f->store_line(""); - if (appnames.has(lang)) { + + if (appnames.is_empty()) { + domain->set_locale_override(lang); + const String &name = domain->translate(project_name, String()); + if (name != project_name) { + f->store_line("CFBundleDisplayName = \"" + name + "\";"); + } + } else if (appnames.has(lang)) { f->store_line("CFBundleDisplayName = \"" + appnames[lang].operator String() + "\";"); } + if (camera_usage_descriptions.has(lang)) { f->store_line("NSCameraUsageDescription = \"" + camera_usage_descriptions[lang].operator String() + "\";"); } diff --git a/editor/translations/localization_editor.cpp b/editor/translations/localization_editor.cpp index 43e9b228a0e..4785eeacf19 100644 --- a/editor/translations/localization_editor.cpp +++ b/editor/translations/localization_editor.cpp @@ -405,11 +405,6 @@ void LocalizationEditor::_template_generate_open() { void LocalizationEditor::_template_add_builtin_toggled() { ProjectSettings::get_singleton()->set_setting("internationalization/locale/translation_add_builtin_strings_to_pot", template_add_builtin->is_pressed()); ProjectSettings::get_singleton()->save(); - - const PackedStringArray sources = GLOBAL_GET("internationalization/locale/translations_pot_files"); - if (sources.is_empty()) { - template_generate_button->set_disabled(!template_add_builtin->is_pressed()); - } } void LocalizationEditor::_template_generate(const String &p_file) { @@ -724,8 +719,6 @@ void LocalizationEditor::update_translations() { // New translation parser plugin might extend possible file extensions in template generation. _update_template_source_file_extensions(); - template_generate_button->set_disabled(sources.is_empty() && !template_add_builtin->is_pressed()); - updating_translations = false; } diff --git a/editor/translations/template_generator.cpp b/editor/translations/template_generator.cpp index 7b9c1566537..0a89a2cb0c0 100644 --- a/editor/translations/template_generator.cpp +++ b/editor/translations/template_generator.cpp @@ -64,6 +64,13 @@ TranslationTemplateGenerator::MessageMap TranslationTemplateGenerator::parse(con } } + if (GLOBAL_GET("application/config/name_localized").operator Dictionary().is_empty()) { + const String &project_name = GLOBAL_GET("application/config/name"); + if (!project_name.is_empty()) { + raw.push_back({ project_name, String(), String(), String(), String() }); + } + } + MessageMap result; for (const Vector &entry : raw) { const String &msgid = entry[0]; @@ -95,7 +102,7 @@ void TranslationTemplateGenerator::generate(const String &p_file) { const MessageMap &map = parse(files, add_builtin); if (map.is_empty()) { - WARN_PRINT("No translatable strings found."); + WARN_PRINT_ED(TTR("No translatable strings found.")); return; } diff --git a/main/main.cpp b/main/main.cpp index 1dc73e60040..0c0d12e0c00 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -772,7 +772,7 @@ Error Main::test_setup() { if (!locale.is_empty()) { translation_server->set_locale(locale); } - translation_server->load_translations(); + translation_server->load_project_translations(translation_server->get_main_domain()); ResourceLoader::load_translation_remaps(); //load remaps for resources // Initialize ThemeDB early so that scene types can register their theme items. @@ -3456,7 +3456,7 @@ Error Main::setup2(bool p_show_boot_logo) { if (!locale.is_empty()) { translation_server->set_locale(locale); } - translation_server->load_translations(); + translation_server->load_project_translations(translation_server->get_main_domain()); ResourceLoader::load_translation_remaps(); //load remaps for resources OS::get_singleton()->benchmark_end_measure("Startup", "Translations and Remaps"); diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index 65378f64928..81d982c4fd0 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -39,6 +39,7 @@ #include "core/io/image_loader.h" #include "core/io/json.h" #include "core/io/marshalls.h" +#include "core/string/translation_server.h" #include "core/version.h" #include "editor/editor_log.h" #include "editor/editor_node.h" @@ -1770,8 +1771,11 @@ void EditorExportPlatformAndroid::_fix_resources(const Ref & Vector string_table; - String package_name = p_preset->get("package/name"); - Dictionary appnames = get_project_setting(p_preset, "application/config/name_localized"); + const String project_name = get_project_name(p_preset, p_preset->get("package/name")); + const Dictionary appnames = get_project_setting(p_preset, "application/config/name_localized"); + const StringName domain_name = "godot.project_name_localization"; + Ref domain = TranslationServer::get_singleton()->get_or_add_domain(domain_name); + TranslationServer::get_singleton()->load_project_translations(domain); for (uint32_t i = 0; i < string_count; i++) { uint32_t offset = decode_uint32(&r_manifest[string_table_begins + i * 4]); @@ -1779,24 +1783,24 @@ void EditorExportPlatformAndroid::_fix_resources(const Ref & String str = _parse_string(&r_manifest[offset], string_flags & UTF8_FLAG); - if (str.begins_with("godot-project-name")) { - if (str == "godot-project-name") { - //project name - str = get_project_name(p_preset, package_name); + if (str == "godot-project-name") { + str = project_name; + } else if (str.begins_with("godot-project-name")) { + String lang = str.substr(str.rfind_char('-') + 1).replace_char('-', '_'); + if (appnames.is_empty()) { + domain->set_locale_override(lang); + str = domain->translate(project_name, String()); } else { - String lang = str.substr(str.rfind_char('-') + 1).replace_char('-', '_'); - if (appnames.has(lang)) { - str = appnames[lang]; - } else { - str = get_project_name(p_preset, package_name); - } + str = appnames.get(lang, project_name); } } string_table.push_back(str); } + TranslationServer::get_singleton()->remove_domain(domain_name); + //write a new string table, but use 16 bits Vector ret; ret.resize(string_table_begins + string_table.size() * 4); diff --git a/platform/android/export/gradle_export_util.cpp b/platform/android/export/gradle_export_util.cpp index 212ee275bdc..d0dd3c2b484 100644 --- a/platform/android/export/gradle_export_util.cpp +++ b/platform/android/export/gradle_export_util.cpp @@ -30,7 +30,7 @@ #include "gradle_export_util.h" -#include "core/config/project_settings.h" +#include "core/string/translation_server.h" int _get_android_orientation_value(DisplayServer::ScreenOrientation screen_orientation) { switch (screen_orientation) { @@ -219,6 +219,12 @@ Error _create_project_name_strings_files(const Ref &p_preset } return ERR_CANT_OPEN; } + + // Setup a temporary translation domain to translate the project name. + const StringName domain_name = "godot.project_name_localization"; + Ref domain = TranslationServer::get_singleton()->get_or_add_domain(domain_name); + TranslationServer::get_singleton()->load_project_translations(domain); + da->list_dir_begin(); while (true) { String file = da->get_next(); @@ -231,8 +237,15 @@ Error _create_project_name_strings_files(const Ref &p_preset } String locale = file.replace("values-", "").replace("-r", "_"); String locale_directory = p_gradle_build_dir.path_join("res/" + file + "/godot_project_name_string.xml"); - if (p_appnames.has(locale)) { - String locale_project_name = p_appnames[locale]; + + String locale_project_name; + if (p_appnames.is_empty()) { + domain->set_locale_override(locale); + locale_project_name = domain->translate(p_project_name, String()); + } else { + locale_project_name = p_appnames.get(locale, p_project_name); + } + if (locale_project_name != p_project_name) { String processed_xml_string = vformat(GODOT_PROJECT_NAME_XML_STRING, _android_xml_escape(locale_project_name)); print_verbose("Storing project name for locale " + locale + " under " + locale_directory); store_string_at_path(locale_directory, processed_xml_string); @@ -242,6 +255,9 @@ Error _create_project_name_strings_files(const Ref &p_preset } } da->list_dir_end(); + + TranslationServer::get_singleton()->remove_domain(domain_name); + return OK; } diff --git a/platform/macos/export/export_plugin.cpp b/platform/macos/export/export_plugin.cpp index 24d720bc8b0..58baeb89a0c 100644 --- a/platform/macos/export/export_plugin.cpp +++ b/platform/macos/export/export_plugin.cpp @@ -35,7 +35,7 @@ #include "core/io/image_loader.h" #include "core/io/plist.h" -#include "core/string/translation.h" +#include "core/string/translation_server.h" #include "drivers/png/png_driver_common.h" #include "editor/editor_node.h" #include "editor/editor_string_names.h" @@ -1757,7 +1757,6 @@ Error EditorExportPlatformMacOS::export_project(const Ref &p } } - Dictionary appnames = get_project_setting(p_preset, "application/config/name_localized"); Dictionary microphone_usage_descriptions = p_preset->get("privacy/microphone_usage_description_localized"); Dictionary camera_usage_descriptions = p_preset->get("privacy/camera_usage_description_localized"); Dictionary location_usage_descriptions = p_preset->get("privacy/location_usage_description_localized"); @@ -1771,15 +1770,21 @@ Error EditorExportPlatformMacOS::export_project(const Ref &p Dictionary removable_volumes_usage_descriptions = p_preset->get("privacy/removable_volumes_usage_description_localized"); Dictionary copyrights = p_preset->get("application/copyright_localized"); - Vector translations = get_project_setting(p_preset, "internationalization/locale/translations"); - if (translations.size() > 0) { + const String project_name = get_project_setting(p_preset, "application/config/name"); + const Dictionary appnames = get_project_setting(p_preset, "application/config/name_localized"); + const StringName domain_name = "godot.project_name_localization"; + Ref domain = TranslationServer::get_singleton()->get_or_add_domain(domain_name); + TranslationServer::get_singleton()->load_project_translations(domain); + const Vector locales = domain->get_loaded_locales(); + + if (!locales.is_empty()) { { String fname = tmp_app_path_name + "/Contents/Resources/en.lproj"; tmp_app_dir->make_dir_recursive(fname); Ref f = FileAccess::open(fname + "/InfoPlist.strings", FileAccess::WRITE); f->store_line("/* Localized versions of Info.plist keys */"); f->store_line(""); - f->store_line("CFBundleDisplayName = \"" + get_project_setting(p_preset, "application/config/name").operator String() + "\";"); + f->store_line("CFBundleDisplayName = \"" + project_name + "\";"); if (!((String)p_preset->get("privacy/microphone_usage_description")).is_empty()) { f->store_line("NSMicrophoneUsageDescription = \"" + p_preset->get("privacy/microphone_usage_description").operator String() + "\";"); } @@ -1816,23 +1821,27 @@ Error EditorExportPlatformMacOS::export_project(const Ref &p f->store_line("NSHumanReadableCopyright = \"" + p_preset->get("application/copyright").operator String() + "\";"); } - HashSet languages; - for (const String &E : translations) { - Ref tr = ResourceLoader::load(E); - if (tr.is_valid() && tr->get_locale() != "en") { - languages.insert(tr->get_locale()); + for (const String &lang : locales) { + if (lang == "en") { + continue; } - } - for (const String &lang : languages) { String fname = tmp_app_path_name + "/Contents/Resources/" + lang + ".lproj"; tmp_app_dir->make_dir_recursive(fname); Ref f = FileAccess::open(fname + "/InfoPlist.strings", FileAccess::WRITE); f->store_line("/* Localized versions of Info.plist keys */"); f->store_line(""); - if (appnames.has(lang)) { + + if (appnames.is_empty()) { + domain->set_locale_override(lang); + const String &name = domain->translate(project_name, String()); + if (name != project_name) { + f->store_line("CFBundleDisplayName = \"" + name + "\";"); + } + } else if (appnames.has(lang)) { f->store_line("CFBundleDisplayName = \"" + appnames[lang].operator String() + "\";"); } + if (microphone_usage_descriptions.has(lang)) { f->store_line("NSMicrophoneUsageDescription = \"" + microphone_usage_descriptions[lang].operator String() + "\";"); } @@ -1872,6 +1881,8 @@ Error EditorExportPlatformMacOS::export_project(const Ref &p } } + TranslationServer::get_singleton()->remove_domain(domain_name); + // Now process our template. bool found_binary = false;