Jump to content

Plugin translation scheme: including code


Recommended Posts

I have written a class which I plan to use to translate my plugins when released (and has already been used for ExtendBorder) and am publishing it here to be freely used by other plugin authors if they wish.

Language packs should be placed in Effects, include the language code in its name and have the extension .fxlang. More than one language pack can be used to help cover a single language. See the basic language pack attached for an example. When creating a language pack, make sure the character encoding is set to UTF-8.

The code (v1.1):

Hidden Content:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;
using System.Collections;
using System.IO;

namespace YourPluginNamespace
{
   public class PluginTrans
   {
       Hashtable transTable = null;
       String plgLang = null;

       public PluginTrans()
       {
           plgLang = CultureInfo.CurrentUICulture.Name.ToLower();

           String langDir = Path.Combine(Environment.CurrentDirectory, "Effects");
           if (!Directory.Exists(langDir)) return;

           transTable = new Hashtable();

           DirectoryInfo langDirInfo = new DirectoryInfo(langDir);

           FileInfo[] langFiles = langDirInfo.GetFiles();
           for (int i = 0; i < langFiles.Length; i++)
           {
               if (FileIsLanguage(langFiles[i], plgLang))
               {
                   using (StreamReader curLangReader = new StreamReader(langFiles[i].FullName))
                   {
                       while (!curLangReader.EndOfStream)
                       {
                           String curLine = curLangReader.ReadLine();
                           KeyValuePair<String, String> curTrans = ReadTransLine(curLine);

                           if (!String.IsNullOrEmpty(curTrans.Key))
                           {
                               transTable[curTrans.Key] = curTrans.Value;
                           }
                       }
                   }
               }
           }

       }

       private static bool FileIsLanguage(FileInfo input, String locale)
       {
           return (input.Name.ToLower().Contains(locale.ToLower()) && input.Extension.ToLower() == ".fxlang");
       }

       public static KeyValuePair<String, String> ReadTransLine(String input)
       {
           int colonPos = input.IndexOf(":");
           int eqSignPos = input.IndexOf("=");

           if (colonPos != -1 && eqSignPos != -1 && input.Length > eqSignPos + 2)
           {
               String termKey = input.Substring(0, eqSignPos).ToLower();
               String termValue = input.Substring(eqSignPos + 1);

               return new KeyValuePair<String, String>(termKey, termValue);
           }

           return new KeyValuePair<String, String>(null, null);
       }

       /// <summary>
       /// Gets the translation for a term.
       /// </summary>
       /// <param name="keyTerm">
       /// en-US version of the term.
       /// </param>
       /// <param name="defaultTerm">
       /// Term to be used if no translation exists.
       /// </param>
       /// <returns></returns>
       public String GetTrans(String keyTerm, String defaultTerm)
       {
           String tableKey = String.Format("{0}:{1}", plgLang, keyTerm.ToLower());

           if (transTable.ContainsKey(tableKey))
           {
               return (String)transTable[tableKey];
           }

           return defaultTerm;
       }

       /// <summary>
       /// Gets the translation of a term.
       /// </summary>
       /// <param name="keyTerm">
       /// en-US version of the term.
       /// </param>
       /// <returns></returns>
       public String GetTrans(String keyTerm)
       {
           return GetTrans(keyTerm, keyTerm);
       }
   }
}

Language pack repository:

http://github.com/simonbrown/PluginTrans/blob/master/de%20and%20fr%20default.fxlang

If anyone wants to contribute, PM me your Github username. It might also be possible to suggest edits without being one, although i'm not sure.

Default language pack (v1.2)

defaultpack.zip

KaHuc.png
Link to comment
Share on other sites

if (plgLang == null) throw new ApplicationException("Call init plz. Kthx.");

Or you could just call Init() for them, and make Init() a private method.

The first time a plugin requests a translation might be in a separate thread.

Yeah, this would work much better with external files like Paint.NET already uses.

Good idea.

KaHuc.png
Link to comment
Share on other sites

