void ParseFile(string projectDirectory, string filePath, ConcurrentDictionary <string, TemplateItem> templateItems) { var referencePath = PathNormalizer.MakeRelativePath(projectDirectory, filePath); DebugHelpers.WriteLine("FileNuggetFinder.ParseFile -- {0}", filePath); // Lookup any/all nuggets in the file and for each add a new template item. using var fs = I18NUtility.Retry(() => File.Open(filePath, FileMode.Open, FileAccess.Read, FileShare.Read), 3); using var streamReader = new StreamReader(fs); _nuggetParser.ParseString(streamReader.ReadToEnd(), delegate(string nuggetString, int pos, Nugget nugget, string iEntity) { var referenceContext = _localizationOptions.DisableReferences ? ReferenceContext.Create("Disabled references", iEntity, 0) : ReferenceContext.Create(referencePath, iEntity, pos); var fileName = Path.GetFileNameWithoutExtension(filePath); // If we have a file like "myfile.aspx.vb" then the fileName will be "myfile.aspx" resulting in split // .pot files. So remove all extensions, so that we just have the actual name to deal with. fileName = fileName.IndexOf('.') > -1 ? fileName.Split('.')[0] : fileName; AddNewTemplateItem( fileName, referenceContext, nugget, templateItems); // Done. return(null); // null means we are not modifying the entity. }); }
/// <summary> /// Parses a PO file into a Language object /// </summary> /// <param name="langTag">The language (tag) you wish to load into Translation object</param> /// <param name="fileNames"></param> /// <param name="loadingCache"></param> /// <returns>A complete translation object with all all translations and language values set.</returns> Translation ParseTranslationFile(string langTag, List <string> fileNames, bool loadingCache) { //todo: consider that lines we don't understand like headers from poedit and #| should be preserved and outputted again. var translation = new Translation(); var language = new Language { LanguageShortTag = langTag }; translation.LanguageInformation = language; var items = new ConcurrentDictionary <string, TranslationItem>(); var paths = new List <string>(); if (!_localizationOptions.GenerateTemplatePerFile || loadingCache) { paths.Add(GetPathForLanguage(langTag)); } paths.AddRange(_localizationOptions.LocaleOtherFiles.Where(file => file.IsSet()).Select(file => GetPathForLanguage(langTag, file))); if (_localizationOptions.GenerateTemplatePerFile && !loadingCache) { if (fileNames != null && fileNames.Count > 0) { paths.AddRange(fileNames.Select(fileName => GetPathForLanguage(langTag, fileName))); } } foreach (var path in paths.Where(File.Exists)) { DebugHelpers.WriteLine("Reading file: {0}", path); using var fs = I18NUtility.Retry(() => File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read), 3); using var streamReader = new StreamReader(fs); // http://www.gnu.org/s/hello/manual/gettext/PO-Files.html string line; var itemStarted = false; while ((line = streamReader.ReadLine()) != null) { var extractedComments = new HashSet <string>(); var translatorComments = new HashSet <string>(); var flags = new HashSet <string>(); var references = new List <ReferenceContext>(); //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(ReferenceContext.Parse(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 = streamReader.ReadLine()) != null && line.StartsWith("#")); } if (line != null && (itemStarted || line.StartsWith("#~"))) { var item = ParseBody(streamReader, line); if (item != null) { // item.TranslatorComments = translatorComments; item.ExtractedComments = extractedComments; item.Flags = flags; item.References = references; // items.AddOrUpdate( item.MsgKey, // Add routine. k => item, // Update routine. (k, v) => { v.References = v.References.Append(item.References); var referencesAsComments = item.References.Select(r => r.ToComment()).ToList(); v.ExtractedComments = v.ExtractedComments.Append(referencesAsComments); v.TranslatorComments = v.TranslatorComments.Append(referencesAsComments); v.Flags = v.Flags.Append(referencesAsComments); 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 = Path.Combine(GetAbsoluteLocaleDir(), _localizationOptions.LocaleFilename + ".pot"); var potDate = DateTime.Now; if (File.Exists(templateFilePath)) { potDate = File.GetLastWriteTime(templateFilePath); } var filePath = GetPathForLanguage(translation.LanguageInformation.LanguageShortTag); using var fs = I18NUtility.Retry(() => File.Open(filePath, FileMode.Create, FileAccess.Write, FileShare.Write), 3); using var stream = new StreamWriter(fs); DebugHelpers.WriteLine("Writing file: {0}", filePath); // Establish ordering of items in PO file. var orderedItems = translation.Items.Values .OrderBy(x => x.References == null || !x.References.Any()) // 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: pot ({_assemblyVersion})\\n\""); stream.WriteLine(); foreach (var item in orderedItems) { var hasReferences = false; if (item.TranslatorComments != null) { foreach (var translatorComment in item.TranslatorComments.Distinct()) { stream.WriteLine("# " + translatorComment); } } if (item.ExtractedComments != null) { foreach (var extractedComment in item.ExtractedComments.Distinct()) { stream.WriteLine("#. " + extractedComment); } } if (item.References != null) { foreach (var reference in item.References.Distinct()) { hasReferences = true; stream.WriteLine("#: " + reference.ToComment()); } } if (item.Flags != null) { foreach (var flag in item.Flags.Distinct()) { stream.WriteLine("#, " + flag); } } if (_localizationOptions.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(""); } }