From 6f63c59304a6bdd2d95443439784c560f984217e Mon Sep 17 00:00:00 2001 From: Cameron White Date: Sun, 23 Aug 2026 11:41:19 -0400 Subject: [PATCH] Use native gettext for Pinta's translations - Related to PR #2261, using the native gettext library is the simplest way to also support translations for .ui files - no additional work is needed since GTK already calls dgettext() for any translatable strings when loading the xml file - We already bundle libintl with Pinta (since it's required by GTK, libadwaita etc) so this doesn't require any additional packaging. I think the original motivation for using the managed Ngettext library was because we no longer had any bindings to gettext when porting to GTK3 - for GTK2 we used the Mono.Unix.Catalog wrapper for the native library - Several of the native methods are already wrapped via GLib and have bindings available (e.g. GLib.Functions.Dgettext()) so this just requires a couple custom bindings for functions like textdomain(). We previously already had one binding for bindtextdomain() to configure libadwaita's translation directory for the macOS app bundle --- Directory.Packages.props | 1 - Pinta.Core/Classes/Translations.cs | 25 +++++++++++++++---------- Pinta.Core/Extensions/IntlExtensions.cs | 22 ++++++++++++++++++++++ Pinta.Core/Pinta.Core.csproj | 1 - Pinta/Main.cs | 6 +++--- 5 files changed, 40 insertions(+), 15 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index fc8333c847..b99658a20c 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -11,7 +11,6 @@ - diff --git a/Pinta.Core/Classes/Translations.cs b/Pinta.Core/Classes/Translations.cs index c0b37b6129..dd4f1392a8 100644 --- a/Pinta.Core/Classes/Translations.cs +++ b/Pinta.Core/Classes/Translations.cs @@ -24,37 +24,42 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. +using System; using System.Globalization; -using NGettext; namespace Pinta.Core; public static class Translations { - private static ICatalog? catalog; + private const string PintaTextDomain = "pinta"; - public static void Init (string domain, string locale_dir) + public static void Init (string localeDir) { CultureInfo cultureInfo = CultureInfo.CurrentUICulture; - catalog = new Catalog (domain, locale_dir, cultureInfo); + string lang = cultureInfo.Name.Replace ('-', '_'); // convert names like en-CA to en_CA - // The dotnet UI culture controls which translations are loaded for Pinta above. - // The GTK / libadwaita libraries use the native version of gettext for their translations - // (e.g. the About dialog), so here we set the LANG environment variable to be consistent. + // Follow the dotnet UI culture to choose which language is used by default. + // Pinta (along with GTK / libadwaita) use the native version of gettext for translations + // so here we set the LANG environment variable to make these consistent. // Note we need to initialize the GLib module since this is called very early in startup, // before GTK is initialized. GLib.Module.Initialize (); - string lang = cultureInfo.Name.Replace ('-', '_'); // convert names like en-CA to en_CA GLib.Functions.Setenv ("LANG", lang, overwrite: true); + + // Initialize gettext for Pinta's translations. + IntlExtensions.BindTextDomain (PintaTextDomain, localeDir); + IntlExtensions.BindTextDomainCodeset (PintaTextDomain, "UTF-8"); + IntlExtensions.TextDomain (PintaTextDomain); } public static string GetString (string text) { - return catalog?.GetString (text) ?? text; + // Just use glib'c gettext wrapper for convenience instead of adding our own binding. + return GLib.Functions.Dgettext (PintaTextDomain, text); } public static string GetString (string text, params object[] args) { - return catalog?.GetString (text, args) ?? text; + return string.Format (GetString (text), args); } } diff --git a/Pinta.Core/Extensions/IntlExtensions.cs b/Pinta.Core/Extensions/IntlExtensions.cs index ecb8c4a613..ec8878922e 100644 --- a/Pinta.Core/Extensions/IntlExtensions.cs +++ b/Pinta.Core/Extensions/IntlExtensions.cs @@ -32,4 +32,26 @@ public static void BindTextDomain (string domain, string dir) GLib.Internal.NonNullablePlatformStringUnownedHandle.Create (domain), GLib.Internal.NonNullablePlatformStringUnownedHandle.Create (dir)); } + + [LibraryImport (IntlLibraryName, EntryPoint = "bind_textdomain_codeset")] + private static partial IntPtr InternalBindTextDomainCodeset ( + GLib.Internal.NonNullablePlatformStringHandle domain, + GLib.Internal.NonNullablePlatformStringHandle codeset); + + public static void BindTextDomainCodeset (string domain, string codeset) + { + InternalBindTextDomainCodeset ( + GLib.Internal.NonNullablePlatformStringUnownedHandle.Create (domain), + GLib.Internal.NonNullablePlatformStringUnownedHandle.Create (codeset)); + } + + [LibraryImport (IntlLibraryName, EntryPoint = "textdomain")] + private static partial IntPtr InternalTextDomain ( + GLib.Internal.NonNullablePlatformStringHandle domain); + + public static void TextDomain (string domain) + { + InternalTextDomain ( + GLib.Internal.NonNullablePlatformStringUnownedHandle.Create (domain)); + } } diff --git a/Pinta.Core/Pinta.Core.csproj b/Pinta.Core/Pinta.Core.csproj index 1952e07559..a5a741bf03 100644 --- a/Pinta.Core/Pinta.Core.csproj +++ b/Pinta.Core/Pinta.Core.csproj @@ -7,7 +7,6 @@ - diff --git a/Pinta/Main.cs b/Pinta/Main.cs index 50c4ba45c4..ae6f665800 100644 --- a/Pinta/Main.cs +++ b/Pinta/Main.cs @@ -42,10 +42,10 @@ public static int Main (string[] args) MacInterop.Environment.Init (); } - string locale_dir = Path.Combine (SystemManager.GetDataRootDirectory (), "locale"); + string localeDir = Path.Combine (SystemManager.GetDataRootDirectory (), "locale"); try { - Translations.Init ("pinta", locale_dir); + Translations.Init (localeDir); } catch (Exception ex) { Console.WriteLine (ex); } @@ -78,7 +78,7 @@ public static int Main (string[] args) parseResult.GetValue (threads_option), parseResult.GetValue (files_arg) ?? [], parseResult.GetValue (debug_option), - locale_dir); + localeDir); }); return root_command.Parse (args).Invoke ();