Add Localization
This commit is contained in:
@@ -35,6 +35,7 @@ public class Engine : EngineModule
|
|||||||
options.PublicDependencies.Add("UI");
|
options.PublicDependencies.Add("UI");
|
||||||
options.PublicDependencies.Add("Utilities");
|
options.PublicDependencies.Add("Utilities");
|
||||||
options.PublicDependencies.Add("Visject");
|
options.PublicDependencies.Add("Visject");
|
||||||
|
options.PublicDependencies.Add("Localization");
|
||||||
|
|
||||||
// Use source folder per platform group
|
// Use source folder per platform group
|
||||||
switch (options.Platform.Target)
|
switch (options.Platform.Target)
|
||||||
|
|||||||
186
Source/Engine/Localization/CultureInfo.cpp
Normal file
186
Source/Engine/Localization/CultureInfo.cpp
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
|
||||||
|
|
||||||
|
#include "CultureInfo.h"
|
||||||
|
#include "Engine/Core/Log.h"
|
||||||
|
#include "Engine/Scripting/ManagedCLR/MType.h"
|
||||||
|
#include "Engine/Utilities/StringConverter.h"
|
||||||
|
#if USE_MONO
|
||||||
|
#include <ThirdParty/mono-2.0/mono/metadata/appdomain.h>
|
||||||
|
#include <ThirdParty/mono-2.0/mono/metadata/culture-info.h>
|
||||||
|
#include <ThirdParty/mono-2.0/mono/metadata/culture-info-tables.h>
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
MonoObject obj;
|
||||||
|
MonoBoolean is_read_only;
|
||||||
|
gint32 lcid;
|
||||||
|
//...
|
||||||
|
} MonoCultureInfo;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
CultureInfo::CultureInfo(int32 lcid)
|
||||||
|
{
|
||||||
|
_lcid = lcid;
|
||||||
|
_lcidParent = 0;
|
||||||
|
_data = nullptr;
|
||||||
|
if (lcid == 0)
|
||||||
|
return;
|
||||||
|
if (lcid == 127)
|
||||||
|
{
|
||||||
|
_englishName = TEXT("Invariant Culture");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#if USE_MONO
|
||||||
|
for (int32 i = 0; i < NUM_CULTURE_ENTRIES; i++)
|
||||||
|
{
|
||||||
|
auto& e = culture_entries[i];
|
||||||
|
if (e.lcid == lcid)
|
||||||
|
{
|
||||||
|
_data = (void*)&e;
|
||||||
|
_lcidParent = (int32)e.parent_lcid;
|
||||||
|
const char* name = idx2string(e.name);
|
||||||
|
_name.SetUTF8(name, StringUtils::Length(name));
|
||||||
|
const char* nativename = idx2string(e.nativename);
|
||||||
|
_nativeName.SetUTF8(nativename, StringUtils::Length(nativename));
|
||||||
|
const char* englishname = idx2string(e.englishname);
|
||||||
|
_englishName.SetUTF8(englishname, StringUtils::Length(englishname));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
#error "Missing CultureInfo implementation."
|
||||||
|
#endif
|
||||||
|
if (!_data)
|
||||||
|
{
|
||||||
|
LOG(Error, "Unknown LCID {0} for CultureInfo", lcid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CultureInfo::CultureInfo(const StringView& name)
|
||||||
|
: CultureInfo(StringAnsiView(StringAsANSI<16>(name.Get(), name.Length()).Get(), name.Length()))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
CultureInfo::CultureInfo(const StringAnsiView& name)
|
||||||
|
{
|
||||||
|
_data = nullptr;
|
||||||
|
if (name.IsEmpty())
|
||||||
|
{
|
||||||
|
_lcid = 0;
|
||||||
|
_lcidParent = 0;
|
||||||
|
_englishName = TEXT("Invariant Culture");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#if USE_MONO
|
||||||
|
for (int32 i = 0; i < NUM_CULTURE_ENTRIES; i++)
|
||||||
|
{
|
||||||
|
auto& e = culture_entries[i];
|
||||||
|
if (name == idx2string(e.name))
|
||||||
|
{
|
||||||
|
_data = (void*)&e;
|
||||||
|
_lcid = (int32)e.lcid;
|
||||||
|
_lcidParent = (int32)e.parent_lcid;
|
||||||
|
_name.SetUTF8(name.Get(), name.Length());
|
||||||
|
const char* nativename = idx2string(e.nativename);
|
||||||
|
_nativeName.SetUTF8(nativename, StringUtils::Length(nativename));
|
||||||
|
const char* englishname = idx2string(e.englishname);
|
||||||
|
_englishName.SetUTF8(englishname, StringUtils::Length(englishname));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
#error "Missing CultureInfo implementation."
|
||||||
|
#endif
|
||||||
|
if (!_data)
|
||||||
|
{
|
||||||
|
LOG(Error, "Unknown name {0} for CultureInfo", String(name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int32 CultureInfo::GetLCID() const
|
||||||
|
{
|
||||||
|
return _lcid;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32 CultureInfo::GetParentLCID() const
|
||||||
|
{
|
||||||
|
return _lcidParent;
|
||||||
|
}
|
||||||
|
|
||||||
|
const String& CultureInfo::GetName() const
|
||||||
|
{
|
||||||
|
return _name;
|
||||||
|
}
|
||||||
|
|
||||||
|
const String& CultureInfo::GetNativeName() const
|
||||||
|
{
|
||||||
|
return _nativeName;
|
||||||
|
}
|
||||||
|
|
||||||
|
const String& CultureInfo::GetEnglishName() const
|
||||||
|
{
|
||||||
|
return _englishName;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CultureInfo::IsRightToLeft() const
|
||||||
|
{
|
||||||
|
#if USE_MONO
|
||||||
|
const auto data = static_cast<CultureInfoEntry*>(_data);
|
||||||
|
if (data)
|
||||||
|
return data->text_info.is_right_to_left ? true : false;
|
||||||
|
#else
|
||||||
|
#error "Missing CultureInfo implementation."
|
||||||
|
#endif
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String CultureInfo::ToString() const
|
||||||
|
{
|
||||||
|
return _name;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CultureInfo::operator==(const CultureInfo& other) const
|
||||||
|
{
|
||||||
|
return _lcid == other._lcid;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* MUtils::ToManaged(const CultureInfo& value)
|
||||||
|
{
|
||||||
|
#if USE_MONO
|
||||||
|
const auto klass = mono_class_from_name(mono_get_corlib(), "System.Globalization", "CultureInfo");
|
||||||
|
if (klass)
|
||||||
|
{
|
||||||
|
void* iter = nullptr;
|
||||||
|
MonoMethod* method;
|
||||||
|
while ((method = mono_class_get_methods(klass, &iter)))
|
||||||
|
{
|
||||||
|
if (StringUtils::Compare(mono_method_get_name(method), ".ctor") == 0)
|
||||||
|
{
|
||||||
|
const auto sig = mono_method_signature(method);
|
||||||
|
void* sigParams = nullptr;
|
||||||
|
mono_signature_get_params(sig, &sigParams);
|
||||||
|
if (mono_signature_get_param_count(sig) == 1 && mono_type_get_class(((MonoType**)sigParams)[0]) != mono_get_string_class())
|
||||||
|
{
|
||||||
|
MonoObject* obj = mono_object_new(mono_domain_get(), klass);
|
||||||
|
int32 lcid = value.GetLCID();
|
||||||
|
void* params[1];
|
||||||
|
params[0] = &lcid;
|
||||||
|
mono_runtime_invoke(method, obj, params, nullptr);
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
CultureInfo MUtils::ToNative(void* value)
|
||||||
|
{
|
||||||
|
int32 lcid = 127;
|
||||||
|
#if USE_MONO
|
||||||
|
if (value)
|
||||||
|
lcid = static_cast<MonoCultureInfo*>(value)->lcid;
|
||||||
|
#endif
|
||||||
|
return CultureInfo(lcid);
|
||||||
|
}
|
||||||
82
Source/Engine/Localization/CultureInfo.h
Normal file
82
Source/Engine/Localization/CultureInfo.h
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Engine/Core/Types/String.h"
|
||||||
|
#include "Engine/Core/Formatting.h"
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The information about a specific culture (aka locale). The information includes the names for the culture, the writing system, the calendar used, the sort order of strings, and formatting for dates and numbers.
|
||||||
|
/// </summary>
|
||||||
|
API_CLASS(InBuild, Namespace="System.Globalization") class FLAXENGINE_API CultureInfo
|
||||||
|
{
|
||||||
|
DECLARE_SCRIPTING_TYPE_MINIMAL(CultureInfo);
|
||||||
|
private:
|
||||||
|
void* _data;
|
||||||
|
int32 _lcid;
|
||||||
|
int32 _lcidParent;
|
||||||
|
String _name;
|
||||||
|
String _nativeName;
|
||||||
|
String _englishName;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="CultureInfo"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="lcid">The culture identifier.</param>
|
||||||
|
CultureInfo(int32 lcid);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="CultureInfo"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The culture name (eg. pl-PL).</param>
|
||||||
|
CultureInfo(const StringView& name);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="CultureInfo"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The culture name (eg. pl-PL).</param>
|
||||||
|
CultureInfo(const StringAnsiView& name);
|
||||||
|
|
||||||
|
public:
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the culture identifier.
|
||||||
|
/// </summary>
|
||||||
|
int32 GetLCID() const;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the parent culture identifier.
|
||||||
|
/// </summary>
|
||||||
|
int32 GetParentLCID() const;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the culture name (eg. pl-PL).
|
||||||
|
/// </summary>
|
||||||
|
const String& GetName() const;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the full localized culture name (eg. Polish (Poland)).
|
||||||
|
/// </summary>
|
||||||
|
const String& GetNativeName() const;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the culture name in English (eg. polski (Polska)).
|
||||||
|
/// </summary>
|
||||||
|
const String& GetEnglishName() const;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true if culture uses right-to-left (RTL) text layout. Otherwise it's more common left-to-right.
|
||||||
|
/// </summary>
|
||||||
|
bool IsRightToLeft() const;
|
||||||
|
|
||||||
|
String ToString() const;
|
||||||
|
bool operator==(const CultureInfo& other) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
DEFINE_DEFAULT_FORMATTING_VIA_TO_STRING(CultureInfo);
|
||||||
|
|
||||||
|
namespace MUtils
|
||||||
|
{
|
||||||
|
extern void* ToManaged(const CultureInfo& value);
|
||||||
|
extern CultureInfo ToNative(void* value);
|
||||||
|
}
|
||||||
18
Source/Engine/Localization/Localization.Build.cs
Normal file
18
Source/Engine/Localization/Localization.Build.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
|
||||||
|
|
||||||
|
using Flax.Build;
|
||||||
|
using Flax.Build.NativeCpp;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Localization and internalization module.
|
||||||
|
/// </summary>
|
||||||
|
public class Localization : EngineModule
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void Setup(BuildOptions options)
|
||||||
|
{
|
||||||
|
base.Setup(options);
|
||||||
|
|
||||||
|
options.PublicDependencies.Add("Scripting");
|
||||||
|
}
|
||||||
|
}
|
||||||
73
Source/Engine/Localization/Localization.cpp
Normal file
73
Source/Engine/Localization/Localization.cpp
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
|
||||||
|
|
||||||
|
#include "Localization.h"
|
||||||
|
#include "CultureInfo.h"
|
||||||
|
#include "Engine/Core/Log.h"
|
||||||
|
#include "Engine/Engine/EngineService.h"
|
||||||
|
|
||||||
|
class LocalizationService : public EngineService
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LocalizationService()
|
||||||
|
: EngineService(TEXT("Localization"), -950)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Init() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
CultureInfo CurrentCulture(0);
|
||||||
|
CultureInfo CurrentLanguage(0);
|
||||||
|
LocalizationService LocalizationServiceInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LocalizationService::Init()
|
||||||
|
{
|
||||||
|
CurrentLanguage = CurrentCulture = CultureInfo(Platform::GetUserLocaleName());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Delegate<> Localization::CurrentLanguageCultureChanged;
|
||||||
|
|
||||||
|
const CultureInfo& Localization::GetCurrentCulture()
|
||||||
|
{
|
||||||
|
return CurrentCulture;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Localization::SetCurrentCulture(const CultureInfo& value)
|
||||||
|
{
|
||||||
|
if (CurrentCulture == value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
LOG(Info, "Changing current culture to: {0} ({1})", value.GetName(), value.GetLCID());
|
||||||
|
CurrentCulture = value;
|
||||||
|
CurrentLanguageCultureChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
const CultureInfo& Localization::GetCurrentLanguage()
|
||||||
|
{
|
||||||
|
return CurrentLanguage;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Localization::SetCurrentLanguage(const CultureInfo& value)
|
||||||
|
{
|
||||||
|
if (CurrentLanguage == value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
LOG(Info, "Changing current language to: {0} ({1})", value.GetName(), value.GetLCID());
|
||||||
|
CurrentLanguage = value;
|
||||||
|
CurrentLanguageCultureChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Localization::SetCurrentLanguageCulture(const CultureInfo& value)
|
||||||
|
{
|
||||||
|
if (CurrentCulture == value && CurrentLanguage == value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
LOG(Info, "Changing current language and culture to: {0} ({1})", value.GetName(), value.GetLCID());
|
||||||
|
CurrentCulture = value;
|
||||||
|
CurrentLanguage = value;
|
||||||
|
CurrentLanguageCultureChanged();
|
||||||
|
}
|
||||||
44
Source/Engine/Localization/Localization.h
Normal file
44
Source/Engine/Localization/Localization.h
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CultureInfo.h"
|
||||||
|
#include "Engine/Core/Types/BaseTypes.h"
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The language and culture localization manager.
|
||||||
|
/// </summary>
|
||||||
|
API_CLASS(Static) class FLAXENGINE_API Localization
|
||||||
|
{
|
||||||
|
DECLARE_SCRIPTING_TYPE_MINIMAL(Localization);
|
||||||
|
public:
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current culture (date, time, currency and values formatting locale).
|
||||||
|
/// </summary>
|
||||||
|
API_PROPERTY() static const CultureInfo& GetCurrentCulture();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the current culture (date, time, currency and values formatting locale).
|
||||||
|
/// </summary>
|
||||||
|
API_PROPERTY() static void SetCurrentCulture(const CultureInfo& value);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current language (text display locale).
|
||||||
|
/// </summary>
|
||||||
|
API_PROPERTY() static const CultureInfo& GetCurrentLanguage();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the current language (text display locale).
|
||||||
|
/// </summary>
|
||||||
|
API_PROPERTY() static void SetCurrentLanguage(const CultureInfo& value);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets both the current language (text display locale) and the current culture (date, time, currency and values formatting locale) at once.
|
||||||
|
/// </summary>
|
||||||
|
API_FUNCTION() static void SetCurrentLanguageCulture(const CultureInfo& value);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when current culture or language gets changed. Can be used to refresh UI to reflect language changes.
|
||||||
|
/// </summary>
|
||||||
|
API_EVENT() static Delegate<> CurrentLanguageCultureChanged;
|
||||||
|
};
|
||||||
36
Source/ThirdParty/glib.h
vendored
Normal file
36
Source/ThirdParty/glib.h
vendored
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
// Wrapper for mono/mono/eglib/glib.h to mock the types for embedding
|
||||||
|
|
||||||
|
#ifndef _GLIB_H_
|
||||||
|
#define _GLIB_H_
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <limits.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Basic data types
|
||||||
|
*/
|
||||||
|
typedef int gint;
|
||||||
|
typedef unsigned int guint;
|
||||||
|
typedef short gshort;
|
||||||
|
typedef unsigned short gushort;
|
||||||
|
typedef long glong;
|
||||||
|
typedef unsigned long gulong;
|
||||||
|
typedef void * gpointer;
|
||||||
|
typedef const void * gconstpointer;
|
||||||
|
typedef char gchar;
|
||||||
|
typedef unsigned char guchar;
|
||||||
|
|
||||||
|
/* Types defined in terms of the stdint.h */
|
||||||
|
typedef int8_t gint8;
|
||||||
|
typedef uint8_t guint8;
|
||||||
|
typedef int16_t gint16;
|
||||||
|
typedef uint16_t guint16;
|
||||||
|
typedef int32_t gint32;
|
||||||
|
typedef uint32_t guint32;
|
||||||
|
typedef int64_t gint64;
|
||||||
|
typedef uint64_t guint64;
|
||||||
|
typedef float gfloat;
|
||||||
|
typedef double gdouble;
|
||||||
|
typedef int32_t gboolean;
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -1059,6 +1059,7 @@ namespace Flax.Build.Bindings
|
|||||||
// Using declarations
|
// Using declarations
|
||||||
contents.AppendLine("using System;");
|
contents.AppendLine("using System;");
|
||||||
contents.AppendLine("using System.ComponentModel;");
|
contents.AppendLine("using System.ComponentModel;");
|
||||||
|
contents.AppendLine("using System.Globalization;"); // TODO: using declarations based on actual types usage
|
||||||
contents.AppendLine("using System.Runtime.CompilerServices;");
|
contents.AppendLine("using System.Runtime.CompilerServices;");
|
||||||
contents.AppendLine("using System.Runtime.InteropServices;");
|
contents.AppendLine("using System.Runtime.InteropServices;");
|
||||||
foreach (var e in moduleInfo.Children)
|
foreach (var e in moduleInfo.Children)
|
||||||
|
|||||||
@@ -307,6 +307,9 @@ namespace Flax.Build.Bindings
|
|||||||
case "MClass":
|
case "MClass":
|
||||||
type = "MonoReflectionType*";
|
type = "MonoReflectionType*";
|
||||||
return "MUtils::GetType({0})";
|
return "MUtils::GetType({0})";
|
||||||
|
case "CultureInfo":
|
||||||
|
type = "void*";
|
||||||
|
return "MUtils::ToManaged({0})";
|
||||||
default:
|
default:
|
||||||
var apiType = FindApiTypeInfo(buildData, typeInfo, caller);
|
var apiType = FindApiTypeInfo(buildData, typeInfo, caller);
|
||||||
if (apiType != null)
|
if (apiType != null)
|
||||||
@@ -448,6 +451,9 @@ namespace Flax.Build.Bindings
|
|||||||
case "VariantType":
|
case "VariantType":
|
||||||
type = "MonoReflectionType*";
|
type = "MonoReflectionType*";
|
||||||
return "MUtils::UnboxVariantType({0})";
|
return "MUtils::UnboxVariantType({0})";
|
||||||
|
case "CultureInfo":
|
||||||
|
type = "void*";
|
||||||
|
return "MUtils::ToNative({0})";
|
||||||
default:
|
default:
|
||||||
// ScriptingObjectReference or AssetReference or WeakAssetReference or SoftObjectReference
|
// ScriptingObjectReference or AssetReference or WeakAssetReference or SoftObjectReference
|
||||||
if ((typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference" || typeInfo.Type == "SoftObjectReference") && typeInfo.GenericArgs != null)
|
if ((typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference" || typeInfo.Type == "SoftObjectReference") && typeInfo.GenericArgs != null)
|
||||||
|
|||||||
Reference in New Issue
Block a user