Skip to content
This repository has been archived by the owner on Sep 4, 2024. It is now read-only.

Load i18n dynamically depending on the os and Locale base dir #219

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions Mono.Addins/Mono.Addins.Localization/FuncLoader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
using System;
using System.Runtime.InteropServices;

class FuncLoader
{
private class Windows
{
[DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);

[DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern IntPtr LoadLibraryW(string lpszLib);
}

private class Linux
{
[DllImport("libdl.so.2")]
public static extern IntPtr dlopen(string path, int flags);

[DllImport("libdl.so.2")]
public static extern IntPtr dlsym(IntPtr handle, string symbol);
}

private class OSX
{
[DllImport("/usr/lib/libSystem.dylib")]
public static extern IntPtr dlopen(string path, int flags);

[DllImport("/usr/lib/libSystem.dylib")]
public static extern IntPtr dlsym(IntPtr handle, string symbol);
}

private class Unix
{
[DllImport("libc")]
public static extern IntPtr dlopen(string path, int flags);

[DllImport("libc")]
public static extern IntPtr dlsym(IntPtr handle, string symbol);
}

[DllImport("libc")]
private static extern int uname(IntPtr buf);

private const int RTLD_LAZY = 0x0001;
private const int RTLD_GLOBAL = 0x0100;

public static bool IsWindows, IsOSX, IsLinux;

static FuncLoader()
{
switch (Environment.OSVersion.Platform)
{
case PlatformID.Win32NT:
case PlatformID.Win32S:
case PlatformID.Win32Windows:
case PlatformID.WinCE:
IsWindows = true;
break;
case PlatformID.MacOSX:
IsOSX = true;
break;
case PlatformID.Unix:
try
{
var buf = Marshal.AllocHGlobal(8192);
if (uname(buf) == 0 && Marshal.PtrToStringAnsi(buf) == "Darwin")
IsOSX = true;
if (uname(buf) == 0 && Marshal.PtrToStringAnsi(buf) == "Linux")
IsLinux = true;

Marshal.FreeHGlobal(buf);
}
catch { }

break;
}
}

public static IntPtr LoadLibrary(string libname)
{
if (IsWindows)
return Windows.LoadLibraryW(libname);

if (IsOSX)
return OSX.dlopen(libname, RTLD_GLOBAL | RTLD_LAZY);

if (IsLinux)
return Linux.dlopen(libname, RTLD_GLOBAL | RTLD_LAZY);

return Unix.dlopen(libname, RTLD_GLOBAL | RTLD_LAZY);
}

public static IntPtr GetProcAddress(IntPtr library, string function)
{
var ret = IntPtr.Zero;

if (IsWindows)
ret = Windows.GetProcAddress(library, function);
else if (IsOSX)
ret = OSX.dlsym(library, function);
else if (IsLinux)
ret = Linux.dlsym(library, function);
else
ret = Unix.dlsym(library, function);

return ret;
}

public static T LoadFunction<T>(IntPtr procaddress)
{
if (procaddress == IntPtr.Zero)
return default(T);

return Marshal.GetDelegateForFunctionPointer<T>(procaddress);
}
}
90 changes: 90 additions & 0 deletions Mono.Addins/Mono.Addins.Localization/GLibrary.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;

class GLibrary
{

[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool SetDllDirectory(string lpPathName);

private static Dictionary<Library, IntPtr> _libraries;
private static HashSet<Library> _librariesNotFound;
private static Dictionary<string, IntPtr> _customlibraries;
private static Dictionary<Library, string[]> _libraryDefinitions;

static GLibrary()
{
_customlibraries = new Dictionary<string, IntPtr>();
_librariesNotFound = new HashSet<Library>();
_libraries = new Dictionary<Library, IntPtr>();
_libraryDefinitions = new Dictionary<Library, string[]>();
_libraryDefinitions[Library.Intl] = new[] {"libintl-8.dll", "libc.so.6", "libintl.8.dylib", "intl"};
}

public static IntPtr Load(Library library)
{
if (_libraries.TryGetValue(library, out var ret))
return ret;

if (TryGet(library, out ret)) return ret;

var err = library + ": " + string.Join(", ", _libraryDefinitions[library]);

throw new DllNotFoundException(err);

}

public static bool IsSupported(Library library) => TryGet(library, out var __);

static bool TryGet(Library library, out IntPtr ret)
{
ret = IntPtr.Zero;

if (_libraries.TryGetValue(library, out ret)) {
return true;
}

if (_librariesNotFound.Contains(library)) {
return false;
}

if (FuncLoader.IsWindows) {
ret = FuncLoader.LoadLibrary(_libraryDefinitions[library][0]);

if (ret == IntPtr.Zero) {
SetDllDirectory(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData));
ret = FuncLoader.LoadLibrary(_libraryDefinitions[library][0]);
}
} else if (FuncLoader.IsOSX) {
ret = FuncLoader.LoadLibrary(_libraryDefinitions[library][2]);

if (ret == IntPtr.Zero) {
ret = FuncLoader.LoadLibrary("/usr/local/lib/" + _libraryDefinitions[library][2]);
if (ret == IntPtr.Zero) {
ret = FuncLoader.LoadLibrary("/opt/homebrew/lib/" + _libraryDefinitions[library][2]);
}
}
} else
ret = FuncLoader.LoadLibrary(_libraryDefinitions[library][1]);

if (ret == IntPtr.Zero) {
for (var i = 0; i < _libraryDefinitions[library].Length; i++) {
ret = FuncLoader.LoadLibrary(_libraryDefinitions[library][i]);

if (ret != IntPtr.Zero)
break;
}
}

if (ret != IntPtr.Zero) {
_libraries[library] = ret;
} else {
_librariesNotFound.Add(library);
}

return ret != IntPtr.Zero;
}

}
29 changes: 18 additions & 11 deletions Mono.Addins/Mono.Addins.Localization/GettextDomain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,22 @@ namespace Mono.Addins.Localization
{
class GettextDomain
{
[DllImport("intl", CallingConvention = CallingConvention.Cdecl)]
static extern IntPtr bindtextdomain (IntPtr domainname, IntPtr dirname);
[DllImport("intl", CallingConvention = CallingConvention.Cdecl)]
static extern IntPtr bind_textdomain_codeset (IntPtr domainname, IntPtr codeset);
[DllImport("intl", CallingConvention = CallingConvention.Cdecl)]
static extern IntPtr dgettext (IntPtr domainname, IntPtr instring);
[DllImport("intl", CallingConvention = CallingConvention.Cdecl)]
static extern IntPtr dngettext (IntPtr domainname, IntPtr instring, IntPtr plural, int n);

[UnmanagedFunctionPointer (CallingConvention.Cdecl)]
delegate IntPtr d_bindtextdomain (IntPtr domainname, IntPtr dirname);
static d_bindtextdomain bindtextdomain = FuncLoader.LoadFunction<d_bindtextdomain>(FuncLoader.GetProcAddress(GLibrary.Load(Library.Intl), "bindtextdomain"));

[UnmanagedFunctionPointer (CallingConvention.Cdecl)]
delegate IntPtr d_bind_textdomain_codeset (IntPtr domainname, IntPtr codeset);
static d_bind_textdomain_codeset bind_textdomain_codeset = FuncLoader.LoadFunction<d_bind_textdomain_codeset>(FuncLoader.GetProcAddress(GLibrary.Load(Library.Intl), "bind_textdomain_codeset"));

[UnmanagedFunctionPointer (CallingConvention.Cdecl)]
delegate IntPtr d_dgettext (IntPtr domainname, IntPtr instring);
static d_dgettext dgettext = FuncLoader.LoadFunction<d_dgettext>(FuncLoader.GetProcAddress(GLibrary.Load(Library.Intl), "dgettext"));

[UnmanagedFunctionPointer (CallingConvention.Cdecl)]
delegate IntPtr d_dngettext (IntPtr domainname, IntPtr instring, IntPtr plural, int n);
static d_dngettext dngettext = FuncLoader.LoadFunction<d_dngettext>(FuncLoader.GetProcAddress(GLibrary.Load(Library.Intl), "dngettext"));

IntPtr ipackage;

public void Init (String package, string localedir)
Expand All @@ -58,11 +65,11 @@ public void Init (String package, string localedir)
string prefix = f.Directory.Parent.Parent.Parent.ToString ();
prefix = Path.Combine (Path.Combine (prefix, "share"), "locale");
}

ipackage = StringToPtr (package);
IntPtr ilocaledir = StringToPtr (localedir);
IntPtr iutf8 = StringToPtr ("UTF-8");

try {
if (bindtextdomain (ipackage, ilocaledir) == IntPtr.Zero)
throw new InvalidOperationException ("Gettext localizer: bindtextdomain failed");
Expand Down
2 changes: 1 addition & 1 deletion Mono.Addins/Mono.Addins.Localization/GettextLocalizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public IAddinLocalizer CreateLocalizer (RuntimeAddin addin, NodeElement element)
string dir = element.GetAttribute ("location");
if (dir.Length == 0)
dir = "locale";
dir = addin.GetFilePath (dir);
dir = addin.GetLocaleFilePath (dir);
domain = new GettextDomain ();
domain.Init (pkg, dir);
return this;
Expand Down
5 changes: 5 additions & 0 deletions Mono.Addins/Mono.Addins.Localization/Library.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@

enum Library
{
Intl,
}
20 changes: 20 additions & 0 deletions Mono.Addins/Mono.Addins/RuntimeAddin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ namespace Mono.Addins
public class RuntimeAddin
{
readonly string id;
readonly string baseLocaleDirectory;
readonly string baseDirectory;
readonly Addin ainfo;
readonly RuntimeAddin parentAddin;
Expand All @@ -75,6 +76,7 @@ internal RuntimeAddin (AddinEngine addinEngine, Addin iad)
AddinDescription description = iad.Description;
id = description.AddinId;
baseDirectory = description.BasePath;
baseLocaleDirectory = Environment.GetEnvironmentVariable ("MONO_ADDINS_BASE_LOCALE_DIR") ?? description.BasePath;
module = description.MainModule;
module.RuntimeAddin = this;
localizerDescription = description.Localizer;
Expand All @@ -87,6 +89,7 @@ internal RuntimeAddin (AddinEngine addinEngine, RuntimeAddin parentAddin, Module
this.module = module;
id = parentAddin.id;
baseDirectory = parentAddin.baseDirectory;
baseLocaleDirectory = Environment.GetEnvironmentVariable ("MONO_ADDINS_BASE_LOCALE_DIR") ?? parentAddin.baseDirectory;
privatePath = parentAddin.privatePath;
ainfo = parentAddin.ainfo;
module.RuntimeAddin = this;
Expand Down Expand Up @@ -480,6 +483,23 @@ public string GetFilePath (string fileName)
return Path.Combine (baseDirectory, fileName);
}

/// <summary>
/// Gets the path of the locale directory for the add-in file.
/// </summary>
/// <param name="dirName">
/// The directory relative path of the file or if env MONO_ADDINS_BASE_LOCALE_DIR is set.
/// </param>
/// <returns>
/// Full path of the directory
/// </returns>
/// <remarks>
/// This method can be used to get the full path of the directory for the localization.
/// </remarks>
public string GetLocaleFilePath (string dirName)
{
return Path.Combine (baseLocaleDirectory, dirName);
}

/// <summary>
/// Gets the path of an add-in file
/// </summary>
Expand Down