2014-02-10 02:10:30 +01:00
/**************************************************************************/
/* translation.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
2018-01-05 00:50:27 +01:00
2014-02-10 02:10:30 +01:00
# include "translation.h"
2024-01-30 21:03:28 +01:00
# include "translation.compat.inc"
2017-01-16 08:04:19 +01:00
2020-11-07 23:33:38 +01:00
# include "core/config/project_settings.h"
2018-09-11 18:13:45 +02:00
# include "core/io/resource_loader.h"
# include "core/os/os.h"
2021-09-23 13:08:50 +02:00
# include "core/string/locales.h"
2014-02-10 02:10:30 +01:00
2020-09-03 13:22:16 +02:00
# ifdef TOOLS_ENABLED
# include "main/main.h"
# endif
2020-07-16 10:52:06 +02:00
Dictionary Translation : : _get_messages ( ) const {
Dictionary d ;
2021-08-09 22:13:42 +02:00
for ( const KeyValue < StringName , StringName > & E : translation_map ) {
d [ E . key ] = E . value ;
2014-02-10 02:10:30 +01:00
}
2020-07-16 10:52:06 +02:00
return d ;
}
2020-02-17 22:06:54 +01:00
Vector < String > Translation : : _get_message_list ( ) const {
2020-08-07 13:17:12 +02:00
Vector < String > msgs ;
msgs . resize ( translation_map . size ( ) ) ;
int idx = 0 ;
2021-08-09 22:13:42 +02:00
for ( const KeyValue < StringName , StringName > & E : translation_map ) {
msgs . set ( idx , E . key ) ;
2020-08-07 13:17:12 +02:00
idx + = 1 ;
2014-02-10 02:10:30 +01:00
}
2020-08-07 13:17:12 +02:00
return msgs ;
2020-07-16 10:52:06 +02:00
}
2014-02-10 02:10:30 +01:00
2022-11-09 13:45:21 +01:00
Vector < String > Translation : : get_translated_message_list ( ) const {
Vector < String > msgs ;
msgs . resize ( translation_map . size ( ) ) ;
int idx = 0 ;
for ( const KeyValue < StringName , StringName > & E : translation_map ) {
msgs . set ( idx , E . value ) ;
idx + = 1 ;
}
return msgs ;
}
2020-08-07 13:17:12 +02:00
void Translation : : _set_messages ( const Dictionary & p_messages ) {
List < Variant > keys ;
p_messages . get_key_list ( & keys ) ;
2021-07-24 15:46:25 +02:00
for ( const Variant & E : keys ) {
2021-07-16 05:45:57 +02:00
translation_map [ E ] = p_messages [ E ] ;
2020-07-16 10:52:06 +02:00
}
2014-02-10 02:10:30 +01:00
}
void Translation : : set_locale ( const String & p_locale ) {
2021-09-23 13:08:50 +02:00
locale = TranslationServer : : get_singleton ( ) - > standardize_locale ( p_locale ) ;
2017-06-28 22:00:18 +02:00
2023-06-27 14:21:31 +02:00
if ( Thread : : is_main_thread ( ) ) {
_notify_translation_changed_if_applies ( ) ;
} else {
// Avoid calling non-thread-safe functions here.
callable_mp ( this , & Translation : : _notify_translation_changed_if_applies ) . call_deferred ( ) ;
}
}
void Translation : : _notify_translation_changed_if_applies ( ) {
2022-08-08 00:52:20 +02:00
if ( OS : : get_singleton ( ) - > get_main_loop ( ) & & TranslationServer : : get_singleton ( ) - > get_loaded_locales ( ) . has ( get_locale ( ) ) ) {
2017-06-28 22:00:18 +02:00
OS : : get_singleton ( ) - > get_main_loop ( ) - > notification ( MainLoop : : NOTIFICATION_TRANSLATION_CHANGED ) ;
}
2014-02-10 02:10:30 +01:00
}
2020-07-16 10:52:06 +02:00
void Translation : : add_message ( const StringName & p_src_text , const StringName & p_xlated_text , const StringName & p_context ) {
2020-08-07 13:17:12 +02:00
translation_map [ p_src_text ] = p_xlated_text ;
2020-07-16 10:52:06 +02:00
}
2020-08-07 13:17:12 +02:00
void Translation : : add_plural_message ( const StringName & p_src_text , const Vector < String > & p_plural_xlated_texts , const StringName & p_context ) {
WARN_PRINT ( " Translation class doesn't handle plural messages. Calling add_plural_message() on a Translation instance is probably a mistake. \n Use a derived Translation class that handles plurals, such as TranslationPO class " ) ;
2020-12-15 13:04:21 +01:00
ERR_FAIL_COND_MSG ( p_plural_xlated_texts . is_empty ( ) , " Parameter vector p_plural_xlated_texts passed in is empty. " ) ;
2020-08-07 13:17:12 +02:00
translation_map [ p_src_text ] = p_plural_xlated_texts [ 0 ] ;
2020-07-16 10:52:06 +02:00
}
StringName Translation : : get_message ( const StringName & p_src_text , const StringName & p_context ) const {
2021-09-29 13:18:45 +02:00
StringName ret ;
if ( GDVIRTUAL_CALL ( _get_message , p_src_text , p_context , ret ) ) {
return ret ;
}
2020-08-07 13:17:12 +02:00
if ( p_context ! = StringName ( ) ) {
WARN_PRINT ( " Translation class doesn't handle context. Using context in get_message() on a Translation instance is probably a mistake. \n Use a derived Translation class that handles context, such as TranslationPO class " ) ;
2020-05-14 16:41:43 +02:00
}
2014-02-10 02:10:30 +01:00
2022-05-13 15:04:37 +02:00
HashMap < StringName , StringName > : : ConstIterator E = translation_map . find ( p_src_text ) ;
2020-08-07 13:17:12 +02:00
if ( ! E ) {
2020-07-16 10:52:06 +02:00
return StringName ( ) ;
}
2022-05-13 15:04:37 +02:00
return E - > value ;
2020-08-07 13:17:12 +02:00
}
2020-07-16 10:52:06 +02:00
2020-08-07 13:17:12 +02:00
StringName Translation : : get_plural_message ( const StringName & p_src_text , const StringName & p_plural_text , int p_n , const StringName & p_context ) const {
2021-09-29 13:18:45 +02:00
StringName ret ;
if ( GDVIRTUAL_CALL ( _get_plural_message , p_src_text , p_plural_text , p_n , p_context , ret ) ) {
return ret ;
}
2020-08-07 13:17:12 +02:00
WARN_PRINT ( " Translation class doesn't handle plural messages. Calling get_plural_message() on a Translation instance is probably a mistake. \n Use a derived Translation class that handles plurals, such as TranslationPO class " ) ;
return get_message ( p_src_text ) ;
2020-07-16 10:52:06 +02:00
}
void Translation : : erase_message ( const StringName & p_src_text , const StringName & p_context ) {
2020-08-07 13:17:12 +02:00
if ( p_context ! = StringName ( ) ) {
WARN_PRINT ( " Translation class doesn't handle context. Using context in erase_message() on a Translation instance is probably a mistake. \n Use a derived Translation class that handles context, such as TranslationPO class " ) ;
2020-07-16 10:52:06 +02:00
}
2020-08-07 13:17:12 +02:00
translation_map . erase ( p_src_text ) ;
2014-02-10 02:10:30 +01:00
}
void Translation : : get_message_list ( List < StringName > * r_messages ) const {
2021-08-09 22:13:42 +02:00
for ( const KeyValue < StringName , StringName > & E : translation_map ) {
r_messages - > push_back ( E . key ) ;
2014-02-10 02:10:30 +01:00
}
}
2014-08-02 03:10:38 +02:00
int Translation : : get_message_count ( ) const {
2020-08-07 13:17:12 +02:00
return translation_map . size ( ) ;
2020-05-19 15:46:49 +02:00
}
2014-08-02 03:10:38 +02:00
2014-02-10 02:10:30 +01:00
void Translation : : _bind_methods ( ) {
2017-02-13 12:47:24 +01:00
ClassDB : : bind_method ( D_METHOD ( " set_locale " , " locale " ) , & Translation : : set_locale ) ;
ClassDB : : bind_method ( D_METHOD ( " get_locale " ) , & Translation : : get_locale ) ;
2024-01-30 21:03:28 +01:00
ClassDB : : bind_method ( D_METHOD ( " add_message " , " src_message " , " xlated_message " , " context " ) , & Translation : : add_message , DEFVAL ( StringName ( ) ) ) ;
ClassDB : : bind_method ( D_METHOD ( " add_plural_message " , " src_message " , " xlated_messages " , " context " ) , & Translation : : add_plural_message , DEFVAL ( StringName ( ) ) ) ;
ClassDB : : bind_method ( D_METHOD ( " get_message " , " src_message " , " context " ) , & Translation : : get_message , DEFVAL ( StringName ( ) ) ) ;
ClassDB : : bind_method ( D_METHOD ( " get_plural_message " , " src_message " , " src_plural_message " , " n " , " context " ) , & Translation : : get_plural_message , DEFVAL ( StringName ( ) ) ) ;
ClassDB : : bind_method ( D_METHOD ( " erase_message " , " src_message " , " context " ) , & Translation : : erase_message , DEFVAL ( StringName ( ) ) ) ;
2017-02-13 12:47:24 +01:00
ClassDB : : bind_method ( D_METHOD ( " get_message_list " ) , & Translation : : _get_message_list ) ;
2022-11-09 13:45:21 +01:00
ClassDB : : bind_method ( D_METHOD ( " get_translated_message_list " ) , & Translation : : get_translated_message_list ) ;
2017-02-13 12:47:24 +01:00
ClassDB : : bind_method ( D_METHOD ( " get_message_count " ) , & Translation : : get_message_count ) ;
2022-08-08 14:18:26 +02:00
ClassDB : : bind_method ( D_METHOD ( " _set_messages " , " messages " ) , & Translation : : _set_messages ) ;
2017-02-13 12:47:24 +01:00
ClassDB : : bind_method ( D_METHOD ( " _get_messages " ) , & Translation : : _get_messages ) ;
2017-03-05 16:44:50 +01:00
2021-09-29 13:18:45 +02:00
GDVIRTUAL_BIND ( _get_plural_message , " src_message " , " src_plural_message " , " n " , " context " ) ;
GDVIRTUAL_BIND ( _get_message , " src_message " , " context " ) ;
2021-11-03 23:06:17 +01:00
ADD_PROPERTY ( PropertyInfo ( Variant : : DICTIONARY , " messages " , PROPERTY_HINT_NONE , " " , PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL ) , " _set_messages " , " _get_messages " ) ;
2017-02-12 01:11:37 +01:00
ADD_PROPERTY ( PropertyInfo ( Variant : : STRING , " locale " ) , " set_locale " , " get_locale " ) ;
2014-02-10 02:10:30 +01:00
}
///////////////////////////////////////////////
2021-08-08 17:07:57 +02:00
struct _character_accent_pair {
const char32_t character ;
const char32_t * accented_character ;
} ;
static _character_accent_pair _character_to_accented [ ] = {
{ ' A ' , U " Å " } ,
{ ' B ' , U " ß " } ,
{ ' C ' , U " Ç " } ,
{ ' D ' , U " Ð " } ,
{ ' E ' , U " É " } ,
{ ' F ' , U " F́ " } ,
{ ' G ' , U " Ĝ " } ,
{ ' H ' , U " Ĥ " } ,
{ ' I ' , U " Ĩ " } ,
{ ' J ' , U " Ĵ " } ,
{ ' K ' , U " ĸ " } ,
{ ' L ' , U " Ł " } ,
{ ' M ' , U " Ḿ " } ,
{ ' N ' , U " й " } ,
{ ' O ' , U " Ö " } ,
{ ' P ' , U " Ṕ " } ,
{ ' Q ' , U " Q́ " } ,
{ ' R ' , U " Ř " } ,
{ ' S ' , U " Ŝ " } ,
{ ' T ' , U " Ŧ " } ,
{ ' U ' , U " Ũ " } ,
{ ' V ' , U " Ṽ " } ,
{ ' W ' , U " Ŵ " } ,
{ ' X ' , U " X́ " } ,
{ ' Y ' , U " Ÿ " } ,
{ ' Z ' , U " Ž " } ,
{ ' a ' , U " á " } ,
{ ' b ' , U " ḅ " } ,
{ ' c ' , U " ć " } ,
{ ' d ' , U " d́ " } ,
{ ' e ' , U " é " } ,
{ ' f ' , U " f́ " } ,
{ ' g ' , U " ǵ " } ,
{ ' h ' , U " h̀ " } ,
{ ' i ' , U " í " } ,
{ ' j ' , U " ǰ " } ,
{ ' k ' , U " ḱ " } ,
{ ' l ' , U " ł " } ,
{ ' m ' , U " m̀ " } ,
{ ' n ' , U " ή " } ,
{ ' o ' , U " ô " } ,
{ ' p ' , U " ṕ " } ,
{ ' q ' , U " q́ " } ,
{ ' r ' , U " ŕ " } ,
{ ' s ' , U " š " } ,
{ ' t ' , U " ŧ " } ,
{ ' u ' , U " ü " } ,
{ ' v ' , U " ṽ " } ,
{ ' w ' , U " ŵ " } ,
{ ' x ' , U " x́ " } ,
{ ' y ' , U " ý " } ,
{ ' z ' , U " ź " } ,
} ;
2021-09-23 13:08:50 +02:00
Vector < TranslationServer : : LocaleScriptInfo > TranslationServer : : locale_script_info ;
2022-05-13 15:04:37 +02:00
HashMap < String , String > TranslationServer : : language_map ;
HashMap < String , String > TranslationServer : : script_map ;
HashMap < String , String > TranslationServer : : locale_rename_map ;
HashMap < String , String > TranslationServer : : country_name_map ;
HashMap < String , String > TranslationServer : : variant_map ;
HashMap < String , String > TranslationServer : : country_rename_map ;
2021-09-23 13:08:50 +02:00
void TranslationServer : : init_locale_info ( ) {
// Init locale info.
language_map . clear ( ) ;
int idx = 0 ;
while ( language_list [ idx ] [ 0 ] ! = nullptr ) {
language_map [ language_list [ idx ] [ 0 ] ] = String : : utf8 ( language_list [ idx ] [ 1 ] ) ;
idx + + ;
}
// Init locale-script map.
locale_script_info . clear ( ) ;
idx = 0 ;
while ( locale_scripts [ idx ] [ 0 ] ! = nullptr ) {
LocaleScriptInfo info ;
info . name = locale_scripts [ idx ] [ 0 ] ;
info . script = locale_scripts [ idx ] [ 1 ] ;
info . default_country = locale_scripts [ idx ] [ 2 ] ;
Vector < String > supported_countries = String ( locale_scripts [ idx ] [ 3 ] ) . split ( " , " , false ) ;
for ( int i = 0 ; i < supported_countries . size ( ) ; i + + ) {
info . supported_countries . insert ( supported_countries [ i ] ) ;
2020-05-14 16:41:43 +02:00
}
2021-09-23 13:08:50 +02:00
locale_script_info . push_back ( info ) ;
idx + + ;
2017-10-29 22:26:09 +01:00
}
2021-09-23 13:08:50 +02:00
// Init supported script list.
script_map . clear ( ) ;
idx = 0 ;
while ( script_list [ idx ] [ 0 ] ! = nullptr ) {
script_map [ script_list [ idx ] [ 1 ] ] = String : : utf8 ( script_list [ idx ] [ 0 ] ) ;
idx + + ;
}
2014-02-10 02:10:30 +01:00
2021-09-23 13:08:50 +02:00
// Init regional variant map.
variant_map . clear ( ) ;
idx = 0 ;
while ( locale_variants [ idx ] [ 0 ] ! = nullptr ) {
variant_map [ locale_variants [ idx ] [ 0 ] ] = locale_variants [ idx ] [ 1 ] ;
idx + + ;
}
2017-03-05 16:44:50 +01:00
2021-09-23 13:08:50 +02:00
// Init locale renames.
locale_rename_map . clear ( ) ;
idx = 0 ;
2020-04-02 01:20:12 +02:00
while ( locale_renames [ idx ] [ 0 ] ! = nullptr ) {
2021-09-23 13:08:50 +02:00
if ( ! String ( locale_renames [ idx ] [ 1 ] ) . is_empty ( ) ) {
locale_rename_map [ locale_renames [ idx ] [ 0 ] ] = locale_renames [ idx ] [ 1 ] ;
2017-10-29 22:26:09 +01:00
}
idx + + ;
}
2021-09-23 13:08:50 +02:00
// Init country names.
country_name_map . clear ( ) ;
idx = 0 ;
while ( country_names [ idx ] [ 0 ] ! = nullptr ) {
country_name_map [ String ( country_names [ idx ] [ 0 ] ) ] = String : : utf8 ( country_names [ idx ] [ 1 ] ) ;
idx + + ;
}
// Init country renames.
country_rename_map . clear ( ) ;
idx = 0 ;
while ( country_renames [ idx ] [ 0 ] ! = nullptr ) {
if ( ! String ( country_renames [ idx ] [ 1 ] ) . is_empty ( ) ) {
country_rename_map [ country_renames [ idx ] [ 0 ] ] = country_renames [ idx ] [ 1 ] ;
}
idx + + ;
}
2017-10-29 22:26:09 +01:00
}
2021-09-23 13:08:50 +02:00
String TranslationServer : : standardize_locale ( const String & p_locale ) const {
2022-10-27 08:19:01 +02:00
return _standardize_locale ( p_locale , false ) ;
}
String TranslationServer : : _standardize_locale ( const String & p_locale , bool p_add_defaults ) const {
2021-09-23 13:08:50 +02:00
// Replaces '-' with '_' for macOS style locales.
String univ_locale = p_locale . replace ( " - " , " _ " ) ;
// Extract locale elements.
2022-09-29 11:53:28 +02:00
String lang_name , script_name , country_name , variant_name ;
2021-09-23 13:08:50 +02:00
Vector < String > locale_elements = univ_locale . get_slice ( " @ " , 0 ) . split ( " _ " ) ;
2022-09-29 11:53:28 +02:00
lang_name = locale_elements [ 0 ] ;
2021-09-23 13:08:50 +02:00
if ( locale_elements . size ( ) > = 2 ) {
2022-02-04 09:32:20 +01:00
if ( locale_elements [ 1 ] . length ( ) = = 4 & & is_ascii_upper_case ( locale_elements [ 1 ] [ 0 ] ) & & is_ascii_lower_case ( locale_elements [ 1 ] [ 1 ] ) & & is_ascii_lower_case ( locale_elements [ 1 ] [ 2 ] ) & & is_ascii_lower_case ( locale_elements [ 1 ] [ 3 ] ) ) {
2022-09-29 11:53:28 +02:00
script_name = locale_elements [ 1 ] ;
2021-09-23 13:08:50 +02:00
}
2022-02-04 09:32:20 +01:00
if ( locale_elements [ 1 ] . length ( ) = = 2 & & is_ascii_upper_case ( locale_elements [ 1 ] [ 0 ] ) & & is_ascii_upper_case ( locale_elements [ 1 ] [ 1 ] ) ) {
2022-09-29 11:53:28 +02:00
country_name = locale_elements [ 1 ] ;
2021-09-23 13:08:50 +02:00
}
2019-12-04 16:47:30 +01:00
}
2021-09-23 13:08:50 +02:00
if ( locale_elements . size ( ) > = 3 ) {
2022-02-04 09:32:20 +01:00
if ( locale_elements [ 2 ] . length ( ) = = 2 & & is_ascii_upper_case ( locale_elements [ 2 ] [ 0 ] ) & & is_ascii_upper_case ( locale_elements [ 2 ] [ 1 ] ) ) {
2022-09-29 11:53:28 +02:00
country_name = locale_elements [ 2 ] ;
} else if ( variant_map . has ( locale_elements [ 2 ] . to_lower ( ) ) & & variant_map [ locale_elements [ 2 ] . to_lower ( ) ] = = lang_name ) {
variant_name = locale_elements [ 2 ] . to_lower ( ) ;
2021-09-23 13:08:50 +02:00
}
}
if ( locale_elements . size ( ) > = 4 ) {
2022-09-29 11:53:28 +02:00
if ( variant_map . has ( locale_elements [ 3 ] . to_lower ( ) ) & & variant_map [ locale_elements [ 3 ] . to_lower ( ) ] = = lang_name ) {
variant_name = locale_elements [ 3 ] . to_lower ( ) ;
2021-09-23 13:08:50 +02:00
}
2019-12-04 16:47:30 +01:00
}
2021-09-23 13:08:50 +02:00
// Try extract script and variant from the extra part.
Vector < String > script_extra = univ_locale . get_slice ( " @ " , 1 ) . split ( " ; " ) ;
for ( int i = 0 ; i < script_extra . size ( ) ; i + + ) {
if ( script_extra [ i ] . to_lower ( ) = = " cyrillic " ) {
2022-09-29 11:53:28 +02:00
script_name = " Cyrl " ;
2021-09-23 13:08:50 +02:00
break ;
} else if ( script_extra [ i ] . to_lower ( ) = = " latin " ) {
2022-09-29 11:53:28 +02:00
script_name = " Latn " ;
2021-09-23 13:08:50 +02:00
break ;
} else if ( script_extra [ i ] . to_lower ( ) = = " devanagari " ) {
2022-09-29 11:53:28 +02:00
script_name = " Deva " ;
2021-09-23 13:08:50 +02:00
break ;
2022-09-29 11:53:28 +02:00
} else if ( variant_map . has ( script_extra [ i ] . to_lower ( ) ) & & variant_map [ script_extra [ i ] . to_lower ( ) ] = = lang_name ) {
variant_name = script_extra [ i ] . to_lower ( ) ;
2021-09-23 13:08:50 +02:00
}
}
2017-10-29 22:26:09 +01:00
2021-09-23 13:08:50 +02:00
// Handles known non-ISO language names used e.g. on Windows.
2022-09-29 11:53:28 +02:00
if ( locale_rename_map . has ( lang_name ) ) {
lang_name = locale_rename_map [ lang_name ] ;
2021-09-23 13:08:50 +02:00
}
2017-03-05 16:44:50 +01:00
2021-09-23 13:08:50 +02:00
// Handle country renames.
2022-09-29 11:53:28 +02:00
if ( country_rename_map . has ( country_name ) ) {
country_name = country_rename_map [ country_name ] ;
2021-09-23 13:08:50 +02:00
}
// Remove unsupported script codes.
2022-09-29 11:53:28 +02:00
if ( ! script_map . has ( script_name ) ) {
script_name = " " ;
2021-09-23 13:08:50 +02:00
}
// Add script code base on language and country codes for some ambiguous cases.
2022-10-27 08:19:01 +02:00
if ( p_add_defaults ) {
if ( script_name . is_empty ( ) ) {
for ( int i = 0 ; i < locale_script_info . size ( ) ; i + + ) {
const LocaleScriptInfo & info = locale_script_info [ i ] ;
if ( info . name = = lang_name ) {
if ( country_name . is_empty ( ) | | info . supported_countries . has ( country_name ) ) {
script_name = info . script ;
break ;
}
2021-09-23 13:08:50 +02:00
}
}
}
2022-10-27 08:19:01 +02:00
if ( ! script_name . is_empty ( ) & & country_name . is_empty ( ) ) {
// Add conntry code based on script for some ambiguous cases.
for ( int i = 0 ; i < locale_script_info . size ( ) ; i + + ) {
const LocaleScriptInfo & info = locale_script_info [ i ] ;
if ( info . name = = lang_name & & info . script = = script_name ) {
country_name = info . default_country ;
break ;
}
2021-09-23 13:08:50 +02:00
}
}
}
// Combine results.
2022-09-29 11:53:28 +02:00
String out = lang_name ;
if ( ! script_name . is_empty ( ) ) {
out = out + " _ " + script_name ;
2021-09-23 13:08:50 +02:00
}
2022-09-29 11:53:28 +02:00
if ( ! country_name . is_empty ( ) ) {
out = out + " _ " + country_name ;
2021-09-23 13:08:50 +02:00
}
2022-09-29 11:53:28 +02:00
if ( ! variant_name . is_empty ( ) ) {
out = out + " _ " + variant_name ;
2021-09-23 13:08:50 +02:00
}
2022-09-29 11:53:28 +02:00
return out ;
2021-09-23 13:08:50 +02:00
}
int TranslationServer : : compare_locales ( const String & p_locale_a , const String & p_locale_b ) const {
2022-10-27 08:19:01 +02:00
String locale_a = _standardize_locale ( p_locale_a , true ) ;
String locale_b = _standardize_locale ( p_locale_b , true ) ;
2021-09-23 13:08:50 +02:00
if ( locale_a = = locale_b ) {
// Exact match.
return 10 ;
}
Vector < String > locale_a_elements = locale_a . split ( " _ " ) ;
Vector < String > locale_b_elements = locale_b . split ( " _ " ) ;
if ( locale_a_elements [ 0 ] = = locale_b_elements [ 0 ] ) {
// Matching language, both locales have extra parts.
// Return number of matching elements.
int matching_elements = 1 ;
for ( int i = 1 ; i < locale_a_elements . size ( ) ; i + + ) {
for ( int j = 1 ; j < locale_b_elements . size ( ) ; j + + ) {
if ( locale_a_elements [ i ] = = locale_b_elements [ j ] ) {
matching_elements + + ;
}
}
2018-09-23 13:58:01 +02:00
}
2021-09-23 13:08:50 +02:00
return matching_elements ;
2016-10-27 10:36:32 +02:00
} else {
2021-09-23 13:08:50 +02:00
// No match.
return 0 ;
2016-10-27 10:36:32 +02:00
}
2021-09-23 13:08:50 +02:00
}
2017-01-09 20:43:44 +01:00
2021-09-23 13:08:50 +02:00
String TranslationServer : : get_locale_name ( const String & p_locale ) const {
2022-09-29 11:53:28 +02:00
String lang_name , script_name , country_name ;
Vector < String > locale_elements = standardize_locale ( p_locale ) . split ( " _ " ) ;
lang_name = locale_elements [ 0 ] ;
2021-09-23 13:08:50 +02:00
if ( locale_elements . size ( ) > = 2 ) {
2022-02-04 09:32:20 +01:00
if ( locale_elements [ 1 ] . length ( ) = = 4 & & is_ascii_upper_case ( locale_elements [ 1 ] [ 0 ] ) & & is_ascii_lower_case ( locale_elements [ 1 ] [ 1 ] ) & & is_ascii_lower_case ( locale_elements [ 1 ] [ 2 ] ) & & is_ascii_lower_case ( locale_elements [ 1 ] [ 3 ] ) ) {
2022-09-29 11:53:28 +02:00
script_name = locale_elements [ 1 ] ;
2021-09-23 13:08:50 +02:00
}
2022-02-04 09:32:20 +01:00
if ( locale_elements [ 1 ] . length ( ) = = 2 & & is_ascii_upper_case ( locale_elements [ 1 ] [ 0 ] ) & & is_ascii_upper_case ( locale_elements [ 1 ] [ 1 ] ) ) {
2022-09-29 11:53:28 +02:00
country_name = locale_elements [ 1 ] ;
2021-09-23 13:08:50 +02:00
}
}
if ( locale_elements . size ( ) > = 3 ) {
2022-02-04 09:32:20 +01:00
if ( locale_elements [ 2 ] . length ( ) = = 2 & & is_ascii_upper_case ( locale_elements [ 2 ] [ 0 ] ) & & is_ascii_upper_case ( locale_elements [ 2 ] [ 1 ] ) ) {
2022-09-29 11:53:28 +02:00
country_name = locale_elements [ 2 ] ;
2021-09-23 13:08:50 +02:00
}
2017-01-09 20:43:44 +01:00
}
2017-06-28 22:00:18 +02:00
2022-09-29 11:53:28 +02:00
String name = language_map [ lang_name ] ;
if ( ! script_name . is_empty ( ) ) {
name = name + " ( " + script_map [ script_name ] + " ) " ;
2021-09-23 13:08:50 +02:00
}
2022-09-29 11:53:28 +02:00
if ( ! country_name . is_empty ( ) ) {
name = name + " , " + country_name_map [ country_name ] ;
2021-09-23 13:08:50 +02:00
}
return name ;
2014-02-10 02:10:30 +01:00
}
2021-09-23 13:08:50 +02:00
Vector < String > TranslationServer : : get_all_languages ( ) const {
Vector < String > languages ;
2022-05-13 15:04:37 +02:00
for ( const KeyValue < String , String > & E : language_map ) {
languages . push_back ( E . key ) ;
2021-09-23 13:08:50 +02:00
}
return languages ;
2014-02-10 02:10:30 +01:00
}
2021-09-23 13:08:50 +02:00
String TranslationServer : : get_language_name ( const String & p_language ) const {
return language_map [ p_language ] ;
}
Vector < String > TranslationServer : : get_all_scripts ( ) const {
Vector < String > scripts ;
2022-05-13 15:04:37 +02:00
for ( const KeyValue < String , String > & E : script_map ) {
scripts . push_back ( E . key ) ;
2020-05-14 16:41:43 +02:00
}
2021-09-23 13:08:50 +02:00
return scripts ;
2017-10-25 07:29:20 +02:00
}
2021-09-23 13:08:50 +02:00
String TranslationServer : : get_script_name ( const String & p_script ) const {
return script_map [ p_script ] ;
}
2019-04-25 03:39:29 +02:00
2021-09-23 13:08:50 +02:00
Vector < String > TranslationServer : : get_all_countries ( ) const {
Vector < String > countries ;
2022-05-13 15:04:37 +02:00
for ( const KeyValue < String , String > & E : country_name_map ) {
countries . push_back ( E . key ) ;
2019-04-25 03:39:29 +02:00
}
2021-09-23 13:08:50 +02:00
return countries ;
2019-04-25 03:39:29 +02:00
}
2021-09-23 13:08:50 +02:00
String TranslationServer : : get_country_name ( const String & p_country ) const {
return country_name_map [ p_country ] ;
}
2017-10-29 22:26:09 +01:00
2021-09-23 13:08:50 +02:00
void TranslationServer : : set_locale ( const String & p_locale ) {
2023-12-05 15:17:22 +01:00
String new_locale = standardize_locale ( p_locale ) ;
if ( locale = = new_locale ) {
return ;
}
2017-10-29 22:26:09 +01:00
2023-12-05 15:17:22 +01:00
locale = new_locale ;
2023-11-13 20:25:41 +01:00
ResourceLoader : : reload_translation_remaps ( ) ;
2021-09-23 13:08:50 +02:00
if ( OS : : get_singleton ( ) - > get_main_loop ( ) ) {
OS : : get_singleton ( ) - > get_main_loop ( ) - > notification ( MainLoop : : NOTIFICATION_TRANSLATION_CHANGED ) ;
2017-10-29 22:26:09 +01:00
}
}
2021-09-23 13:08:50 +02:00
String TranslationServer : : get_locale ( ) const {
return locale ;
}
2017-10-29 22:26:09 +01:00
2022-08-08 00:52:20 +02:00
PackedStringArray TranslationServer : : get_loaded_locales ( ) const {
PackedStringArray locales ;
2022-05-19 01:43:40 +02:00
for ( const Ref < Translation > & E : translations ) {
const Ref < Translation > & t = E ;
2022-08-08 00:52:20 +02:00
ERR_FAIL_COND_V ( t . is_null ( ) , PackedStringArray ( ) ) ;
2021-09-23 13:08:50 +02:00
String l = t - > get_locale ( ) ;
2017-10-29 22:26:09 +01:00
2021-09-23 13:08:50 +02:00
locales . push_back ( l ) ;
2017-10-29 22:26:09 +01:00
}
return locales ;
}
2014-02-10 02:10:30 +01:00
void TranslationServer : : add_translation ( const Ref < Translation > & p_translation ) {
translations . insert ( p_translation ) ;
}
2020-05-14 14:29:06 +02:00
2014-02-10 02:10:30 +01:00
void TranslationServer : : remove_translation ( const Ref < Translation > & p_translation ) {
translations . erase ( p_translation ) ;
}
2020-08-07 13:17:12 +02:00
Ref < Translation > TranslationServer : : get_translation_object ( const String & p_locale ) {
Ref < Translation > res ;
2021-09-23 13:08:50 +02:00
int best_score = 0 ;
2020-08-07 13:17:12 +02:00
2022-05-19 01:43:40 +02:00
for ( const Ref < Translation > & E : translations ) {
const Ref < Translation > & t = E ;
2020-08-07 13:17:12 +02:00
ERR_FAIL_COND_V ( t . is_null ( ) , nullptr ) ;
String l = t - > get_locale ( ) ;
2021-09-23 13:08:50 +02:00
int score = compare_locales ( p_locale , l ) ;
2022-01-24 17:58:16 +01:00
if ( score > 0 & & score > = best_score ) {
2020-08-07 13:17:12 +02:00
res = t ;
2021-09-23 13:08:50 +02:00
best_score = score ;
if ( score = = 10 ) {
break ; // Exact match, skip the rest.
}
2020-08-07 13:17:12 +02:00
}
}
return res ;
}
2014-08-02 03:10:38 +02:00
void TranslationServer : : clear ( ) {
translations . clear ( ) ;
2020-05-19 15:46:49 +02:00
}
2014-08-02 03:10:38 +02:00
2020-07-16 10:52:06 +02:00
StringName TranslationServer : : translate ( const StringName & p_message , const StringName & p_context ) const {
2019-12-04 16:47:30 +01:00
// Match given message against the translation catalog for the project locale.
2014-02-10 02:10:30 +01:00
2020-05-14 16:41:43 +02:00
if ( ! enabled ) {
2014-02-10 02:10:30 +01:00
return p_message ;
2020-05-14 16:41:43 +02:00
}
2014-02-10 02:10:30 +01:00
2020-08-07 13:17:12 +02:00
StringName res = _get_message_from_translations ( p_message , p_context , locale , false ) ;
2020-07-16 10:52:06 +02:00
if ( ! res & & fallback . length ( ) > = 2 ) {
2020-08-07 13:17:12 +02:00
res = _get_message_from_translations ( p_message , p_context , fallback , false ) ;
2020-07-16 10:52:06 +02:00
}
if ( ! res ) {
2021-08-08 17:07:57 +02:00
return pseudolocalization_enabled ? pseudolocalize ( p_message ) : p_message ;
2020-07-16 10:52:06 +02:00
}
2021-08-08 17:07:57 +02:00
return pseudolocalization_enabled ? pseudolocalize ( res ) : res ;
2020-07-16 10:52:06 +02:00
}
StringName TranslationServer : : translate_plural ( const StringName & p_message , const StringName & p_message_plural , int p_n , const StringName & p_context ) const {
if ( ! enabled ) {
if ( p_n = = 1 ) {
return p_message ;
}
2020-08-07 13:17:12 +02:00
return p_message_plural ;
2020-07-16 10:52:06 +02:00
}
2020-08-07 13:17:12 +02:00
StringName res = _get_message_from_translations ( p_message , p_context , locale , true , p_message_plural , p_n ) ;
2020-07-16 10:52:06 +02:00
if ( ! res & & fallback . length ( ) > = 2 ) {
2020-08-07 13:17:12 +02:00
res = _get_message_from_translations ( p_message , p_context , fallback , true , p_message_plural , p_n ) ;
2020-07-16 10:52:06 +02:00
}
if ( ! res ) {
if ( p_n = = 1 ) {
return p_message ;
}
2020-08-07 13:17:12 +02:00
return p_message_plural ;
2020-07-16 10:52:06 +02:00
}
return res ;
}
2020-08-07 13:17:12 +02:00
StringName TranslationServer : : _get_message_from_translations ( const StringName & p_message , const StringName & p_context , const String & p_locale , bool plural , const String & p_message_plural , int p_n ) const {
2014-02-10 02:10:30 +01:00
StringName res ;
2021-09-23 13:08:50 +02:00
int best_score = 0 ;
2014-02-10 02:10:30 +01:00
2022-05-19 01:43:40 +02:00
for ( const Ref < Translation > & E : translations ) {
const Ref < Translation > & t = E ;
2019-12-04 16:47:30 +01:00
ERR_FAIL_COND_V ( t . is_null ( ) , p_message ) ;
2014-02-10 02:10:30 +01:00
String l = t - > get_locale ( ) ;
2021-09-23 13:08:50 +02:00
int score = compare_locales ( p_locale , l ) ;
2022-01-24 17:58:16 +01:00
if ( score > 0 & & score > = best_score ) {
2021-09-23 13:08:50 +02:00
StringName r ;
if ( ! plural ) {
2022-01-24 17:58:16 +01:00
r = t - > get_message ( p_message , p_context ) ;
2021-09-23 13:08:50 +02:00
} else {
2022-01-24 17:58:16 +01:00
r = t - > get_plural_message ( p_message , p_message_plural , p_n , p_context ) ;
}
if ( ! r ) {
continue ;
2019-12-04 16:47:30 +01:00
}
2022-01-24 17:58:16 +01:00
res = r ;
2021-09-23 13:08:50 +02:00
best_score = score ;
if ( score = = 10 ) {
break ; // Exact match, skip the rest.
2019-12-04 16:47:30 +01:00
}
}
2014-02-10 02:10:30 +01:00
}
return res ;
}
2020-04-02 01:20:12 +02:00
TranslationServer * TranslationServer : : singleton = nullptr ;
2014-02-10 02:10:30 +01:00
bool TranslationServer : : _load_translations ( const String & p_from ) {
2017-10-05 20:34:34 +02:00
if ( ProjectSettings : : get_singleton ( ) - > has_setting ( p_from ) ) {
2022-10-18 16:43:37 +02:00
const Vector < String > & translation_names = GLOBAL_GET ( p_from ) ;
2014-02-10 02:10:30 +01:00
2022-09-29 11:53:28 +02:00
int tcount = translation_names . size ( ) ;
2014-02-10 02:10:30 +01:00
if ( tcount ) {
2022-09-29 11:53:28 +02:00
const String * r = translation_names . ptr ( ) ;
2014-02-10 02:10:30 +01:00
for ( int i = 0 ; i < tcount ; i + + ) {
Ref < Translation > tr = ResourceLoader : : load ( r [ i ] ) ;
2020-05-14 16:41:43 +02:00
if ( tr . is_valid ( ) ) {
2014-02-10 02:10:30 +01:00
add_translation ( tr ) ;
2020-05-14 16:41:43 +02:00
}
2014-02-10 02:10:30 +01:00
}
}
return true ;
}
return false ;
}
void TranslationServer : : setup ( ) {
2021-02-17 17:44:49 +01:00
String test = GLOBAL_DEF ( " internationalization/locale/test " , " " ) ;
2015-01-04 15:03:31 +01:00
test = test . strip_edges ( ) ;
2021-12-09 10:42:46 +01:00
if ( ! test . is_empty ( ) ) {
2015-01-04 15:03:31 +01:00
set_locale ( test ) ;
2020-05-14 16:41:43 +02:00
} else {
2015-01-04 15:03:31 +01:00
set_locale ( OS : : get_singleton ( ) - > get_locale ( ) ) ;
2020-05-14 16:41:43 +02:00
}
2021-08-08 17:07:57 +02:00
2021-02-17 17:44:49 +01:00
fallback = GLOBAL_DEF ( " internationalization/locale/fallback " , " en " ) ;
2021-08-08 17:07:57 +02:00
pseudolocalization_enabled = GLOBAL_DEF ( " internationalization/pseudolocalization/use_pseudolocalization " , false ) ;
pseudolocalization_accents_enabled = GLOBAL_DEF ( " internationalization/pseudolocalization/replace_with_accents " , true ) ;
pseudolocalization_double_vowels_enabled = GLOBAL_DEF ( " internationalization/pseudolocalization/double_vowels " , false ) ;
pseudolocalization_fake_bidi_enabled = GLOBAL_DEF ( " internationalization/pseudolocalization/fake_bidi " , false ) ;
pseudolocalization_override_enabled = GLOBAL_DEF ( " internationalization/pseudolocalization/override " , false ) ;
expansion_ratio = GLOBAL_DEF ( " internationalization/pseudolocalization/expansion_ratio " , 0.0 ) ;
pseudolocalization_prefix = GLOBAL_DEF ( " internationalization/pseudolocalization/prefix " , " [ " ) ;
pseudolocalization_suffix = GLOBAL_DEF ( " internationalization/pseudolocalization/suffix " , " ] " ) ;
pseudolocalization_skip_placeholders_enabled = GLOBAL_DEF ( " internationalization/pseudolocalization/skip_placeholders " , true ) ;
2015-01-04 15:03:31 +01:00
# ifdef TOOLS_ENABLED
2022-11-08 19:53:22 +01:00
ProjectSettings : : get_singleton ( ) - > set_custom_property_info ( PropertyInfo ( Variant : : STRING , " internationalization/locale/fallback " , PROPERTY_HINT_LOCALE_ID , " " ) ) ;
2015-01-04 15:03:31 +01:00
# endif
2014-02-10 02:10:30 +01:00
}
2016-05-04 03:25:37 +02:00
void TranslationServer : : set_tool_translation ( const Ref < Translation > & p_translation ) {
tool_translation = p_translation ;
}
2020-09-03 13:22:16 +02:00
Ref < Translation > TranslationServer : : get_tool_translation ( ) const {
return tool_translation ;
}
String TranslationServer : : get_tool_locale ( ) {
# ifdef TOOLS_ENABLED
2022-02-18 21:15:02 +01:00
if ( Engine : : get_singleton ( ) - > is_editor_hint ( ) | | Engine : : get_singleton ( ) - > is_project_manager_hint ( ) ) {
if ( TranslationServer : : get_singleton ( ) - > get_tool_translation ( ) . is_valid ( ) ) {
return tool_translation - > get_locale ( ) ;
} else {
return " en " ;
}
2020-09-03 13:22:16 +02:00
} else {
# else
{
# endif
2023-02-21 23:08:05 +01:00
// Look for best matching loaded translation.
String best_locale = " en " ;
int best_score = 0 ;
for ( const Ref < Translation > & E : translations ) {
const Ref < Translation > & t = E ;
ERR_FAIL_COND_V ( t . is_null ( ) , best_locale ) ;
String l = t - > get_locale ( ) ;
int score = compare_locales ( locale , l ) ;
if ( score > 0 & & score > = best_score ) {
best_locale = l ;
best_score = score ;
if ( score = = 10 ) {
break ; // Exact match, skip the rest.
}
}
}
return best_locale ;
2020-09-03 13:22:16 +02:00
}
}
2020-07-16 10:52:06 +02:00
StringName TranslationServer : : tool_translate ( const StringName & p_message , const StringName & p_context ) const {
2016-05-04 03:25:37 +02:00
if ( tool_translation . is_valid ( ) ) {
2020-07-16 10:52:06 +02:00
StringName r = tool_translation - > get_message ( p_message , p_context ) ;
2016-05-28 00:58:28 +02:00
if ( r ) {
2021-08-08 17:07:57 +02:00
return editor_pseudolocalization ? tool_pseudolocalize ( r ) : r ;
2016-05-28 00:58:28 +02:00
}
2016-05-04 03:25:37 +02:00
}
2021-08-08 17:07:57 +02:00
return editor_pseudolocalization ? tool_pseudolocalize ( p_message ) : p_message ;
2020-03-18 18:34:36 +01:00
}
2016-05-04 03:25:37 +02:00
2020-07-16 10:52:06 +02:00
StringName TranslationServer : : tool_translate_plural ( const StringName & p_message , const StringName & p_message_plural , int p_n , const StringName & p_context ) const {
if ( tool_translation . is_valid ( ) ) {
StringName r = tool_translation - > get_plural_message ( p_message , p_message_plural , p_n , p_context ) ;
if ( r ) {
return r ;
}
}
if ( p_n = = 1 ) {
return p_message ;
}
2020-08-07 13:17:12 +02:00
return p_message_plural ;
2020-07-16 10:52:06 +02:00
}
2023-12-16 00:56:06 +01:00
void TranslationServer : : set_property_translation ( const Ref < Translation > & p_translation ) {
property_translation = p_translation ;
}
2023-10-05 15:12:10 +02:00
StringName TranslationServer : : property_translate ( const StringName & p_message , const StringName & p_context ) const {
2023-12-16 00:56:06 +01:00
if ( property_translation . is_valid ( ) ) {
2023-10-05 15:12:10 +02:00
StringName r = property_translation - > get_message ( p_message , p_context ) ;
2023-12-16 00:56:06 +01:00
if ( r ) {
return r ;
}
}
return p_message ;
}
2020-03-18 18:34:36 +01:00
void TranslationServer : : set_doc_translation ( const Ref < Translation > & p_translation ) {
doc_translation = p_translation ;
}
2020-07-16 10:52:06 +02:00
StringName TranslationServer : : doc_translate ( const StringName & p_message , const StringName & p_context ) const {
2020-03-18 18:34:36 +01:00
if ( doc_translation . is_valid ( ) ) {
2020-07-16 10:52:06 +02:00
StringName r = doc_translation - > get_message ( p_message , p_context ) ;
2020-03-18 18:34:36 +01:00
if ( r ) {
return r ;
}
}
2016-05-04 03:25:37 +02:00
return p_message ;
}
2020-07-16 10:52:06 +02:00
StringName TranslationServer : : doc_translate_plural ( const StringName & p_message , const StringName & p_message_plural , int p_n , const StringName & p_context ) const {
if ( doc_translation . is_valid ( ) ) {
StringName r = doc_translation - > get_plural_message ( p_message , p_message_plural , p_n , p_context ) ;
if ( r ) {
return r ;
}
}
if ( p_n = = 1 ) {
return p_message ;
}
2020-08-07 13:17:12 +02:00
return p_message_plural ;
2020-07-16 10:52:06 +02:00
}
2023-12-16 00:56:06 +01:00
void TranslationServer : : set_extractable_translation ( const Ref < Translation > & p_translation ) {
extractable_translation = p_translation ;
2022-12-27 03:58:01 +01:00
}
2023-12-16 00:56:06 +01:00
StringName TranslationServer : : extractable_translate ( const StringName & p_message , const StringName & p_context ) const {
if ( extractable_translation . is_valid ( ) ) {
StringName r = extractable_translation - > get_message ( p_message , p_context ) ;
2022-12-27 03:58:01 +01:00
if ( r ) {
return r ;
}
}
return p_message ;
}
2023-12-16 00:56:06 +01:00
StringName TranslationServer : : extractable_translate_plural ( const StringName & p_message , const StringName & p_message_plural , int p_n , const StringName & p_context ) const {
if ( extractable_translation . is_valid ( ) ) {
StringName r = extractable_translation - > get_plural_message ( p_message , p_message_plural , p_n , p_context ) ;
if ( r ) {
return r ;
}
}
if ( p_n = = 1 ) {
return p_message ;
}
return p_message_plural ;
}
2021-08-08 17:07:57 +02:00
bool TranslationServer : : is_pseudolocalization_enabled ( ) const {
return pseudolocalization_enabled ;
}
void TranslationServer : : set_pseudolocalization_enabled ( bool p_enabled ) {
pseudolocalization_enabled = p_enabled ;
2023-11-13 20:25:41 +01:00
ResourceLoader : : reload_translation_remaps ( ) ;
2021-08-08 17:07:57 +02:00
if ( OS : : get_singleton ( ) - > get_main_loop ( ) ) {
OS : : get_singleton ( ) - > get_main_loop ( ) - > notification ( MainLoop : : NOTIFICATION_TRANSLATION_CHANGED ) ;
}
}
void TranslationServer : : set_editor_pseudolocalization ( bool p_enabled ) {
editor_pseudolocalization = p_enabled ;
}
void TranslationServer : : reload_pseudolocalization ( ) {
pseudolocalization_accents_enabled = GLOBAL_GET ( " internationalization/pseudolocalization/replace_with_accents " ) ;
pseudolocalization_double_vowels_enabled = GLOBAL_GET ( " internationalization/pseudolocalization/double_vowels " ) ;
pseudolocalization_fake_bidi_enabled = GLOBAL_GET ( " internationalization/pseudolocalization/fake_bidi " ) ;
pseudolocalization_override_enabled = GLOBAL_GET ( " internationalization/pseudolocalization/override " ) ;
expansion_ratio = GLOBAL_GET ( " internationalization/pseudolocalization/expansion_ratio " ) ;
pseudolocalization_prefix = GLOBAL_GET ( " internationalization/pseudolocalization/prefix " ) ;
pseudolocalization_suffix = GLOBAL_GET ( " internationalization/pseudolocalization/suffix " ) ;
pseudolocalization_skip_placeholders_enabled = GLOBAL_GET ( " internationalization/pseudolocalization/skip_placeholders " ) ;
2023-11-13 20:25:41 +01:00
ResourceLoader : : reload_translation_remaps ( ) ;
2021-08-08 17:07:57 +02:00
if ( OS : : get_singleton ( ) - > get_main_loop ( ) ) {
OS : : get_singleton ( ) - > get_main_loop ( ) - > notification ( MainLoop : : NOTIFICATION_TRANSLATION_CHANGED ) ;
}
}
StringName TranslationServer : : pseudolocalize ( const StringName & p_message ) const {
String message = p_message ;
int length = message . length ( ) ;
if ( pseudolocalization_override_enabled ) {
message = get_override_string ( message ) ;
}
if ( pseudolocalization_double_vowels_enabled ) {
message = double_vowels ( message ) ;
}
if ( pseudolocalization_accents_enabled ) {
message = replace_with_accented_string ( message ) ;
}
if ( pseudolocalization_fake_bidi_enabled ) {
message = wrap_with_fakebidi_characters ( message ) ;
}
StringName res = add_padding ( message , length ) ;
return res ;
}
StringName TranslationServer : : tool_pseudolocalize ( const StringName & p_message ) const {
String message = p_message ;
message = double_vowels ( message ) ;
message = replace_with_accented_string ( message ) ;
StringName res = " [!!! " + message + " !!!] " ;
return res ;
}
String TranslationServer : : get_override_string ( String & p_message ) const {
String res ;
2024-01-05 13:05:16 +01:00
for ( int i = 0 ; i < p_message . length ( ) ; i + + ) {
2021-08-08 17:07:57 +02:00
if ( pseudolocalization_skip_placeholders_enabled & & is_placeholder ( p_message , i ) ) {
res + = p_message [ i ] ;
res + = p_message [ i + 1 ] ;
i + + ;
continue ;
}
res + = ' * ' ;
}
return res ;
}
String TranslationServer : : double_vowels ( String & p_message ) const {
String res ;
2024-01-05 13:05:16 +01:00
for ( int i = 0 ; i < p_message . length ( ) ; i + + ) {
2021-08-08 17:07:57 +02:00
if ( pseudolocalization_skip_placeholders_enabled & & is_placeholder ( p_message , i ) ) {
res + = p_message [ i ] ;
res + = p_message [ i + 1 ] ;
i + + ;
continue ;
}
res + = p_message [ i ] ;
if ( p_message [ i ] = = ' a ' | | p_message [ i ] = = ' e ' | | p_message [ i ] = = ' i ' | | p_message [ i ] = = ' o ' | | p_message [ i ] = = ' u ' | |
p_message [ i ] = = ' A ' | | p_message [ i ] = = ' E ' | | p_message [ i ] = = ' I ' | | p_message [ i ] = = ' O ' | | p_message [ i ] = = ' U ' ) {
res + = p_message [ i ] ;
}
}
return res ;
} ;
String TranslationServer : : replace_with_accented_string ( String & p_message ) const {
String res ;
2024-01-05 13:05:16 +01:00
for ( int i = 0 ; i < p_message . length ( ) ; i + + ) {
2021-08-08 17:07:57 +02:00
if ( pseudolocalization_skip_placeholders_enabled & & is_placeholder ( p_message , i ) ) {
res + = p_message [ i ] ;
res + = p_message [ i + 1 ] ;
i + + ;
continue ;
}
const char32_t * accented = get_accented_version ( p_message [ i ] ) ;
if ( accented ) {
res + = accented ;
} else {
res + = p_message [ i ] ;
}
}
return res ;
}
String TranslationServer : : wrap_with_fakebidi_characters ( String & p_message ) const {
String res ;
char32_t fakebidiprefix = U ' \ u202e ' ;
char32_t fakebidisuffix = U ' \ u202c ' ;
res + = fakebidiprefix ;
// The fake bidi unicode gets popped at every newline so pushing it back at every newline.
2024-01-05 13:05:16 +01:00
for ( int i = 0 ; i < p_message . length ( ) ; i + + ) {
2021-08-08 17:07:57 +02:00
if ( p_message [ i ] = = ' \n ' ) {
res + = fakebidisuffix ;
res + = p_message [ i ] ;
res + = fakebidiprefix ;
} else if ( pseudolocalization_skip_placeholders_enabled & & is_placeholder ( p_message , i ) ) {
res + = fakebidisuffix ;
res + = p_message [ i ] ;
res + = p_message [ i + 1 ] ;
res + = fakebidiprefix ;
i + + ;
} else {
res + = p_message [ i ] ;
}
}
res + = fakebidisuffix ;
return res ;
}
2022-04-05 12:40:26 +02:00
String TranslationServer : : add_padding ( const String & p_message , int p_length ) const {
2023-01-29 00:52:05 +01:00
String underscores = String ( " _ " ) . repeat ( p_length * expansion_ratio / 2 ) ;
String prefix = pseudolocalization_prefix + underscores ;
String suffix = underscores + pseudolocalization_suffix ;
return prefix + p_message + suffix ;
2021-08-08 17:07:57 +02:00
}
const char32_t * TranslationServer : : get_accented_version ( char32_t p_character ) const {
2022-02-04 09:32:20 +01:00
if ( ! is_ascii_char ( p_character ) ) {
2021-08-08 17:07:57 +02:00
return nullptr ;
}
for ( unsigned int i = 0 ; i < sizeof ( _character_to_accented ) / sizeof ( _character_to_accented [ 0 ] ) ; i + + ) {
if ( _character_to_accented [ i ] . character = = p_character ) {
return _character_to_accented [ i ] . accented_character ;
}
}
return nullptr ;
}
bool TranslationServer : : is_placeholder ( String & p_message , int p_index ) const {
2024-01-05 13:05:16 +01:00
return p_index < p_message . length ( ) - 1 & & p_message [ p_index ] = = ' % ' & &
2021-10-28 15:19:35 +02:00
( p_message [ p_index + 1 ] = = ' s ' | | p_message [ p_index + 1 ] = = ' c ' | | p_message [ p_index + 1 ] = = ' d ' | |
p_message [ p_index + 1 ] = = ' o ' | | p_message [ p_index + 1 ] = = ' x ' | | p_message [ p_index + 1 ] = = ' X ' | | p_message [ p_index + 1 ] = = ' f ' ) ;
2021-08-08 17:07:57 +02:00
}
2024-01-14 21:47:06 +01:00
# ifdef TOOLS_ENABLED
void TranslationServer : : get_argument_options ( const StringName & p_function , int p_idx , List < String > * r_options ) const {
const String pf = p_function ;
if ( p_idx = = 0 ) {
HashMap < String , String > * target_hash_map = nullptr ;
if ( pf = = " get_language_name " ) {
target_hash_map = & language_map ;
} else if ( pf = = " get_script_name " ) {
target_hash_map = & script_map ;
} else if ( pf = = " get_country_name " ) {
target_hash_map = & country_name_map ;
}
if ( target_hash_map ) {
for ( const KeyValue < String , String > & E : * target_hash_map ) {
r_options - > push_back ( E . key . quote ( ) ) ;
}
}
}
Object : : get_argument_options ( p_function , p_idx , r_options ) ;
}
# endif // TOOLS_ENABLED
2014-02-10 02:10:30 +01:00
void TranslationServer : : _bind_methods ( ) {
2017-02-13 12:47:24 +01:00
ClassDB : : bind_method ( D_METHOD ( " set_locale " , " locale " ) , & TranslationServer : : set_locale ) ;
ClassDB : : bind_method ( D_METHOD ( " get_locale " ) , & TranslationServer : : get_locale ) ;
2022-02-08 08:49:14 +01:00
ClassDB : : bind_method ( D_METHOD ( " get_tool_locale " ) , & TranslationServer : : get_tool_locale ) ;
2014-02-10 02:10:30 +01:00
2021-09-23 13:08:50 +02:00
ClassDB : : bind_method ( D_METHOD ( " compare_locales " , " locale_a " , " locale_b " ) , & TranslationServer : : compare_locales ) ;
ClassDB : : bind_method ( D_METHOD ( " standardize_locale " , " locale " ) , & TranslationServer : : standardize_locale ) ;
ClassDB : : bind_method ( D_METHOD ( " get_all_languages " ) , & TranslationServer : : get_all_languages ) ;
ClassDB : : bind_method ( D_METHOD ( " get_language_name " , " language " ) , & TranslationServer : : get_language_name ) ;
ClassDB : : bind_method ( D_METHOD ( " get_all_scripts " ) , & TranslationServer : : get_all_scripts ) ;
ClassDB : : bind_method ( D_METHOD ( " get_script_name " , " script " ) , & TranslationServer : : get_script_name ) ;
ClassDB : : bind_method ( D_METHOD ( " get_all_countries " ) , & TranslationServer : : get_all_countries ) ;
ClassDB : : bind_method ( D_METHOD ( " get_country_name " , " country " ) , & TranslationServer : : get_country_name ) ;
2017-10-25 07:29:20 +02:00
ClassDB : : bind_method ( D_METHOD ( " get_locale_name " , " locale " ) , & TranslationServer : : get_locale_name ) ;
2024-01-30 21:03:28 +01:00
ClassDB : : bind_method ( D_METHOD ( " translate " , " message " , " context " ) , & TranslationServer : : translate , DEFVAL ( StringName ( ) ) ) ;
ClassDB : : bind_method ( D_METHOD ( " translate_plural " , " message " , " plural_message " , " n " , " context " ) , & TranslationServer : : translate_plural , DEFVAL ( StringName ( ) ) ) ;
2014-08-02 03:10:38 +02:00
2017-08-09 13:19:41 +02:00
ClassDB : : bind_method ( D_METHOD ( " add_translation " , " translation " ) , & TranslationServer : : add_translation ) ;
ClassDB : : bind_method ( D_METHOD ( " remove_translation " , " translation " ) , & TranslationServer : : remove_translation ) ;
2020-08-07 13:17:12 +02:00
ClassDB : : bind_method ( D_METHOD ( " get_translation_object " , " locale " ) , & TranslationServer : : get_translation_object ) ;
2014-08-02 03:10:38 +02:00
2017-02-13 12:47:24 +01:00
ClassDB : : bind_method ( D_METHOD ( " clear " ) , & TranslationServer : : clear ) ;
2019-04-25 03:39:29 +02:00
ClassDB : : bind_method ( D_METHOD ( " get_loaded_locales " ) , & TranslationServer : : get_loaded_locales ) ;
2021-08-08 17:07:57 +02:00
ClassDB : : bind_method ( D_METHOD ( " is_pseudolocalization_enabled " ) , & TranslationServer : : is_pseudolocalization_enabled ) ;
ClassDB : : bind_method ( D_METHOD ( " set_pseudolocalization_enabled " , " enabled " ) , & TranslationServer : : set_pseudolocalization_enabled ) ;
ClassDB : : bind_method ( D_METHOD ( " reload_pseudolocalization " ) , & TranslationServer : : reload_pseudolocalization ) ;
ClassDB : : bind_method ( D_METHOD ( " pseudolocalize " , " message " ) , & TranslationServer : : pseudolocalize ) ;
ADD_PROPERTY ( PropertyInfo ( Variant : : Type : : BOOL , " pseudolocalization_enabled " ) , " set_pseudolocalization_enabled " , " is_pseudolocalization_enabled " ) ;
2014-02-10 02:10:30 +01:00
}
void TranslationServer : : load_translations ( ) {
2021-02-17 17:44:49 +01:00
_load_translations ( " internationalization/locale/translations " ) ; //all
_load_translations ( " internationalization/locale/translations_ " + locale . substr ( 0 , 2 ) ) ;
2015-01-04 15:03:31 +01:00
2014-02-10 02:10:30 +01:00
if ( locale . substr ( 0 , 2 ) ! = locale ) {
2021-02-17 17:44:49 +01:00
_load_translations ( " internationalization/locale/translations_ " + locale ) ;
2014-02-10 02:10:30 +01:00
}
}
2020-05-12 17:01:17 +02:00
TranslationServer : : TranslationServer ( ) {
2014-02-10 02:10:30 +01:00
singleton = this ;
2021-09-23 13:08:50 +02:00
init_locale_info ( ) ;
2014-02-10 02:10:30 +01:00
}