Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/duplicati/duplicati.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKenneth Skovhede <kenneth@hexad.dk>2016-09-27 21:44:19 +0300
committerKenneth Skovhede <kenneth@hexad.dk>2016-09-27 21:44:19 +0300
commit07345f6250958e5c42777274514cffbb2bc44887 (patch)
tree99ff0f0ecde9a1fbc48cc36a40aed6595393c948 /Duplicati/Library/Localization
parent521aa4daab8cb01e2f24067bad5b05c82524589e (diff)
Implemented PO file parsing in Localizations
Diffstat (limited to 'Duplicati/Library/Localization')
-rw-r--r--Duplicati/Library/Localization/Duplicati.Library.Localization.csproj2
-rw-r--r--Duplicati/Library/Localization/ILocalizationService.cs6
-rw-r--r--Duplicati/Library/Localization/LocalizationContext.cs64
-rw-r--r--Duplicati/Library/Localization/LocalizationService.cs99
-rw-r--r--Duplicati/Library/Localization/MockLocalizationService.cs8
-rw-r--r--Duplicati/Library/Localization/PoLocalizationService.cs254
-rw-r--r--Duplicati/Library/Localization/Short.cs2
7 files changed, 426 insertions, 9 deletions
diff --git a/Duplicati/Library/Localization/Duplicati.Library.Localization.csproj b/Duplicati/Library/Localization/Duplicati.Library.Localization.csproj
index 9b5fb236d..3bc0bcc1e 100644
--- a/Duplicati/Library/Localization/Duplicati.Library.Localization.csproj
+++ b/Duplicati/Library/Localization/Duplicati.Library.Localization.csproj
@@ -41,6 +41,8 @@
<Compile Include="ILocalizationService.cs" />
<Compile Include="MockLocalizationService.cs" />
<Compile Include="Short.cs" />
+ <Compile Include="PoLocalizationService.cs" />
+ <Compile Include="LocalizationContext.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
</Project> \ No newline at end of file
diff --git a/Duplicati/Library/Localization/ILocalizationService.cs b/Duplicati/Library/Localization/ILocalizationService.cs
index 38b7921cc..7f2df2007 100644
--- a/Duplicati/Library/Localization/ILocalizationService.cs
+++ b/Duplicati/Library/Localization/ILocalizationService.cs
@@ -16,6 +16,7 @@
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
using System;
+using System.Collections.Generic;
namespace Duplicati.Library.Localization
{
@@ -61,6 +62,11 @@ namespace Duplicati.Library.Localization
/// <param name="message">The string to localize</param>
/// <param name="args">The arguments</param>
string Localize(string message, params object[] args);
+
+ /// <summary>
+ /// Gets a lookup table with all strings
+ /// </summary>
+ IDictionary<string, string> AllStrings { get; }
}
}
diff --git a/Duplicati/Library/Localization/LocalizationContext.cs b/Duplicati/Library/Localization/LocalizationContext.cs
new file mode 100644
index 000000000..3554ccdcb
--- /dev/null
+++ b/Duplicati/Library/Localization/LocalizationContext.cs
@@ -0,0 +1,64 @@
+// Copyright (C) 2016, The Duplicati Team
+// http://www.duplicati.com, info@duplicati.com
+//
+// This library is free software; you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation; either version 2.1 of the
+// License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+using System;
+namespace Duplicati.Library.Localization
+{
+ /// <summary>
+ /// Helper class for choosing a temporary localization context
+ /// </summary>
+ internal class LocalizationContext : IDisposable
+ {
+ /// <summary>
+ /// The previous context
+ /// </summary>
+ private object m_prev;
+
+ /// <summary>
+ /// Flag to prevent double dispose
+ /// </summary>
+ private bool m_isDisposed = true;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="T:Duplicati.Library.Localization.LocalizationContext"/> class.
+ /// </summary>
+ /// <param name="ci">The localization to use.</param>
+ public LocalizationContext(System.Globalization.CultureInfo ci)
+ {
+ m_prev = System.Runtime.Remoting.Messaging.CallContext.LogicalGetData(LocalizationService.LOGICAL_CONTEXT_KEY);
+ System.Runtime.Remoting.Messaging.CallContext.LogicalSetData(LocalizationService.LOGICAL_CONTEXT_KEY, ci.Name);
+ m_isDisposed = false;
+ }
+
+ /// <summary>
+ /// Releases all resource used by the <see cref="T:Duplicati.Library.Localization.LocalizationContext"/> object.
+ /// </summary>
+ /// <remarks>Call <see cref="Dispose"/> when you are finished using the
+ /// <see cref="T:Duplicati.Library.Localization.LocalizationContext"/>. The <see cref="Dispose"/> method leaves
+ /// the <see cref="T:Duplicati.Library.Localization.LocalizationContext"/> in an unusable state. After calling
+ /// <see cref="Dispose"/>, you must release all references to the
+ /// <see cref="T:Duplicati.Library.Localization.LocalizationContext"/> so the garbage collector can reclaim the
+ /// memory that the <see cref="T:Duplicati.Library.Localization.LocalizationContext"/> was occupying.</remarks>
+ public void Dispose()
+ {
+ if (!m_isDisposed)
+ {
+ System.Runtime.Remoting.Messaging.CallContext.LogicalSetData(LocalizationService.LOGICAL_CONTEXT_KEY, m_prev);
+ m_isDisposed = true;
+ }
+ }
+ }
+}
diff --git a/Duplicati/Library/Localization/LocalizationService.cs b/Duplicati/Library/Localization/LocalizationService.cs
index 6a3646599..fe96552b7 100644
--- a/Duplicati/Library/Localization/LocalizationService.cs
+++ b/Duplicati/Library/Localization/LocalizationService.cs
@@ -16,6 +16,10 @@
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
using System;
+using System.Linq;
+using System.Globalization;
+using System.Collections.Generic;
+using System.Text.RegularExpressions;
namespace Duplicati.Library.Localization
{
@@ -25,31 +29,112 @@ namespace Duplicati.Library.Localization
public static class LocalizationService
{
/// <summary>
- /// A default service
+ /// The cache of services
/// </summary>
- private static ILocalizationService DefaultService = new MockLocalizationService();
+ private static Dictionary<CultureInfo, ILocalizationService> Services = new Dictionary<CultureInfo, ILocalizationService>();
+
+ /// <summary>
+ /// The key for accessing logical context
+ /// </summary>
+ internal const string LOGICAL_CONTEXT_KEY = "DUPLICATI_LOCALIZATION_CULTURE_CONTEXT";
+
+ /// <summary>
+ /// Regular expression to match a locale
+ /// </summary>
+ public static readonly Regex CI_MATCHER = new Regex(@"[A-z]{2}(-[A-z]{4})?(-[A-z]{2})?");
+
+ /// <summary>
+ /// Returns a temporary disposable localization context
+ /// </summary>
+ /// <returns>The context that must be disposed.</returns>
+ /// <param name="ci">The locale to use.</param>
+ public static IDisposable TemporaryContext(CultureInfo ci)
+ {
+ if (ci == null)
+ return null;
+
+ return new LocalizationContext(ci);
+ }
+
+ /// <summary>
+ /// A non-translating service
+ /// </summary>
+ private static ILocalizationService InvariantService = new MockLocalizationService();
/// <summary>
/// Gets a localization provider with the default language
/// </summary>
- public static ILocalizationService Default { get { return Get(System.Globalization.CultureInfo.InvariantCulture); } }
+ public static ILocalizationService Invariant { get { return Get(CultureInfo.InvariantCulture); } }
+
+ /// <summary>
+ /// Parses the culture string into a cultureinfo instance.
+ /// </summary>
+ /// <returns>The parsed culture.</returns>
+ /// <param name="culture">The culture string.</param>
+ /// <param name="returninvariant">Set to <c>true</c> to return the invariant culture if the string is not valid, otherwise null is returned.</param>
+ public static CultureInfo ParseCulture(string culture, bool returninvariant = false)
+ {
+ var ci = returninvariant ? CultureInfo.InvariantCulture : null;
+
+ if (CI_MATCHER.Match(culture).Success)
+ try { ci = new CultureInfo(culture); }
+ catch { }
+
+ return ci;
+ }
/// <summary>
/// Gets a localization provider with the current language
/// </summary>
- public static ILocalizationService Current { get { return Get(System.Globalization.CultureInfo.CurrentCulture); } }
+ public static ILocalizationService Current
+ {
+ get
+ {
+ var lc = System.Runtime.Remoting.Messaging.CallContext.LogicalGetData(LOGICAL_CONTEXT_KEY) as string;
+ if (!string.IsNullOrWhiteSpace(lc))
+ return Get(new CultureInfo(lc));
+ return Get(CultureInfo.CurrentCulture);
+ }
+ }
/// <summary>
/// Gets a localization provider with the OS install language
/// </summary>
- public static ILocalizationService Installed { get { return Get(System.Globalization.CultureInfo.InstalledUICulture); } }
+ public static ILocalizationService Installed { get { return Get(CultureInfo.InstalledUICulture); } }
/// <summary>
/// Gets a localization provider with the specified language
/// </summary>
- public static ILocalizationService Get(System.Globalization.CultureInfo ci)
+ public static ILocalizationService Get(CultureInfo ci)
+ {
+ if (ci == CultureInfo.InvariantCulture)
+ return InvariantService;
+
+ ILocalizationService service;
+ if (!Services.TryGetValue(ci, out service))
+ service = Services[ci] = new PoLocalizationService(ci);
+
+ return service;
+ }
+
+ /// <summary>
+ /// Gets a list of all locales known by the CLR
+ /// </summary>
+ /// <value>All locales.</value>
+ public static IEnumerable<string> AllLocales
+ {
+ get
+ {
+ return CultureInfo.GetCultures(CultureTypes.AllCultures).Select(x => x.Name).Where(x => !string.IsNullOrWhiteSpace(x)).Distinct().OrderBy(x => x);
+ }
+ }
+
+ /// <summary>
+ /// Gets all cultures with localization support
+ /// </summary>
+ public static IEnumerable<string> SupportedCultures
{
- return DefaultService;
+ get { return PoLocalizationService.SupportedCultures; }
}
}
}
diff --git a/Duplicati/Library/Localization/MockLocalizationService.cs b/Duplicati/Library/Localization/MockLocalizationService.cs
index c13e96e56..6c6df2e7d 100644
--- a/Duplicati/Library/Localization/MockLocalizationService.cs
+++ b/Duplicati/Library/Localization/MockLocalizationService.cs
@@ -16,6 +16,7 @@
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
using System;
+using System.Collections.Generic;
namespace Duplicati.Library.Localization
{
@@ -75,7 +76,12 @@ namespace Duplicati.Library.Localization
public string Localize(string message, params object[] args)
{
return string.Format(message, args);
- }
+ }
+
+ /// <summary>
+ /// Gets all strings.
+ /// </summary>
+ public IDictionary<string, string> AllStrings { get { return new Dictionary<string, string>(); } }
}
}
diff --git a/Duplicati/Library/Localization/PoLocalizationService.cs b/Duplicati/Library/Localization/PoLocalizationService.cs
new file mode 100644
index 000000000..9b748dd36
--- /dev/null
+++ b/Duplicati/Library/Localization/PoLocalizationService.cs
@@ -0,0 +1,254 @@
+// Copyright (C) 2016, The Duplicati Team
+// http://www.duplicati.com, info@duplicati.com
+//
+// This library is free software; you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation; either version 2.1 of the
+// License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+using System;
+using System.IO;
+using System.Linq;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Reflection;
+using System.Text.RegularExpressions;
+
+namespace Duplicati.Library.Localization
+{
+ /// <summary>
+ /// Class for reading embedded PO files
+ /// </summary>
+ public class PoLocalizationService : ILocalizationService
+ {
+ /// <summary>
+ /// The environment variable used to locate PO files
+ /// </summary>
+ public const string LOCALIZATIONDIR_ENVNAME = "LOCALIZATION_FOLDER";
+
+ /// <summary>
+ /// A cached copy of all strings
+ /// </summary>
+ private Dictionary<string, string> m_messages = new Dictionary<string, string>();
+
+ /// <summary>
+ /// Path to search for extra .po files in
+ /// </summary>
+ public static string[] SearchPaths =
+ string.IsNullOrWhiteSpace(AppDomain.CurrentDomain.GetData(LOCALIZATIONDIR_ENVNAME) as string)
+ ? new string[] { Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) }
+ : (AppDomain.CurrentDomain.GetData(LOCALIZATIONDIR_ENVNAME) as string).Split(new char[] { Path.PathSeparator }, StringSplitOptions.RemoveEmptyEntries);
+
+
+ /// <summary>
+ /// Assembly to look for .po files in
+ /// </summary>
+ public static Assembly SearchAssembly = Assembly.GetExecutingAssembly();
+
+ /// <summary>
+ /// Regular expression to match a PO line entry
+ /// </summary>
+ private static readonly Regex PO_MATCHER = new Regex(@"(?<key>\w+(\[(?<index>[0-9]+)\])?)\s+""(?<value>(?:\\.|""""|[^""\\])*)""");
+
+ /// <summary>
+ /// Regular expression to match a locale from a filename
+ /// </summary>
+ private static readonly Regex CI_MATCHER = new Regex(@"localization-(?<culture>" + LocalizationService.CI_MATCHER + @")\.po");
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="T:Duplicati.Library.Localization.PoLocalizationService"/> class.
+ /// </summary>
+ /// <param name="ci">The culture to find.</param>
+ public PoLocalizationService(CultureInfo ci)
+ {
+ var filenames = new string[] {
+ // Load the generic country version first
+ string.Format("localization-{0}.po", ci.Name),
+ // Then the specialized version with overrides
+ string.Format("localization-{0}.po", ci.TwoLetterISOLanguageName)
+ };
+
+ foreach(var fn in filenames)
+ {
+ // Use embedded version first
+ if (SearchAssembly != null)
+ {
+ // Find the localization streams inside the assembly
+ var names =
+ from name in SearchAssembly.GetManifestResourceNames()
+ let m = CI_MATCHER.Match(name)
+ let c = m.Success && string.Equals(m.Value, fn, StringComparison.InvariantCultureIgnoreCase) ? LocalizationService.ParseCulture(m.Groups["culture"].Value) : null
+ where c != null
+ select name;
+
+ foreach (var sn in names)
+ using (var s = SearchAssembly.GetManifestResourceStream(sn))
+ if (s != null)
+ using (var sr = new StreamReader(s, System.Text.Encoding.UTF8, true))
+ ParseStream(sr);
+ }
+
+ // Then override with external
+ foreach(var sp in SearchPaths)
+ if (!string.IsNullOrWhiteSpace(sp) && File.Exists(Path.Combine(sp, fn)))
+ using (var fs = new StreamReader(Path.Combine(sp, fn), System.Text.Encoding.UTF8, true))
+ ParseStream(fs);
+ }
+ }
+
+ /// <summary>
+ /// Parses a PO file by reading each line
+ /// </summary>
+ /// <param name="rd">The reader to extract data from.</param>
+ private void ParseStream(TextReader rd)
+ {
+ string ln;
+ string msgid = null;
+
+ while ((ln = rd.ReadLine()) != null)
+ {
+ var m = PO_MATCHER.Match(ln);
+ if (m.Success)
+ {
+ var key = m.Groups["key"].Value;
+ var value = m.Groups["value"].Value;
+
+ if (!string.IsNullOrEmpty(value))
+ value = value.Replace("\"\"", "\"").Replace("\\\"", "\"").Replace("\\t", "\t").Replace("\\r", "\r").Replace("\\n", "\n");
+
+
+ if (string.Equals(key, "msgid", StringComparison.OrdinalIgnoreCase))
+ {
+ msgid = value;
+ }
+ else if (string.Equals(key, "msgstr", StringComparison.OrdinalIgnoreCase))
+ {
+ m_messages[msgid] = value;
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Performs the actual translation
+ /// </summary>
+ /// <param name="msg">The message to translate.</param>
+ private string Transform(string msg)
+ {
+ string res;
+ if (string.IsNullOrWhiteSpace(msg) || !m_messages.TryGetValue(msg, out res))
+ return msg;
+
+ return res;
+ }
+
+ /// <summary>
+ /// Localizes the string similar to how string.Format works
+ /// </summary>
+ /// <param name="message">The string to localize</param>
+ /// <returns>The localized string</returns>
+ public string Localize(string message)
+ {
+ return string.Format(Transform(message));
+ }
+
+ /// <summary>
+ /// Localizes the string similar to how string.Format works
+ /// </summary>
+ /// <param name="message">The string to localize</param>
+ /// <param name="arg0">The first argument</param>
+ /// <returns>The localized string</returns>
+ public string Localize(string message, object arg0)
+ {
+ return string.Format(Transform(message), arg0);
+ }
+
+ /// <summary>
+ /// Localizes the string similar to how string.Format works
+ /// </summary>
+ /// <param name="message">The string to localize</param>
+ /// <param name="arg0">The first argument</param>
+ /// <param name="arg1">The second argument</param>
+ /// <returns>The localized string</returns>
+ public string Localize(string message, object arg0, object arg1)
+ {
+ return string.Format(Transform(message), arg0, arg1);
+ }
+
+ /// <summary>
+ /// Localizes the string similar to how string.Format works
+ /// </summary>
+ /// <param name="message">The string to localize</param>
+ /// <param name="arg0">The first argument</param>
+ /// <param name="arg1">The second argument</param>
+ /// <param name="arg2">The third argument</param>
+ /// <returns>The localized string</returns>
+ public string Localize(string message, object arg0, object arg1, object arg2)
+ {
+ return string.Format(Transform(message), arg0, arg1, arg2);
+ }
+
+ /// <summary>
+ /// Localizes the string similar to how string.Format works
+ /// </summary>
+ /// <param name="message">The string to localize</param>
+ /// <param name="args">The arguments</param>
+ public string Localize(string message, params object[] args)
+ {
+ return string.Format(Transform(message), args);
+ }
+
+ /// <summary>
+ /// Gets all strings.
+ /// </summary>
+ public IDictionary<string, string> AllStrings { get { return m_messages; } }
+
+ private static string[] m_supportedcultures = null;
+
+ /// <summary>
+ /// Gets a list of all the supported cultures
+ /// </summary>
+ public static IEnumerable<string> SupportedCultures
+ {
+ get
+ {
+
+ if (m_supportedcultures == null)
+ {
+ var lst = new string[0].AsEnumerable();
+
+ if (SearchAssembly != null)
+ lst = lst.Union(SearchAssembly.GetManifestResourceNames());
+ foreach (var sp in SearchPaths)
+ if (Directory.Exists(sp))
+ lst = lst.Union(Directory.GetFiles(sp, "localization-*.po"));
+
+ var allcultures =
+ from name in lst
+ let m = CI_MATCHER.Match(name)
+ let ci = m.Success ? LocalizationService.ParseCulture(m.Groups["culture"].Value) : null
+ where ci != null
+ select ci;
+
+ m_supportedcultures =
+ allcultures
+ .Select(x => x.TwoLetterISOLanguageName).Distinct().OrderBy(x => x)
+ .Union(
+ allcultures.Select(x => x.Name).Distinct().OrderBy(x => x)
+ ).ToArray();
+
+ }
+
+ return m_supportedcultures;
+ }
+ }
+ }
+}
diff --git a/Duplicati/Library/Localization/Short.cs b/Duplicati/Library/Localization/Short.cs
index c66c36487..88c0088f7 100644
--- a/Duplicati/Library/Localization/Short.cs
+++ b/Duplicati/Library/Localization/Short.cs
@@ -27,7 +27,7 @@ namespace Duplicati.Library.Localization.Short
/// <summary>
/// The instance for translation
/// </summary>
- private static ILocalizationService LS = LocalizationService.Default;
+ private static ILocalizationService LS = LocalizationService.Invariant;
/// <summary>
/// Localizes the string similar to how string.Format works