public void MergeTranslation(IDictionary<string, TemplateItem> src, Translation dst) { // Our purpose here is to merge newly parsed message items (src) with those already stored in a translation repo (dst). // 1. Where an orphan msgid is found (present in the dst but not the src) we update it in the dst to remove all references. // 2. Where a src msgid is missing from dst, we simply ADD it to dst. // 3. Where a src msgid is present in dst, we update the item in the dst to match the src (references, comments, etc.). // // 1. // Simply remove all references from dst items, for now. foreach (TranslationItem dstItem in dst.Items.Values) { dstItem.References = null; } // 2. and 3. foreach (TemplateItem srcItem in src.Values) { TranslationItem dstItem = dst.Items.GetOrAdd(srcItem.MsgKey, k => new TranslationItem { MsgKey = srcItem.MsgKey }); dstItem.MsgId = srcItem.MsgId; dstItem.References = srcItem.References; dstItem.ExtractedComments = srcItem.Comments; } // Persist changes. _repository.SaveTranslation(dst); }
/// <summary> /// Parses a PO file into a Language object /// </summary> /// <param name="langtag">The language (tag) you wish to load into Translation object</param> /// <returns>A complete translation object with all all translations and language values set.</returns> private Translation ParseTranslationFile(string langtag) { //todo: consider that lines we don't understand like headers from poedit and #| should be preserved and outputted again. Translation translation = new Translation(); Language language = new Language(); language.LanguageShortTag = langtag; translation.LanguageInformation = language; var items = new ConcurrentDictionary<string, TranslationItem>(); string path = GetPathForLanguage(langtag); if (File.Exists(path)) { Console.WriteLine("Reading file: {0}", path); using (var fs = File.OpenText(path)) { // http://www.gnu.org/s/hello/manual/gettext/PO-Files.html string line; bool itemStarted = false; while ((line = fs.ReadLine()) != null) { List<string> extractedComments = new List<string>(); List<string> translatorComments = new List<string>(); List<string> flags = new List<string>(); List<string> references = new List<string>(); //read all comments, flags and other descriptive items for this string //if we have #~ its a historical/log entry but it is the messageID/message so we skip this do/while if (line.StartsWith("#") && !line.StartsWith("#~")) { do { itemStarted = true; switch (line[1]) { case '.': //Extracted comments extractedComments.Add(line.Substring(2).Trim()); break; case ':': //references references.Add(line.Substring(2).Trim()); break; case ',': //flags flags.Add(line.Substring(2).Trim()); break; case '|': //msgid previous-untranslated-string - NOT used by us break; default: //translator comments translatorComments.Add(line.Substring(1).Trim()); break; } } while ((line = fs.ReadLine()) != null && line.StartsWith("#")); } if (line != null && (itemStarted || line.StartsWith("#~"))) { TranslationItem item = ParseBody(fs, line, extractedComments); if (item != null) { // item.TranslatorComments = translatorComments; item.ExtractedComments = extractedComments; item.Flags = flags; item.References = references; // items.AddOrUpdate( item.MsgKey, // Add routine. k => { return item; }, // Update routine. (k, v) => { v.References = v.References.Append(item.References); v.ExtractedComments = v.ExtractedComments.Append(item.References); v.TranslatorComments = v.TranslatorComments.Append(item.References); v.Flags = v.Flags.Append(item.References); return v; }); } } itemStarted = false; } } } translation.Items = items; return translation; }
/// <summary> /// Saves a translation into file with standard pattern locale/langtag/message.po /// Also saves a backup of previous version /// </summary> /// <param name="translation">The translation you wish to save. Must have Language shortag filled out.</param> public void SaveTranslation(Translation translation) { var templateFilePath = GetAbsoluteLocaleDir() + "/messages.pot"; var POTDate = DateTime.Now; if (File.Exists(templateFilePath)) { POTDate = File.GetLastWriteTime(templateFilePath); } string filePath = GetPathForLanguage(translation.LanguageInformation.LanguageShortTag); string backupPath = GetPathForLanguage(translation.LanguageInformation.LanguageShortTag) + ".backup"; if (File.Exists(filePath)) //we backup one version. more advanced backup solutions could be added here. { if (File.Exists(backupPath)) { File.Delete(backupPath); } System.IO.File.Move(filePath, backupPath); } if (File.Exists(filePath)) //we make sure the old file is removed first { File.Delete(filePath); } bool hasReferences = false; if (!File.Exists(filePath)) { var fileInfo = new FileInfo(filePath); var dirInfo = new DirectoryInfo(Path.GetDirectoryName(filePath)); if (!dirInfo.Exists) { dirInfo.Create(); } fileInfo.Create().Close(); } using (StreamWriter stream = new StreamWriter(filePath)) { Console.WriteLine("Writing file: {0}", filePath); // Establish ordering of items in PO file. var orderedItems = translation.Items.Values .OrderBy(x => x.References == null || x.References.Count() == 0) // Non-orphan items before orphan items. .ThenBy(x => x.MsgKey); // Then order alphanumerically. // //This is required for poedit to read the files correctly if they contains for instance swedish characters stream.WriteLine("msgid \"\""); stream.WriteLine("msgstr \"\""); stream.WriteLine("\"Project-Id-Version: \\n\""); stream.WriteLine("\"POT-Creation-Date: " + POTDate.ToString("yyyy-MM-dd HH:mmzzz") + "\\n\""); stream.WriteLine("\"PO-Revision-Date: " + DateTime.Now.ToString("yyyy-MM-dd HH:mmzzz") + "\\n\""); stream.WriteLine("\"MIME-Version: 1.0\\n\""); stream.WriteLine("\"Content-Type: text/plain; charset=utf-8\\n\""); stream.WriteLine("\"Content-Transfer-Encoding: 8bit\\n\""); stream.WriteLine("\"X-Generator: i18n.POTGenerator\\n\""); stream.WriteLine(); foreach (var item in orderedItems) { hasReferences = false; if (item.TranslatorComments != null) { foreach (var translatorComment in item.TranslatorComments) { stream.WriteLine("# " + translatorComment); } } if (item.ExtractedComments != null) { foreach (var extractedComment in item.ExtractedComments) { stream.WriteLine("#. " + extractedComment); } } if (item.References != null) { foreach (var reference in item.References) { hasReferences = true; stream.WriteLine("#: " + reference); } } if (item.Flags != null) { foreach (var flag in item.Flags) { stream.WriteLine("#, " + flag); } } string prefix = hasReferences ? "" : prefix = "#~ "; if (_settings.MessageContextEnabledFromComment && item.ExtractedComments != null && item.ExtractedComments.Count() != 0) { WriteString(stream, hasReferences, "msgctxt", item.ExtractedComments.First()); } WriteString(stream, hasReferences, "msgid", escape(item.MsgId)); WriteString(stream, hasReferences, "msgstr", escape(item.Message)); stream.WriteLine(""); } } }