Sign in to follow this  
Simon Brown

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

Share this post


Link to post
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.

Share this post


Link to post
Share on other sites

I don't know how but it would be great to use text file per language, so everyone could create his own translation if it doesn't exist and this task won't belong only to the plugin maker.

Share this post


Link to post
Share on other sites

transTable["de:top"] = "Top"; and transTable["de:bottom"] = "Boden";

is more like

transTable["de:top"] = "Oben"; and transTable["de:bottom"] = "Unten";

(I'm a native German speacker ;-)

Share this post


Link to post
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.

Share this post


Link to post
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.

Share this post


Link to post
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.

Share this post


Link to post
Share on other sites

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.

Thanks, i'll do that. Update posted.

Share this post


Link to post
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.

Share this post


Link to post
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.

Share this post


Link to post
Share on other sites

Could you provide the source of ExtendBorder to see how to implement this? Or Some part of the code in the plugin side?

Share this post


Link to post
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)

Share this post


Link to post
Share on other sites

So, you still have to send your strings into the translator in English? MadJik might like it if he could program in French...

[/ignore me, I'm a trouble maker]

Share this post


Link to post
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

Share this post


Link to post
Share on other sites

So who will decide what we translate?

I'd just go with the pinned PlugIn-Packs.. though the authors will have to submit some kind of template we can use in our language pack, don't they?

Share this post


Link to post
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).

Share this post


Link to post
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.

Sign in to follow this