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.
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.
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 ;-)
If it's possible to get better, good is not the Status Quo!
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.
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.
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.
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.
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..
To translators: would you be okay using GitHub to edit the translation file? While it's intended to be used with a special program it's possible to edit the files online.