Add Localization

This commit is contained in:
Wojtek Figat
2021-04-14 12:22:48 +02:00
parent ac1eb8d58d
commit 7dd2a8fac9
9 changed files with 447 additions and 0 deletions

View File

@@ -35,6 +35,7 @@ public class Engine : EngineModule
options.PublicDependencies.Add("UI");
options.PublicDependencies.Add("Utilities");
options.PublicDependencies.Add("Visject");
options.PublicDependencies.Add("Localization");
// Use source folder per platform group
switch (options.Platform.Target)

View 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);
}

View 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);
}

View 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");
}
}

View 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();
}

View 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
View 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

View File

@@ -1059,6 +1059,7 @@ namespace Flax.Build.Bindings
// Using declarations
contents.AppendLine("using System;");
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.InteropServices;");
foreach (var e in moduleInfo.Children)

View File

@@ -307,6 +307,9 @@ namespace Flax.Build.Bindings
case "MClass":
type = "MonoReflectionType*";
return "MUtils::GetType({0})";
case "CultureInfo":
type = "void*";
return "MUtils::ToManaged({0})";
default:
var apiType = FindApiTypeInfo(buildData, typeInfo, caller);
if (apiType != null)
@@ -448,6 +451,9 @@ namespace Flax.Build.Bindings
case "VariantType":
type = "MonoReflectionType*";
return "MUtils::UnboxVariantType({0})";
case "CultureInfo":
type = "void*";
return "MUtils::ToNative({0})";
default:
// ScriptingObjectReference or AssetReference or WeakAssetReference or SoftObjectReference
if ((typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference" || typeInfo.Type == "SoftObjectReference") && typeInfo.GenericArgs != null)