[gbrainy] Tool to assist to spot translations with mismatching string formatters and expression variables
- From: Jordi Mas <jmas src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gbrainy] Tool to assist to spot translations with mismatching string formatters and expression variables
- Date: Sun, 31 Oct 2010 15:51:44 +0000 (UTC)
commit e8fc19bec7a2679ad00b6d134733a39ff43d04e5
Author: Jordi Mas <jmas softcatala org>
Date: Sun Oct 31 16:50:40 2010 +0100
Tool to assist to spot translations with mismatching string formatters and expression variables
tools/GetTextParser/CatalogParser.cs | 344 +++++++++++++++++++++++++++++++++
tools/GetTextParser/StringEscaping.cs | 270 ++++++++++++++++++++++++++
tools/Makefile.am | 8 +
tools/TranslationsChecker.cs | 172 ++++++++++++++++
4 files changed, 794 insertions(+), 0 deletions(-)
---
diff --git a/tools/GetTextParser/CatalogParser.cs b/tools/GetTextParser/CatalogParser.cs
new file mode 100644
index 0000000..303c8f4
--- /dev/null
+++ b/tools/GetTextParser/CatalogParser.cs
@@ -0,0 +1,344 @@
+//
+// CatalogParser.cs
+//
+// Author:
+// David Makovsk� <yakeen sannyas-on net>
+//
+// Copyright (C) 1999-2006 Vaclav Slavik (Code and design inspiration - poedit.org)
+// Copyright (C) 2007 David Makovsk�
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.IO;
+
+namespace MonoDevelop.Gettext
+{
+ public abstract class CatalogParser
+ {
+ //string fileName;
+ string loadedFile;
+ string[] fileLines;
+ string newLine;
+ int lineNumber = 0;
+
+ public CatalogParser (string fileName, Encoding encoding)
+ {
+ //this.fileName = fileName;
+ loadedFile = File.ReadAllText (fileName, encoding);
+ fileLines = CatalogParser.GetLines (loadedFile, out newLine);
+ }
+
+ // Returns new line constant used in file
+ public string NewLine
+ {
+ get { return newLine; }
+ }
+
+ static string[] GetLines (string fileStr, out string newLine)
+ {
+ // TODO: probe first new line...
+ int posLN = fileStr.IndexOf ('\n');
+ int posCR = fileStr.IndexOf ('\r');
+ string useNewLineForParsing = String.Empty;
+ newLine = String.Empty;
+
+ if (posLN != -1) {
+ if (posCR - 1 == posLN) {
+ newLine = "\r\n"; //CRLF
+ } else if (posCR == -1) {
+ newLine = "\n"; //LF
+ }
+ } else if (posCR != -1 && posLN == -1) {
+ newLine = "\r"; //LF
+ } else {
+ newLine = Environment.NewLine; //mixed for writing use system one
+ int countLN = 0;
+ int start = 0;
+ while ((start = fileStr.IndexOf ('\n', start)) != -1)
+ countLN++;
+
+ int countCR = 0;
+ start = 0;
+ while ((start = fileStr.IndexOf ('\r', start)) != -1)
+ countCR++;
+
+ // for parsing use one with more occurences
+ useNewLineForParsing = countCR > countLN ? "\r" : "\n";
+ }
+
+ if (useNewLineForParsing == String.Empty)
+ useNewLineForParsing = newLine;
+
+ List<string> lines = new List<string> ();
+
+ foreach (string line in fileStr.Split (new string[] {useNewLineForParsing}, StringSplitOptions.None)) {
+ lines.Add (line);
+ }
+ return lines.ToArray ();
+ }
+
+ // If input begins with pattern, fill output with end of input (without
+ // pattern; strips trailing spaces) and return true. Return false otherwise
+ // and don't touch output
+ static bool ReadParam (string input, string pattern, out string output)
+ {
+ output = String.Empty;
+ input = input.TrimStart (' ', '\t');
+ if (input.Length < pattern.Length)
+ return false;
+
+ if (! input.StartsWith (pattern))
+ return false;
+
+ output = StringEscaping.FromGettextFormat (input.Substring (pattern.Length).TrimEnd (' ', '\t'));
+ return true;
+ }
+
+ string ParseMessage (ref string line, ref string dummy, ref int lineNumber)
+ {
+ StringBuilder result = new StringBuilder (dummy.Substring (0, dummy.Length - 1));
+
+ while ((line = fileLines[lineNumber++]) != String.Empty) {
+ if (line[0] == '\t')
+ line = line.Substring (1);
+
+ if (line[0] == '"' && line[line.Length - 1] == '"') {
+ result.Append (StringEscaping.FromGettextFormat (line.Substring (1, line.Length - 2)));
+ } else
+ break;
+ }
+ return result.ToString ();
+ }
+
+ // Parses the entire file, calls OnEntry each time msgid/msgstr pair is found.
+ // return false if parsing failed, true otherwise
+ public bool Parse ()
+ {
+ if (fileLines.Length == 0)
+ return false;
+
+ string line, dummy;
+ string mflags = String.Empty;
+ string mstr = String.Empty;
+ string msgidPlural = String.Empty;
+ string mcomment = String.Empty;
+ List<string> mrefs = new List<string> ();
+ List<string> mautocomments = new List<string> ();
+ List<string> mtranslations = new List<string> ();
+ bool hasPlural = false;
+
+ line = fileLines[lineNumber++];
+ if (line == String.Empty)
+ line = fileLines[lineNumber++];
+
+ while (line != String.Empty)
+ {
+ // ignore empty special tags (except for automatic comments which we
+ // DO want to preserve):
+ while (line == "#," || line == "#:")
+ line = fileLines[lineNumber++];
+
+ // flags:
+ // Can't we have more than one flag, now only the last is kept ...
+ if (CatalogParser.ReadParam (line, "#, ", out dummy))
+ {
+ mflags = dummy; //"#, " +
+ line = fileLines[lineNumber++];
+ }
+
+ // auto comments:
+ if (CatalogParser.ReadParam (line, "#. ", out dummy) || CatalogParser.ReadParam (line, "#.", out dummy)) // second one to account for empty auto comments
+ {
+ mautocomments.Add (dummy);
+ line = fileLines[lineNumber++];
+ }
+
+ // references:
+ else if (CatalogParser.ReadParam (line, "#: ", out dummy))
+ {
+ // A line may contain several references, separated by white-space.
+ // Each reference is in the form "path_name:line_number"
+ // (path_name may contain spaces)
+ dummy = dummy.Trim ();
+ while (dummy != String.Empty) {
+ int i = 0;
+ while (i < dummy.Length && dummy[i] != ':') {
+ i++;
+ }
+ while (i < dummy.Length && ! Char.IsWhiteSpace (dummy[i])) {
+ i++;
+ }
+
+ mrefs.Add (dummy.Substring (0, i));
+ dummy = dummy.Substring (i).Trim ();
+ }
+
+ line = fileLines[lineNumber++];
+ }
+
+ // msgid:
+ else if (CatalogParser.ReadParam (line, "msgid \"", out dummy) ||
+ CatalogParser.ReadParam (line, "msgid\t\"", out dummy))
+ {
+ mstr = ParseMessage (ref line, ref dummy, ref lineNumber);
+ }
+
+ // msgid_plural:
+ else if (CatalogParser.ReadParam (line, "msgid_plural \"", out dummy) ||
+ CatalogParser.ReadParam (line, "msgid_plural\t\"", out dummy))
+ {
+ msgidPlural = ParseMessage (ref line, ref dummy, ref lineNumber);
+ hasPlural = true;
+ }
+
+ // msgstr:
+ else if (CatalogParser.ReadParam (line, "msgstr \"", out dummy) ||
+ CatalogParser.ReadParam (line, "msgstr\t\"", out dummy))
+ {
+ if (hasPlural) {
+ // TODO: use logging
+ Console.WriteLine ("Broken catalog file: singular form msgstr used together with msgid_plural");
+ return false;
+ }
+
+
+ string str = ParseMessage (ref line, ref dummy, ref lineNumber);
+ mtranslations.Add (str);
+
+ if (! OnEntry (mstr, String.Empty, false, mtranslations.ToArray (),
+ mflags, mrefs.ToArray (), mcomment,
+ mautocomments.ToArray ()))
+ {
+ return false;
+ }
+
+ mcomment = mstr = msgidPlural = mflags = String.Empty;
+ hasPlural = false;
+ mrefs.Clear ();
+ mautocomments.Clear ();
+ mtranslations.Clear ();
+ } else if (CatalogParser.ReadParam (line, "msgstr[", out dummy)) {
+ // msgstr[i]:
+ if (!hasPlural){
+ // TODO: use logging
+ Console.WriteLine ("Broken catalog file: plural form msgstr used without msgid_plural");
+ return false;
+ }
+
+ int pos = dummy.IndexOf (']');
+ string idx = dummy.Substring (pos - 1, 1);
+ string label = "msgstr[" + idx + "]";
+
+ while (CatalogParser.ReadParam (line, label + " \"", out dummy) || CatalogParser.ReadParam (line, label + "\t\"", out dummy)) {
+ StringBuilder str = new StringBuilder (dummy.Substring (0, dummy.Length - 1));
+
+ while ((line = fileLines[lineNumber++]) != String.Empty) {
+ if (line[0] == '\t')
+ line = line.Substring (1);
+ if (line[0] == '"' && line[line.Length - 1] == '"') {
+ str.Append (line.Substring (1, line.Length - 2));
+ } else {
+ if (ReadParam (line, "msgstr[", out dummy)) {
+ pos = dummy.IndexOf (']');
+ idx = dummy.Substring (pos - 1, 1);
+ label = "msgstr[" + idx + "]";
+ }
+ break;
+ }
+ }
+ mtranslations.Add (str.ToString ());
+ }
+
+ if (! OnEntry (mstr, msgidPlural, true, mtranslations.ToArray (),
+ mflags, mrefs.ToArray (), mcomment,
+ mautocomments.ToArray ()))
+ {
+ return false;
+ }
+
+ mcomment = mstr = msgidPlural = mflags = String.Empty;
+ hasPlural = false;
+ mrefs.Clear ();
+ mautocomments.Clear ();
+ mtranslations.Clear ();
+ }else if (CatalogParser.ReadParam (line, "#~ ", out dummy)) {
+ // deleted lines:
+
+ List<string> deletedLines = new List<string> ();
+ deletedLines.Add (line);
+ while ((line = fileLines[lineNumber++]) != String.Empty) {
+ // if line does not start with "#~ " anymore, stop reading
+ if (! ReadParam (line, "#~ ", out dummy))
+ break;
+
+ deletedLines.Add (line);
+ }
+ if (! OnDeletedEntry (deletedLines.ToArray (), mflags, null, mcomment, mautocomments.ToArray ()))
+ return false;
+
+ mcomment = mstr = msgidPlural = mflags = String.Empty;
+ hasPlural = false;
+ mrefs.Clear ();
+ mautocomments.Clear ();
+ mtranslations.Clear ();
+ } else if (line[0] == '#') {
+ // comment:
+
+ while (line != String.Empty &&
+ ((line[0] == '#' && line.Length < 2) ||
+ (line[0] == '#' && line[1] != ',' && line[1] != ':' && line[1] != '.')))
+ {
+ mcomment += mcomment.Length > 0 ? '\n' + line : line;
+ line = fileLines[lineNumber++];
+ }
+ } else {
+ line = fileLines[lineNumber++];
+
+ }
+
+ while (line == String.Empty && lineNumber < fileLines.Length)
+ line = fileLines[lineNumber++];
+ }
+
+ return true;
+ }
+
+ // Called when new entry was parsed. Parsing continues
+ // if returned value is true and is cancelled if it is false.
+ protected abstract bool OnEntry (string msgid, string msgidPlural, bool hasPlural,
+ string[] translations, string flags,
+ string[] references, string comment,
+ string[] autocomments);
+
+ // Called when new deleted entry was parsed. Parsing continues
+ // if returned value is true and is cancelled if it
+ // is false. Defaults to an empty implementation.
+ protected virtual bool OnDeletedEntry (string[] deletedLines, string flags,
+ string[] references, string comment,
+ string[] autocomments)
+ {
+ return true;
+ }
+ }
+}
diff --git a/tools/GetTextParser/StringEscaping.cs b/tools/GetTextParser/StringEscaping.cs
new file mode 100644
index 0000000..b68c3b4
--- /dev/null
+++ b/tools/GetTextParser/StringEscaping.cs
@@ -0,0 +1,270 @@
+//
+// StringEscaping.cs
+//
+// Author:
+// Michael Hutchinson <mhutchinson novell com>
+//
+// Copyright (C) 2008 Novell, Inc (http://www.novell.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Globalization;
+using System.Text;
+
+namespace MonoDevelop.Gettext
+{
+
+
+ public static class StringEscaping
+ {
+ //based on http://www-ccs.ucsd.edu/c/charset.html
+ //with modififications, as Gettext dosn't follow all C escaping
+ public static string ToGettextFormat (string text)
+ {
+ StringBuilder sb = new StringBuilder ();
+ for (int i = 0; i < text.Length; i++) {
+ char c = text[i];
+ switch (c) {
+ case '"':
+ sb.Append ("\\\"");
+ continue;
+
+ //Gettext doesn't like escaping this
+ //case '\'':
+ // sb.Append ("\\'");
+ // continue;
+
+ //These shouldn't be in translations... but will be caught by IsControl
+ //case '\a':
+ //case '\b':
+ //case '\f':
+ //case '\v':
+
+ //this doesn't matter since we're not dealing with trigraphs
+ //case "?":
+ // sb.Append ("\\?");
+ // continue;
+
+ case '\\':
+ sb.Append ("\\\\");
+ continue;
+ case '\n':
+ sb.Append ("\\n");
+ continue;
+ case '\r':
+ sb.Append ("\\r");
+ continue;
+ case '\t':
+ sb.Append ("\\t");
+ continue;
+ }
+
+ if (c != '_' && char.IsControl (c))
+ throw new FormatException (String.Format (("Invalid character '{0}' in translatable string: '{1}'"),
+ c,
+ text));
+
+ sb.Append (c);
+ }
+ return sb.ToString ();
+ }
+
+ public static string FromGettextFormat (string text)
+ {
+ StringBuilder sb = new StringBuilder ();
+ for (int i = 0; i < text.Length; i++) {
+ char c = text[i];
+ switch (c) {
+ case '\\':
+ if (i + 1 < text.Length) {
+ char nextChar = text [i + 1];
+ if (nextChar == '\\' || nextChar == '"') {
+ sb.Append (nextChar);
+ i++;
+ continue;
+ }
+ if (nextChar == 'n') {
+ sb.Append ('\n');
+ i++;
+ continue;
+ }
+ if (nextChar == 't') {
+ sb.Append ('\t');
+ i++;
+ continue;
+ }
+ if (nextChar == 'r') {
+ sb.Append ('\r');
+ i++;
+ continue;
+ }
+
+ throw new FormatException (String.Format ("Invalid escape sequence '{0}' in string: '{1}'",
+ nextChar,
+ text));
+ }
+ break;
+ }
+
+ sb.Append (c);
+ }
+ return sb.ToString ();
+ }
+
+ public static string UnEscape (EscapeMode mode, string text)
+ {
+ switch (mode) {
+ case EscapeMode.None:
+ return text;
+ case EscapeMode.CSharp:
+ return FromCSharpFormat (text);
+ case EscapeMode.CSharpVerbatim:
+ return FromCSharpVerbatimFormat (text);
+ case EscapeMode.Xml:
+ return FromXml (text);
+ default:
+ throw new Exception ("Unknown string escaping mode '" + mode.ToString () + "'");
+ }
+ }
+
+ /*
+ //based on http://www-ccs.ucsd.edu/c/charset.html
+ public static string FromCFormat (string text)
+ {
+ }
+ */
+
+ //based on the C# 2.0 spec
+ static string FromCSharpVerbatimFormat (string text)
+ {
+ StringBuilder sb = new StringBuilder ();
+ for (int i = 0; i < text.Length; i++) {
+ char c1 = text[i];
+ if (c1 == '"') {
+ i++;
+ char c2 = text[i];
+ if (c2 != '"')
+ throw new FormatException ("Unescaped '\"' character in C# verbatim string.");
+ }
+ sb.Append (c1);
+ }
+ return sb.ToString ();
+ }
+
+ static string FromXml (string text)
+ {
+ StringBuilder sb = new StringBuilder ();
+ for (int i = 0; i < text.Length; i++) {
+ char c1 = text[i];
+ if (c1 == '&') {
+ int end = text.IndexOf (';', i);
+ if (end == -1)
+ throw new FormatException ("Unterminated XML entity.");
+ string entity = text.Substring (i+1, end - i - 1);
+ switch (entity) {
+ case "lt":
+ sb.Append ('<');
+ break;
+ case "gt":
+ sb.Append ('>');
+ break;
+ case "amp":
+ sb.Append ('&');
+ break;
+ case "apos":
+ sb.Append ('\'');
+ break;
+ case "quot":
+ sb.Append ('"');
+ break;
+ default:
+ throw new FormatException ("Unrecogised XML entity '&" + entity + ";'.");
+ }
+ i = end;
+ } else
+ sb.Append (c1);
+ }
+ return sb.ToString ();
+ }
+
+ //based on the C# 2.0 spec
+ static string FromCSharpFormat (string text)
+ {
+ StringBuilder sb = new StringBuilder ();
+ for (int i = 0; i < text.Length; i++) {
+ char c1 = text[i];
+ if (c1 != '\\') {
+ sb.Append (c1);
+ continue;
+ }
+
+ i++;
+ char c2 = text[i];
+
+ switch (c2) {
+ case '\'':
+ case '"':
+ case '\\':
+ sb.Append (c2);
+ break;
+ case 'n':
+ sb.Append ('\n');
+ break;
+ case 'r':
+ sb.Append ('\r');
+ break;
+ case 't':
+ sb.Append ('\t');
+ break;
+ case 'U':
+ //FIXME UNICODE
+ //break;
+ case 'u':
+ //FIXME unicode
+ //break;
+ case 'x':
+ //FIXME hex unicode
+ //break;
+ //if (char.IsControl (c);
+
+ //case '0':
+ //case 'a':
+ //case 'b':
+ //case 'f':
+ //case 'v':
+ default:
+ throw new FormatException ("Invalid escape '\\" + c2 + "' in translatable string.");
+ }
+
+ }
+ return sb.ToString ();
+ }
+
+ public enum EscapeMode
+ {
+ None,
+ CSharp,
+ CSharpVerbatim,
+ Xml
+ }
+ }
+}
diff --git a/tools/Makefile.am b/tools/Makefile.am
index 8c99c83..a379e69 100644
--- a/tools/Makefile.am
+++ b/tools/Makefile.am
@@ -3,6 +3,11 @@ EXTRAFLAGS = $(CSC_DEFINES)
GAMESXMLSTRINGS_CSFILES = \
$(srcdir)/GameXmlToGetString.cs
+TRANSLATIONSCHECKER_CSFILES = \
+ $(srcdir)/TranslationsChecker.cs \
+ $(srcdir)/GetTextParser/CatalogParser.cs \
+ $(srcdir)/GetTextParser/StringEscaping.cs
+
ASSEMBLIES = \
-r:../src/gbrainy.Core.dll \
-r:Mono.Posix
@@ -11,6 +16,9 @@ GameXmlToGetString.exe: $(GAMESXMLSTRINGS_CSFILES) ../data/games.xml $(srcdir)/G
$(CSC) -target:winexe -out:$@ $(EXTRAFLAGS) $(GAMESXMLSTRINGS_CSFILES) $(ASSEMBLIES)
export MONO_PATH=../src && $(MONO) $@ $(srcdir)
+TranslationsChecker.exe: $(TRANSLATIONSCHECKER_CSFILES)
+ $(CSC) -target:winexe -out:$@ $(EXTRAFLAGS) $(TRANSLATIONSCHECKER_CSFILES) $(ASSEMBLIES)
+
all: GameXmlToGetString.exe
EXTRA_DIST = $(GAMESXMLSTRINGS_CSFILES) $(srcdir)/GameXmlGetStringTemplate.cs $(srcdir)/GameXmlGetString.cs
diff --git a/tools/TranslationsChecker.cs b/tools/TranslationsChecker.cs
new file mode 100644
index 0000000..21de2d8
--- /dev/null
+++ b/tools/TranslationsChecker.cs
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2010 Jordi Mas i Hernà ndez <jmas softcatala org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+using System;
+using System.Text;
+using System.IO;
+using System.Collections.Generic;
+using MonoDevelop.Gettext;
+using System.Text.RegularExpressions;
+
+public class GameXmlToGetString
+{
+ public class Parser : CatalogParser
+ {
+ public Parser (string fileName, Encoding encoding) : base (fileName, encoding)
+ {
+
+ }
+
+ List <string> GetExpressionVariables (string str)
+ {
+ Regex regex;
+ Match match;
+ List <string> strs = new List <string> ();
+ string expression = "(\\[[a-z0-9._%+-]*\\])+"; // alike [age]
+
+ regex = new Regex (expression, RegexOptions.IgnoreCase);
+ match = regex.Match (str);
+
+ if (String.IsNullOrEmpty (match.Value) == false)
+ {
+ while (String.IsNullOrEmpty (match.Value) == false)
+ {
+ strs.Add (match.Value);
+ match = match.NextMatch ();
+ }
+ }
+ return strs;
+ }
+
+ List <string> GetStringFormaters (string str)
+ {
+ Regex regex;
+ Match match;
+ List <string> strs = new List <string> ();
+ string expression = "(\\{[a-z0-9.:_%+-]*\\})+"; // alike [age]
+
+ regex = new Regex (expression, RegexOptions.IgnoreCase);
+ match = regex.Match (str);
+
+ if (String.IsNullOrEmpty (match.Value) == false)
+ {
+ while (String.IsNullOrEmpty (match.Value) == false)
+ {
+ strs.Add (match.Value);
+ match = match.NextMatch ();
+ }
+ }
+ return strs;
+ }
+
+
+ int Count (List <string> strings, string str)
+ {
+ int cnt = 0;
+
+ foreach (string s in strings)
+ {
+ if (s == str)
+ cnt++;
+ }
+ return cnt;
+
+ }
+
+ protected override bool OnEntry (string msgid, string msgidPlural, bool hasPlural,
+ string[] translations, string flags,
+ string[] references, string comment,
+ string[] autocomments)
+ {
+
+ if (String.IsNullOrEmpty (translations [0]))
+ return true;
+
+ // This string uses [] but not variables expressions
+ if (msgid == "Usage: gbrainy [options]")
+ return true;
+
+ if (flags.IndexOf ("fuzzy") != -1)
+ return true;
+
+ // Check Expression variables (like [age])
+ List <string> source = GetExpressionVariables (msgid);
+
+ for (int i = 0; i < translations.Length; i++)
+ {
+ List <string> target = GetExpressionVariables (translations [i]);
+
+ foreach (string s in source)
+ {
+ if (Count (source, s) != Count (target, s))
+ {
+ Console.WriteLine ("Gbrainy expression variable error. In '{0}' string '{1}' count does not match", msgid, s);
+ }
+ }
+ }
+
+ // Check Formatters (like {0})
+ source = GetStringFormaters (msgid);
+
+ for (int i = 0; i < translations.Length; i++)
+ {
+ List <string> target = GetStringFormaters (translations [i]);
+
+ foreach (string s in source)
+ {
+ if (Count (source, s) != Count (target, s))
+ {
+ Console.WriteLine ("String Formatter error. In '{0}' string '{1}' count does not match", msgid, s);
+ }
+ }
+ }
+
+ return true;
+ }
+ }
+
+ /*
+ This tool scans the LINGUAS files and searches for potential
+ mismatching string formatters and expression variables that can
+ cause problems at runtime.
+ */
+ static void Main (string[] args)
+ {
+ string line, file;
+ Stream read = File.OpenRead ("../po/LINGUAS");
+ StreamReader sr = new StreamReader (read);
+
+ while (true) {
+ line = sr.ReadLine ();
+ if (line == null)
+ break;
+
+ if (line.StartsWith ("#") == true)
+ continue;
+
+ file = "../po/" + line + ".po";
+
+ Console.WriteLine ("Openning {0}", file);
+ Parser parser = new Parser (file, Encoding.UTF8);
+ parser.Parse ();
+ }
+ }
+}
+
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]