diff --git a/Vernacular.Catalog/Vernacular/PluralRules.cs b/Vernacular.Catalog/Vernacular/PluralRules.cs index a5216b2..3795bd3 100644 --- a/Vernacular.Catalog/Vernacular/PluralRules.cs +++ b/Vernacular.Catalog/Vernacular/PluralRules.cs @@ -3,8 +3,10 @@ // // Author: // Aaron Bockover +// Stephane Delcroix // // Copyright 2012 Rdio, Inc. +// Copyright 2012 S. Delcroix // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -30,7 +32,29 @@ namespace Vernacular { public static class PluralRules { + struct PluralInfo + { + public int nforms; + public string header; + public Func getOrder; + } + public static int GetOrder (string isoLanguageCode, int n) + { + return GetPluralInfo(isoLanguageCode).getOrder(n); + } + + public static int GetNumberOfPlurals (string isoLanguageCode) + { + return GetPluralInfo(isoLanguageCode).nforms; + } + + public static string GetPluralHeader (string isoLanguageCode) + { + return GetPluralInfo(isoLanguageCode).header; + } + + static PluralInfo GetPluralInfo (string isoLanguageCode) { switch (isoLanguageCode) { case "ay": // Aymará @@ -56,10 +80,19 @@ public static int GetOrder (string isoLanguageCode, int n) case "vi": // Vietnamese case "wo": // Wolof // 1 form - return 0; + return new PluralInfo + { + nforms = 1, + header = "Plural-Forms: nplurals=1; plural=0;", + getOrder = n => 0 + }; case "is": // Icelandic // 2 forms - return (n % 10 != 1 || n % 100 == 11) ? 1 : 0; + return new PluralInfo { + nforms = 2, + header = "Plural-Forms: nplurals=2; plural=(n % 10 != 1 || n % 100 == 11) ? 1 : 0;", + getOrder = n => (n % 10 != 1 || n % 100 == 11) ? 1 : 0 + }; case "af": // Afrikaans case "an": // Aragonese case "ast": // Asturian @@ -124,13 +157,25 @@ public static int GetOrder (string isoLanguageCode, int n) case "ur": // Urdu case "yo": // Yoruba // 2 forms - return n != 1 ? 1 : 0; + return new PluralInfo { + nforms = 2, + header = "Plural-Forms: nplurals=2; plural=n != 1 ? 1 : 0;", + getOrder = n => n != 1 ? 1 : 0 + }; case "mk": // Macedonian // 2 forms - return n == 1 || n % 10 == 1 ? 0 : 1; + return new PluralInfo { + nforms = 2, + header = "Plural-Forms: nplurals=2; plural=n == 1 || n % 10 == 1 ? 0 : 1;", + getOrder = n => n == 1 || n % 10 == 1 ? 0 : 1 + }; case "jv": // Javanese // 2 forms - return n != 0 ? 1 : 0; + return new PluralInfo { + nforms = 2, + header = "Plural-Forms: nplurals=2; plural=n != 0 ? 1 : 0;", + getOrder = n => n != 0 ? 1 : 0 + }; case "ach": // Acholi (maybe) case "ak": // Akan case "am": // Amharic @@ -150,23 +195,47 @@ public static int GetOrder (string isoLanguageCode, int n) case "wa": // Walloon case "zh": // Chinese // 2 forms - return n > 1 ? 1 : 0; + return new PluralInfo { + nforms = 2, + header = "Plural-Forms: nplurals=2; plural=n > 1 ? 1 : 0;", + getOrder = n => n > 1 ? 1 : 0 + }; case "lt": // Lithuanian // 3 forms - return n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2; + return new PluralInfo { + nforms = 3, + header = "Plural-Forms: nplurals=3; plural=n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2;", + getOrder = n => n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2 + }; case "cs": // Czech case "sk": // Slovak // 3 forms - return n == 1 ? 0 : (n >= 2 && n <= 4) ? 1 : 2; + return new PluralInfo { + nforms = 3, + header = "Plural-Forms: nplurals=3; plural=n == 1 ? 0 : (n >= 2 && n <= 4) ? 1 : 2;", + getOrder = n => n == 1 ? 0 : (n >= 2 && n <= 4) ? 1 : 2 + }; case "mnk": // Mandinka // 3 forms - return n == 0 ? 0 : n == 1 ? 1 : 2; + return new PluralInfo { + nforms = 3, + header = "Plural-Forms: nplurals=3; plural=n == 0 ? 0 : n == 1 ? 1 : 2;", + getOrder = n => n == 0 ? 0 : n == 1 ? 1 : 2 + }; case "lv": // Latvian // 3 forms - return n % 10 == 1 && n % 100 != 11 ? 0 : n != 0 ? 1 : 2; + return new PluralInfo { + nforms = 3, + header = "Plural-Forms: nplurals=3; plural=n % 10 == 1 && n % 100 != 11 ? 0 : n != 0 ? 1 : 2;", + getOrder = n => n % 10 == 1 && n % 100 != 11 ? 0 : n != 0 ? 1 : 2 + }; case "ro": // Romanian // 3 forms - return n == 1 ? 0 : (n == 0 || (n % 100 > 0 && n % 100 < 20)) ? 1 : 2; + return new PluralInfo { + nforms = 3, + header = "Plural-Forms: nplurals=3; plural=n == 1 ? 0 : (n == 0 || (n % 100 > 0 && n % 100 < 20)) ? 1 : 2;", + getOrder = n => n == 1 ? 0 : (n == 0 || (n % 100 > 0 && n % 100 < 20)) ? 1 : 2 + }; case "be": // Belarusian case "bs": // Bosnian case "hr": // Croatian @@ -174,30 +243,66 @@ public static int GetOrder (string isoLanguageCode, int n) case "sr": // Serbian case "uk": // Ukrainian // 3 forms - return n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2; + return new PluralInfo { + nforms = 3, + header = "Plural-Forms: nplurals=3; plural=n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2;", + getOrder = n => n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2 + }; case "pl": // Polish // 3 forms - return n == 1 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2; + return new PluralInfo { + nforms = 3, + header = "Plural-Forms: nplurals=3; plural=n == 1 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2;", + getOrder = n => n == 1 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2 + }; case "kw": // Cornish // 4 forms - return n == 1 ? 0 : (n == 2) ? 1 : (n == 3) ? 2 : 3; + return new PluralInfo { + nforms = 4, + header = "Plural-Forms: nplurals=4; plural=n == 1 ? 0 : (n == 2) ? 1 : (n == 3) ? 2 : 3;", + getOrder = n => n == 1 ? 0 : (n == 2) ? 1 : (n == 3) ? 2 : 3 + }; case "gd": // Scottish Gaelic // 4 forms - return (n == 1 || n == 11) ? 0 : (n == 2 || n == 12) ? 1 : (n > 2 && n < 20) ? 2 : 3; + return new PluralInfo { + nforms = 4, + header = "Plural-Forms: nplurals=4; plural=(n == 1 || n == 11) ? 0 : (n == 2 || n == 12) ? 1 : (n > 2 && n < 20) ? 2 : 3;", + getOrder = n => (n == 1 || n == 11) ? 0 : (n == 2 || n == 12) ? 1 : (n > 2 && n < 20) ? 2 : 3 + }; case "mt": // Maltese // 4 forms - return n == 1 ? 0 : n == 0 || (n % 100 > 1 && n % 100 < 11) ? 1 : (n % 100 > 10 && n % 100 < 20) ? 2 : 3; + return new PluralInfo { + nforms = 4, + header = "Plural-Forms: nplurals=4; plural=n == 1 ? 0 : n == 0 || (n % 100 > 1 && n % 100 < 11) ? 1 : (n % 100 > 10 && n % 100 < 20) ? 2 : 3;", + getOrder = n => n == 1 ? 0 : n == 0 || (n % 100 > 1 && n % 100 < 11) ? 1 : (n % 100 > 10 && n % 100 < 20) ? 2 : 3 + }; case "cy": // Welsh // 4 forms - return n == 1 ? 0 : (n == 2) ? 1 : (n != 8 && n != 11) ? 2 : 3; + return new PluralInfo { + nforms = 4, + header = "Plural-Forms: nplurals=4; plural=n == 1 ? 0 : (n == 2) ? 1 : (n != 8 && n != 11) ? 2 : 3;", + getOrder = n => n == 1 ? 0 : (n == 2) ? 1 : (n != 8 && n != 11) ? 2 : 3 + }; case "sl": // Slovenian // 4 forms - return n % 100 == 1 ? 1 : n % 100 == 2 ? 2 : n % 100 == 3 || n % 100 == 4 ? 3 : 0; + return new PluralInfo { + nforms = 4, + header = "Plural-Forms: nplurals=4; plural=n % 100 == 1 ? 1 : n % 100 == 2 ? 2 : n % 100 == 3 || n % 100 == 4 ? 3 : 0;", + getOrder = n => n % 100 == 1 ? 1 : n % 100 == 2 ? 2 : n % 100 == 3 || n % 100 == 4 ? 3 : 0 + }; case "ga": // Irish // 5 forms - return n == 1 ? 0 : n == 2 ? 1 : n < 7 ? 2 : n < 11 ? 3 : 4; + return new PluralInfo { + nforms = 5, + header = "Plural-Forms: nplurals=5; plural=n == 1 ? 0 : n == 2 ? 1 : n < 7 ? 2 : n < 11 ? 3 : 4;", + getOrder = n => n == 1 ? 0 : n == 2 ? 1 : n < 7 ? 2 : n < 11 ? 3 : 4 + }; default: - return n != 1 ? 1 : 0; + return new PluralInfo { + nforms = 2, + header = "Plural-Forms: nplurals=2; plural=n != 1 ? 1 : 0;", + getOrder = n => n != 1 ? 1 : 0 + }; } } } diff --git a/Vernacular.Potato/Vernacular.Potato/Document.cs b/Vernacular.Potato/Vernacular.Potato/Document.cs index 0dfbd3b..f890e3b 100644 --- a/Vernacular.Potato/Vernacular.Potato/Document.cs +++ b/Vernacular.Potato/Vernacular.Potato/Document.cs @@ -31,6 +31,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; +using System.Linq; using Vernacular.Potato.Internal; @@ -109,7 +110,6 @@ private IEnumerable GetAllUnits () public override string Generate () { var builder = new StringBuilder (); - foreach (var part in GetAllUnits ()) { builder.Append (part.Generate ()); builder.Append ("\n\n"); diff --git a/Vernacular.Tool/Vernacular.Generators/PoGenerator.cs b/Vernacular.Tool/Vernacular.Generators/PoGenerator.cs index 856abe7..a5817f1 100644 --- a/Vernacular.Tool/Vernacular.Generators/PoGenerator.cs +++ b/Vernacular.Tool/Vernacular.Generators/PoGenerator.cs @@ -40,6 +40,8 @@ public sealed class PoGenerator : Generator public bool PotMode { get; set; } public bool ExcludeHeaderMetadata { get; set; } + public string InitWithLocale { get; set; } + protected override void Generate () { var document = new Document (); @@ -53,6 +55,9 @@ protected override void Generate () document.Headers.PopulateWithRequiredHeaders (); document.Headers ["X-Generator"] = "Vernacular"; + if (!String.IsNullOrEmpty(InitWithLocale)) { + document.Headers["Plural-Forms"] = PluralRules.GetPluralHeader(InitWithLocale); + } } var sorted_strings = from localized_string in Strings @@ -131,6 +136,11 @@ orderby localized_string.UntranslatedPluralValue ); var translated = localized_string.TranslatedValues; + + if (!string.IsNullOrEmpty(InitWithLocale) && plural != null) { + translated = new string[PluralRules.GetNumberOfPlurals (InitWithLocale)]; + } + if (translated == null) { if (plural == null) { translated = new [] { singular }; @@ -143,7 +153,7 @@ orderby localized_string.UntranslatedPluralValue unit.Add (new Message { Type = translated.Length == 1 ? MessageType.SingularString : MessageType.PluralString, PluralOrder = i, - Value = PotMode ? String.Empty : translated [i] + Value = (PotMode || translated[i] == null) ? String.Empty : translated [i] }); } diff --git a/Vernacular.Tool/Vernacular.Tool/Entry.cs b/Vernacular.Tool/Vernacular.Tool/Entry.cs index 3915313..5483148 100644 --- a/Vernacular.Tool/Vernacular.Tool/Entry.cs +++ b/Vernacular.Tool/Vernacular.Tool/Entry.cs @@ -49,6 +49,7 @@ public static int Main (string[] args) string android_input_strings_xml = null; string android_output_strings_xml = null; string analyer_config_path = null; + string initWithLocale = null; LocalizationMetadata metadata = null; bool generate_pot = false; bool exclude_po_header = false; @@ -80,6 +81,7 @@ public static int Main (string[] args) "for preserving hand-maintained string resources", v => android_input_strings_xml = v }, { "android-output-strings-xml=", "Output file of localized Android Strings.xml " + "for preserving hand-maintained string resources", v => android_output_strings_xml = v }, + { "init-with-locale=", "Init a .po file for the locale", v => initWithLocale = v}, { "pot", v => generate_pot = v != null }, { "exclude-po-header", v => exclude_po_header = v != null }, { "l|log", "Display logging", v => log = v != null }, @@ -128,6 +130,18 @@ public static int Main (string[] args) ((PoGenerator)generator).ExcludeHeaderMetadata = exclude_po_header; } + if (initWithLocale != null && !(generator is PoGenerator)) { + throw new OptionException ("init-with-locale option only valid with po generator", "init-with-locale"); + } + + if (initWithLocale != null && generate_pot) { + throw new OptionException ("you can not use init-with-locale with -pot", "init-with-locale"); + } + + if (initWithLocale != null) { + (generator as PoGenerator).InitWithLocale = initWithLocale; + } + if (reduce_master_path != null && reduce_retain_path == null) { throw new OptionException ("reduce-retain must be specified if reduce-master is", "reduce-retain"); } else if (reduce_master_path == null && reduce_retain_path != null) {