Merge pull request #112415 from timothyqiu/project-name-localized

Allow localizing the application name with project translations
This commit is contained in:
Thaddeus Crews 2025-11-10 08:20:00 -06:00
commit 9ac55bcc7f
No known key found for this signature in database
GPG key ID: 8C6E5FEB5FC03CCC
12 changed files with 100 additions and 57 deletions

View file

@ -1633,7 +1633,7 @@ ProjectSettings::ProjectSettings() {
#endif #endif
GLOBAL_DEF_BASIC("application/config/name", ""); 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(PropertyInfo(Variant::STRING, "application/config/description", PROPERTY_HINT_MULTILINE_TEXT), "");
GLOBAL_DEF_BASIC("application/config/version", ""); GLOBAL_DEF_BASIC("application/config/version", "");
GLOBAL_DEF_INTERNAL(PropertyInfo(Variant::STRING, "application/config/tags"), PackedStringArray()); GLOBAL_DEF_INTERNAL(PropertyInfo(Variant::STRING, "application/config/tags"), PackedStringArray());

View file

@ -611,7 +611,10 @@ void TranslationServer::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::Type::BOOL, "pseudolocalization_enabled"), "set_pseudolocalization_enabled", "is_pseudolocalization_enabled"); ADD_PROPERTY(PropertyInfo(Variant::Type::BOOL, "pseudolocalization_enabled"), "set_pseudolocalization_enabled", "is_pseudolocalization_enabled");
} }
void TranslationServer::load_translations() { void TranslationServer::load_project_translations(Ref<TranslationDomain> p_domain) {
DEV_ASSERT(p_domain.is_valid());
p_domain->clear();
const String prop = "internationalization/locale/translations"; const String prop = "internationalization/locale/translations";
if (!ProjectSettings::get_singleton()->has_setting(prop)) { if (!ProjectSettings::get_singleton()->has_setting(prop)) {
return; return;
@ -620,7 +623,7 @@ void TranslationServer::load_translations() {
for (const String &path : translations) { for (const String &path : translations) {
Ref<Translation> tr = ResourceLoader::load(path); Ref<Translation> tr = ResourceLoader::load(path);
if (tr.is_valid()) { if (tr.is_valid()) {
add_translation(tr); p_domain->add_translation(tr);
} }
} }
} }

View file

@ -151,7 +151,7 @@ public:
void clear(); void clear();
void load_translations(); void load_project_translations(Ref<TranslationDomain> p_domain);
#ifdef TOOLS_ENABLED #ifdef TOOLS_ENABLED
virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override; virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override;

View file

@ -338,6 +338,7 @@
</member> </member>
<member name="application/config/name_localized" type="Dictionary" setter="" getter="" default="{}"> <member name="application/config/name_localized" type="Dictionary" setter="" getter="" default="{}">
Translations of the project's name. This setting is used by OS tools to translate application name on Android, iOS and macOS. 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.
</member> </member>
<member name="application/config/project_settings_override" type="String" setter="" getter="" default="&quot;&quot;"> <member name="application/config/project_settings_override" type="String" setter="" getter="" default="&quot;&quot;">
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. 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.

View file

@ -578,8 +578,7 @@ void EditorNode::_gdextensions_reloaded() {
void EditorNode::_update_translations() { void EditorNode::_update_translations() {
Ref<TranslationDomain> main = TranslationServer::get_singleton()->get_main_domain(); Ref<TranslationDomain> main = TranslationServer::get_singleton()->get_main_domain();
main->clear(); TranslationServer::get_singleton()->load_project_translations(main);
TranslationServer::get_singleton()->load_translations();
if (main->is_enabled()) { if (main->is_enabled()) {
// Check for the exact locale. // Check for the exact locale.

View file

@ -32,7 +32,7 @@
#include "core/io/json.h" #include "core/io/json.h"
#include "core/io/plist.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_node.h"
#include "editor/editor_string_names.h" #include "editor/editor_string_names.h"
#include "editor/export/editor_export.h" #include "editor/export/editor_export.h"
@ -1915,42 +1915,51 @@ Error EditorExportPlatformAppleEmbedded::_export_project_helper(const Ref<Editor
return ERR_FILE_NOT_FOUND; return ERR_FILE_NOT_FOUND;
} }
Dictionary appnames = get_project_setting(p_preset, "application/config/name_localized");
Dictionary camera_usage_descriptions = p_preset->get("privacy/camera_usage_description_localized"); Dictionary camera_usage_descriptions = p_preset->get("privacy/camera_usage_description_localized");
Dictionary microphone_usage_descriptions = p_preset->get("privacy/microphone_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"); Dictionary photolibrary_usage_descriptions = p_preset->get("privacy/photolibrary_usage_description_localized");
Vector<String> translations = get_project_setting(p_preset, "internationalization/locale/translations"); const String project_name = get_project_setting(p_preset, "application/config/name");
if (translations.size() > 0) { const Dictionary appnames = get_project_setting(p_preset, "application/config/name_localized");
const StringName domain_name = "godot.project_name_localization";
Ref<TranslationDomain> domain = TranslationServer::get_singleton()->get_or_add_domain(domain_name);
TranslationServer::get_singleton()->load_project_translations(domain);
const Vector<String> locales = domain->get_loaded_locales();
if (!locales.is_empty()) {
{ {
String fname = binary_dir + "/en.lproj"; String fname = binary_dir + "/en.lproj";
tmp_app_path->make_dir_recursive(fname); tmp_app_path->make_dir_recursive(fname);
Ref<FileAccess> f = FileAccess::open(fname + "/InfoPlist.strings", FileAccess::WRITE); Ref<FileAccess> f = FileAccess::open(fname + "/InfoPlist.strings", FileAccess::WRITE);
f->store_line("/* Localized versions of Info.plist keys */"); f->store_line("/* Localized versions of Info.plist keys */");
f->store_line(""); 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("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("NSMicrophoneUsageDescription = \"" + p_preset->get("privacy/microphone_usage_description").operator String() + "\";");
f->store_line("NSPhotoLibraryUsageDescription = \"" + p_preset->get("privacy/photolibrary_usage_description").operator String() + "\";"); f->store_line("NSPhotoLibraryUsageDescription = \"" + p_preset->get("privacy/photolibrary_usage_description").operator String() + "\";");
} }
HashSet<String> languages; for (const String &lang : locales) {
for (const String &E : translations) { if (lang == "en") {
Ref<Translation> tr = ResourceLoader::load(E); continue;
if (tr.is_valid() && tr->get_locale() != "en") {
languages.insert(tr->get_locale());
} }
}
for (const String &lang : languages) {
String fname = binary_dir + "/" + lang + ".lproj"; String fname = binary_dir + "/" + lang + ".lproj";
tmp_app_path->make_dir_recursive(fname); tmp_app_path->make_dir_recursive(fname);
Ref<FileAccess> f = FileAccess::open(fname + "/InfoPlist.strings", FileAccess::WRITE); Ref<FileAccess> f = FileAccess::open(fname + "/InfoPlist.strings", FileAccess::WRITE);
f->store_line("/* Localized versions of Info.plist keys */"); f->store_line("/* Localized versions of Info.plist keys */");
f->store_line(""); 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() + "\";"); f->store_line("CFBundleDisplayName = \"" + appnames[lang].operator String() + "\";");
} }
if (camera_usage_descriptions.has(lang)) { if (camera_usage_descriptions.has(lang)) {
f->store_line("NSCameraUsageDescription = \"" + camera_usage_descriptions[lang].operator String() + "\";"); f->store_line("NSCameraUsageDescription = \"" + camera_usage_descriptions[lang].operator String() + "\";");
} }

View file

@ -405,11 +405,6 @@ void LocalizationEditor::_template_generate_open() {
void LocalizationEditor::_template_add_builtin_toggled() { 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()->set_setting("internationalization/locale/translation_add_builtin_strings_to_pot", template_add_builtin->is_pressed());
ProjectSettings::get_singleton()->save(); 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) { 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. // New translation parser plugin might extend possible file extensions in template generation.
_update_template_source_file_extensions(); _update_template_source_file_extensions();
template_generate_button->set_disabled(sources.is_empty() && !template_add_builtin->is_pressed());
updating_translations = false; updating_translations = false;
} }

View file

@ -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; MessageMap result;
for (const Vector<String> &entry : raw) { for (const Vector<String> &entry : raw) {
const String &msgid = entry[0]; const String &msgid = entry[0];
@ -95,7 +102,7 @@ void TranslationTemplateGenerator::generate(const String &p_file) {
const MessageMap &map = parse(files, add_builtin); const MessageMap &map = parse(files, add_builtin);
if (map.is_empty()) { if (map.is_empty()) {
WARN_PRINT("No translatable strings found."); WARN_PRINT_ED(TTR("No translatable strings found."));
return; return;
} }

View file

@ -772,7 +772,7 @@ Error Main::test_setup() {
if (!locale.is_empty()) { if (!locale.is_empty()) {
translation_server->set_locale(locale); 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 ResourceLoader::load_translation_remaps(); //load remaps for resources
// Initialize ThemeDB early so that scene types can register their theme items. // 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()) { if (!locale.is_empty()) {
translation_server->set_locale(locale); 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 ResourceLoader::load_translation_remaps(); //load remaps for resources
OS::get_singleton()->benchmark_end_measure("Startup", "Translations and Remaps"); OS::get_singleton()->benchmark_end_measure("Startup", "Translations and Remaps");

View file

@ -39,6 +39,7 @@
#include "core/io/image_loader.h" #include "core/io/image_loader.h"
#include "core/io/json.h" #include "core/io/json.h"
#include "core/io/marshalls.h" #include "core/io/marshalls.h"
#include "core/string/translation_server.h"
#include "core/version.h" #include "core/version.h"
#include "editor/editor_log.h" #include "editor/editor_log.h"
#include "editor/editor_node.h" #include "editor/editor_node.h"
@ -1770,8 +1771,11 @@ void EditorExportPlatformAndroid::_fix_resources(const Ref<EditorExportPreset> &
Vector<String> string_table; Vector<String> string_table;
String package_name = p_preset->get("package/name"); const String project_name = get_project_name(p_preset, p_preset->get("package/name"));
Dictionary appnames = get_project_setting(p_preset, "application/config/name_localized"); const Dictionary appnames = get_project_setting(p_preset, "application/config/name_localized");
const StringName domain_name = "godot.project_name_localization";
Ref<TranslationDomain> 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++) { for (uint32_t i = 0; i < string_count; i++) {
uint32_t offset = decode_uint32(&r_manifest[string_table_begins + i * 4]); uint32_t offset = decode_uint32(&r_manifest[string_table_begins + i * 4]);
@ -1779,24 +1783,24 @@ void EditorExportPlatformAndroid::_fix_resources(const Ref<EditorExportPreset> &
String str = _parse_string(&r_manifest[offset], string_flags & UTF8_FLAG); String str = _parse_string(&r_manifest[offset], string_flags & UTF8_FLAG);
if (str.begins_with("godot-project-name")) { if (str == "godot-project-name") {
if (str == "godot-project-name") { str = project_name;
//project name } else if (str.begins_with("godot-project-name")) {
str = get_project_name(p_preset, package_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 { } else {
String lang = str.substr(str.rfind_char('-') + 1).replace_char('-', '_'); str = appnames.get(lang, project_name);
if (appnames.has(lang)) {
str = appnames[lang];
} else {
str = get_project_name(p_preset, package_name);
}
} }
} }
string_table.push_back(str); string_table.push_back(str);
} }
TranslationServer::get_singleton()->remove_domain(domain_name);
//write a new string table, but use 16 bits //write a new string table, but use 16 bits
Vector<uint8_t> ret; Vector<uint8_t> ret;
ret.resize(string_table_begins + string_table.size() * 4); ret.resize(string_table_begins + string_table.size() * 4);

View file

@ -30,7 +30,7 @@
#include "gradle_export_util.h" #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) { int _get_android_orientation_value(DisplayServer::ScreenOrientation screen_orientation) {
switch (screen_orientation) { switch (screen_orientation) {
@ -219,6 +219,12 @@ Error _create_project_name_strings_files(const Ref<EditorExportPreset> &p_preset
} }
return ERR_CANT_OPEN; return ERR_CANT_OPEN;
} }
// Setup a temporary translation domain to translate the project name.
const StringName domain_name = "godot.project_name_localization";
Ref<TranslationDomain> domain = TranslationServer::get_singleton()->get_or_add_domain(domain_name);
TranslationServer::get_singleton()->load_project_translations(domain);
da->list_dir_begin(); da->list_dir_begin();
while (true) { while (true) {
String file = da->get_next(); String file = da->get_next();
@ -231,8 +237,15 @@ Error _create_project_name_strings_files(const Ref<EditorExportPreset> &p_preset
} }
String locale = file.replace("values-", "").replace("-r", "_"); String locale = file.replace("values-", "").replace("-r", "_");
String locale_directory = p_gradle_build_dir.path_join("res/" + file + "/godot_project_name_string.xml"); 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)); 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); print_verbose("Storing project name for locale " + locale + " under " + locale_directory);
store_string_at_path(locale_directory, processed_xml_string); store_string_at_path(locale_directory, processed_xml_string);
@ -242,6 +255,9 @@ Error _create_project_name_strings_files(const Ref<EditorExportPreset> &p_preset
} }
} }
da->list_dir_end(); da->list_dir_end();
TranslationServer::get_singleton()->remove_domain(domain_name);
return OK; return OK;
} }

View file

@ -35,7 +35,7 @@
#include "core/io/image_loader.h" #include "core/io/image_loader.h"
#include "core/io/plist.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 "drivers/png/png_driver_common.h"
#include "editor/editor_node.h" #include "editor/editor_node.h"
#include "editor/editor_string_names.h" #include "editor/editor_string_names.h"
@ -1757,7 +1757,6 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &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 microphone_usage_descriptions = p_preset->get("privacy/microphone_usage_description_localized");
Dictionary camera_usage_descriptions = p_preset->get("privacy/camera_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"); Dictionary location_usage_descriptions = p_preset->get("privacy/location_usage_description_localized");
@ -1771,15 +1770,21 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p
Dictionary removable_volumes_usage_descriptions = p_preset->get("privacy/removable_volumes_usage_description_localized"); Dictionary removable_volumes_usage_descriptions = p_preset->get("privacy/removable_volumes_usage_description_localized");
Dictionary copyrights = p_preset->get("application/copyright_localized"); Dictionary copyrights = p_preset->get("application/copyright_localized");
Vector<String> translations = get_project_setting(p_preset, "internationalization/locale/translations"); const String project_name = get_project_setting(p_preset, "application/config/name");
if (translations.size() > 0) { const Dictionary appnames = get_project_setting(p_preset, "application/config/name_localized");
const StringName domain_name = "godot.project_name_localization";
Ref<TranslationDomain> domain = TranslationServer::get_singleton()->get_or_add_domain(domain_name);
TranslationServer::get_singleton()->load_project_translations(domain);
const Vector<String> locales = domain->get_loaded_locales();
if (!locales.is_empty()) {
{ {
String fname = tmp_app_path_name + "/Contents/Resources/en.lproj"; String fname = tmp_app_path_name + "/Contents/Resources/en.lproj";
tmp_app_dir->make_dir_recursive(fname); tmp_app_dir->make_dir_recursive(fname);
Ref<FileAccess> f = FileAccess::open(fname + "/InfoPlist.strings", FileAccess::WRITE); Ref<FileAccess> f = FileAccess::open(fname + "/InfoPlist.strings", FileAccess::WRITE);
f->store_line("/* Localized versions of Info.plist keys */"); f->store_line("/* Localized versions of Info.plist keys */");
f->store_line(""); 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()) { if (!((String)p_preset->get("privacy/microphone_usage_description")).is_empty()) {
f->store_line("NSMicrophoneUsageDescription = \"" + p_preset->get("privacy/microphone_usage_description").operator String() + "\";"); f->store_line("NSMicrophoneUsageDescription = \"" + p_preset->get("privacy/microphone_usage_description").operator String() + "\";");
} }
@ -1816,23 +1821,27 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p
f->store_line("NSHumanReadableCopyright = \"" + p_preset->get("application/copyright").operator String() + "\";"); f->store_line("NSHumanReadableCopyright = \"" + p_preset->get("application/copyright").operator String() + "\";");
} }
HashSet<String> languages; for (const String &lang : locales) {
for (const String &E : translations) { if (lang == "en") {
Ref<Translation> tr = ResourceLoader::load(E); continue;
if (tr.is_valid() && tr->get_locale() != "en") {
languages.insert(tr->get_locale());
} }
}
for (const String &lang : languages) {
String fname = tmp_app_path_name + "/Contents/Resources/" + lang + ".lproj"; String fname = tmp_app_path_name + "/Contents/Resources/" + lang + ".lproj";
tmp_app_dir->make_dir_recursive(fname); tmp_app_dir->make_dir_recursive(fname);
Ref<FileAccess> f = FileAccess::open(fname + "/InfoPlist.strings", FileAccess::WRITE); Ref<FileAccess> f = FileAccess::open(fname + "/InfoPlist.strings", FileAccess::WRITE);
f->store_line("/* Localized versions of Info.plist keys */"); f->store_line("/* Localized versions of Info.plist keys */");
f->store_line(""); 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() + "\";"); f->store_line("CFBundleDisplayName = \"" + appnames[lang].operator String() + "\";");
} }
if (microphone_usage_descriptions.has(lang)) { if (microphone_usage_descriptions.has(lang)) {
f->store_line("NSMicrophoneUsageDescription = \"" + microphone_usage_descriptions[lang].operator String() + "\";"); f->store_line("NSMicrophoneUsageDescription = \"" + microphone_usage_descriptions[lang].operator String() + "\";");
} }
@ -1872,6 +1881,8 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p
} }
} }
TranslationServer::get_singleton()->remove_domain(domain_name);
// Now process our template. // Now process our template.
bool found_binary = false; bool found_binary = false;