using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.RegularExpressions; using Wordbee.Beebox.Extensibility; namespace Wordbee.Beebox.Extensions.TranslatedFilesAmender { /// /// An extension that picks translated files and replaces strings by regex. /// Placeholders are supported for source/target locale and filename. /// Use case: /// - Replace any locales present in translated file with the target locale (since filter may not change those, e.g. xml, json) /// public class TranslatedFilesAmenderAction : BeeboxTranslatedFileAction { /// /// Gets the globally unique id of this extension. /// public override string ExtensionID { get { return "wordbee-targetfile-amender"; } } /// /// Gets if the extension is by default enabled. If false, then it must be enabled explicitly in each /// Beebox project from the Beebox Admin UI. /// public override bool EnableByDefault { get { return false; } } /// /// Gets a name. Displayed in the Beebox extension manager page. /// public override string Name { get { return "Replace content in translated files"; } } /// /// Gets a description. Displayed in the Beebox extension manager page. /// public override string Description { get { return "Applies regexes on newly created translated files. The regex permits to replace content, see help on parameters."; } } /// /// Gets version number. Displayed in the Beebox extension manager page. /// public override string Version { get { return "1.1"; } } /// /// Gets the author. Displayed in the Beebox extension manager page. /// public override string Author { get { return "Wordbee"; } } /// /// Gets optional help description (not html!) of the configuration parameter. /// By returning a non-null value, you explicitly tell the Beebox that this extension has a configurable parameter. /// public override string ParameterHelp { get { return "Specify a json array of objects, one per regular expression." + "- The object has property 'regex': Must contain a matching group ( ... )\n " + "- The object has property 'replace': A string replacing the match.\n " + "- Use capturing groups and $n references in replacement to replace selected parts of the matched content.\n " + "- The object has optional property 'condition': Additional regex that must match the extracted content. If set then $n references in replace string are not supported.\n " + "The replacement string may contain these placeholders:\n " + "{name} : The file name without extension.\n " + "{tlocale} : The target language code.\n " + "{slocale} : The source language code.\n "; } } /// /// Gets the default value of the parameter. Default can be changed by user. /// public override string ParameterDefault { get { // en ====> fr var json = new JArray( new JObject( new JProperty("regex", @"(Locale\>)\w+?(\<\/Locale)"), new JProperty("replace", "$1{tlocale}$2") ) ); return json.ToString(Formatting.None); } } /// /// Validates the user parameters. /// Return null if ok, otherwise return an error text. /// /// The parameter edited by a user. /// Null: parameter is ok. Otherwise return an error message. public override string ValidateParameter(string parameter) { ParseParameters(parameter, out string error); return error; } /// /// Called whenever a translated file was created in the OUT directory. Its location is: /// {outdirectory}\{locale}\{relativepath} /// When your code shall copy the file to another location, please keep in mind that the relativepath can include directories. /// Example: "chapter\myfile1.doc", "data.xml", etc. /// /// The project key /// The full path of the created translated file. /// The "OUT" directory path. /// The target language code /// The relative path of the file with respect to {out directory}\{locale}(\){projectfolder} /// The source language code /// The extension's parameters. You need to implement the respective virtual methods to permit configuring the parameter individually per project. public override void Process( string projectkey, string path, string outdirectory, string targetlocale, string filename, string sourcelocale, IExtensionConfiguration parameters) { // Get parameters var patterns = ParseParameters(parameters.Parameter ?? ParameterDefault, out _); if (patterns == null) return; try { string content = File.ReadAllText(path); content = ApplyPatterns(content, patterns, sourcelocale, targetlocale, filename); File.WriteAllText(path, content); } catch (Exception e) { throw new InvalidOperationException(string.Format("Could not regex-replace content in '{0}': {1}", filename, e.Message)); } } /// /// Apply the patterns to the content /// /// /// /// /// /// /// public string ApplyPatterns(string content, List> patterns, string sourcelocale, string targetlocale, string filename) { if (!patterns.Any()) return content; foreach (var pattern in patterns) { var replace = pattern.Item2; if (replace.Contains('{')) { replace = replace .Replace("{slocale}", sourcelocale) .Replace("{tlocale}", targetlocale) .Replace("{name}", filename); } // Insert placeholders into replacement pattern if (pattern.Item3 == null) { // Apply replacement string which allows use of $1, $2 etc. content = pattern.Item1.Replace(content, replace); } else { // Apply replacements and check condition. Replacement string does not permit $1, $2 etc. content = pattern.Item1.Replace(content, new MatchEvaluator((m) => { if (pattern.Item3.IsMatch(m.Value)) return replace; return m.Value; })); } } return content; } /// /// Parse parameters /// /// /// /// public List> ParseParameters(string parameters, out string error) { error = null; if (string.IsNullOrWhiteSpace(parameters)) return default; try { var items = new List>(); foreach (var jparam in JArray.Parse(parameters).Cast()) { string regexstr = jparam.Value("regex"); if (string.IsNullOrWhiteSpace(regexstr)) throw new InvalidOperationException("No regex"); // Capturing regex Regex regex; try { regex = new Regex(regexstr); } catch { error = "The regex is invalid: " + regexstr; return default; } // Replacement pattern string replace = jparam.Value("replace"); // Optional condition regex Regex regexCondition = null; string condition = jparam.Value("condition"); if (!string.IsNullOrWhiteSpace(condition)) { try { regexCondition = new Regex(condition); } catch { error = "The regex is invalid: " + condition; return default; } } items.Add(Tuple.Create(regex, replace, regexCondition)); } return items.Count == 0 ? default : items; } catch { error = "Not a valid json or not structured as per expectation."; return default; } } } }