Good idea.

The only problem is that it will have to be in a folder other than Paint.NET User Files. As when an effect's constructor is called, that information isn't available, and that might be a plugin's only chance to call Init.

I'm not sure whether it's best to store this in Paint.NET's Program Files folder, AppData, or somewhere else.

KaHuc.png
Link to comment
Share on other sites

The only problem is that it will have to be in a folder other than Paint.NET User Files. As when an effect's constructor is called, that information isn't available, and that might be a plugin's only chance to call Init.

I'm not sure whether it's best to store this in Paint.NET's Program Files folder, AppData, or somewhere else.

You're over-thinking it. There's nothing wrong with Paint.NET\Effects\, and rather than having a static class that has to be Initted, just make an instance class that has to be created in the Effect's ctor.

xZYt6wl.png

ambigram signature by Kemaru

[i write plugins and stuff]

If you like a post, upvote it!

Link to comment
Share on other sites

The first time a plugin requests a translation might be in a separate thread.

Then either use a mutex, or use a local variable to create the dictionary and then assign it to the static field as the very last step.

The Paint.NET Blog: https://blog.getpaint.net/

Donations are always appreciated! https://www.getpaint.net/donate.html

forumSig_bmwE60.jpg

Link to comment
Share on other sites

Then either use a mutex, or use a local variable to create the dictionary and then assign it to the static field as the very last step.

It was because of:

For now I'd just use CultureInfo.CurrentUICulture. This isn't guaranteed to be correct since it has to be set per-thread, so pretty much only the UI thread will have the "correct" value. But, Effects are (currently) instantiated on the UI thread for this so you can at least access it in your constructor.

And at that time it was a static class, which it now isn't.

Although on second thought, obviously plugins will only need the translations in the UI thread.

KaHuc.png
Link to comment
Share on other sites

I'll post the relevant parts.

(SNIP)

       PluginTrans pluginTrans;

       public ExtendBorderEffect()
           : base("Extend border", null, SubmenuNames.Distort, EffectFlags.Configurable)
       {
           pluginTrans = new PluginTrans();
       }

(SNIP)

       protected override ControlInfo OnCreateConfigUI(PropertyCollection props)
       {
           ControlInfo uiBuilder = base.OnCreateConfigUI(props);

           uiBuilder.SetPropertyControlType("Left", PropertyControlType.Slider);
           uiBuilder.SetPropertyControlType("Top", PropertyControlType.Slider);
           uiBuilder.SetPropertyControlType("Right", PropertyControlType.Slider);
           uiBuilder.SetPropertyControlType("Bottom", PropertyControlType.Slider);

           uiBuilder.SetPropertyControlValue("Left", ControlInfoPropertyNames.DisplayName, pluginTrans.GetTrans("left", "Left"));
           uiBuilder.SetPropertyControlValue("Top", ControlInfoPropertyNames.DisplayName, pluginTrans.GetTrans("top", "Top"));
           uiBuilder.SetPropertyControlValue("Right", ControlInfoPropertyNames.DisplayName, pluginTrans.GetTrans("right", "Right"));
           uiBuilder.SetPropertyControlValue("Bottom", ControlInfoPropertyNames.DisplayName, pluginTrans.GetTrans("bottom", "Bottom"));

           return uiBuilder;
       }

(SNIP)

KaHuc.png
Link to comment
Share on other sites

Just revised the German parts of the pack.

It would be nice if the translator would know what entry he'd be dealing with, context is always important when translating words. 'forever' has alone 3 possible expressions in German..

defaultpack.zip

Link to comment
Share on other sites

I've created the repository, and added you as a collaborator (assuming your username there is the same as it is here). See the first post for the URI.

though the authors will have to submit some kind of template we can use in our language pack, don't they?

My initial hope was that the key could just be the phrase in US-English (which doesn't have the be the default label), although that might not be practical. I guess that seems like the best option if there's anything that isn't obvious (e.g. more than color instead of colour).

KaHuc.png
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...