parameters) {
- this.globalParameters = this.globalParameters.merge(new KeyedTrParameters(parameters));
+ this.globalParameters = this.globalParameters.merge(new KeyedTranslationParameters(parameters));
}
@Override
diff --git a/core/src/main/java/org/densy/polyglot/core/formatter/EscapeSequenceFormatter.java b/core/src/main/java/org/densy/polyglot/core/formatter/EscapeSequenceFormatter.java
new file mode 100644
index 0000000..d182c5d
--- /dev/null
+++ b/core/src/main/java/org/densy/polyglot/core/formatter/EscapeSequenceFormatter.java
@@ -0,0 +1,46 @@
+package org.densy.polyglot.core.formatter;
+
+import org.densy.polyglot.api.formatter.context.TranslationFormatContext;
+import org.densy.polyglot.api.formatter.TranslationFormatter;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Post-processor formatter that removes escape sequences.
+ * This should be the LAST formatter in the chain.
+ *
+ * Converts:
+ * - \{param} -> {param}
+ * - \\{param} -> \{param}
+ * - \\\{param} -> \{param}
+ * - \\\\{param} -> \\{param}
+ *
+ * In general: removes one backslash from each pair/sequence of backslashes
+ */
+public class EscapeSequenceFormatter implements TranslationFormatter {
+ private static final Pattern ESCAPE_PATTERN = Pattern.compile("\\\\+");
+
+ @Override
+ public String format(String text, TranslationFormatContext context) {
+ Matcher matcher = ESCAPE_PATTERN.matcher(text);
+ StringBuilder result = new StringBuilder();
+
+ while (matcher.find()) {
+ String backslashes = matcher.group(0);
+ int count = backslashes.length();
+
+ // Remove one slash from each pair; if the number is odd, round down.
+ // \\ -> \
+ // \\\ -> \
+ // \\\\ -> \\
+ int resultCount = count / 2;
+ String replacement = "\\".repeat(resultCount);
+
+ matcher.appendReplacement(result, Matcher.quoteReplacement(replacement));
+ }
+
+ matcher.appendTail(result);
+ return result.toString();
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/java/org/densy/polyglot/core/formatter/NestedTranslationFormatter.java b/core/src/main/java/org/densy/polyglot/core/formatter/NestedTranslationFormatter.java
new file mode 100644
index 0000000..ac372f4
--- /dev/null
+++ b/core/src/main/java/org/densy/polyglot/core/formatter/NestedTranslationFormatter.java
@@ -0,0 +1,82 @@
+package org.densy.polyglot.core.formatter;
+
+import org.densy.polyglot.api.formatter.context.TranslationFormatContext;
+import org.densy.polyglot.api.formatter.TranslationFormatter;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Formatter for nested translations in the format {translation-key}
+ * Allows embedding other translations within a translation string.
+ *
+ * Example: "Welcome message: {welcome.message}" will be replaced with the translation of "welcome.message"
+ *
+ * Supports escaping: \{key} will not be replaced
+ */
+public class NestedTranslationFormatter implements TranslationFormatter {
+
+ public static final Pattern DEFAULT_PATTERN = Pattern.compile("(\\\\*)\\{([^}]+)}");
+
+ private final Pattern pattern;
+ private final int maxDepth;
+
+ public NestedTranslationFormatter() {
+ this(DEFAULT_PATTERN);
+ }
+
+ public NestedTranslationFormatter(Pattern pattern) {
+ this(pattern, 5);
+ }
+
+ public NestedTranslationFormatter(Pattern pattern, int maxDepth) {
+ this.pattern = pattern;
+ this.maxDepth = maxDepth;
+ }
+
+ @Override
+ public String format(String text, TranslationFormatContext context) {
+ return formatRecursive(text, context, 0);
+ }
+
+ private String formatRecursive(String text, TranslationFormatContext context, int depth) {
+ // Protection against infinite recursion
+ if (depth >= maxDepth) {
+ return text;
+ }
+
+ Matcher matcher = pattern.matcher(text);
+ StringBuilder result = new StringBuilder();
+
+ while (matcher.find()) {
+ String fullMatch = matcher.group(0);
+ String backslashes = matcher.group(1);
+ String key = matcher.group(2);
+ int backslashCount = backslashes.length();
+
+ boolean isEscaped = backslashCount % 2 == 1;
+
+ String replacement;
+ if (isEscaped) {
+ // escaped, so we leave it as it is
+ replacement = fullMatch;
+ } else {
+ String nestedTranslation = context.getTranslation().translate(context.getLanguage(), key, context.getParameters());
+
+ if (nestedTranslation != null && !nestedTranslation.equals(key)) {
+ // recursively process nested translations
+ String processedNested = formatRecursive(nestedTranslation, context, depth + 1);
+ replacement = backslashes + processedNested;
+ } else {
+ // no translation found, so we'll leave it as is.
+ replacement = fullMatch;
+ }
+ }
+
+ matcher.appendReplacement(result, Matcher.quoteReplacement(replacement));
+ }
+
+ matcher.appendTail(result);
+ return result.toString();
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/java/org/densy/polyglot/core/formatter/PatternArrayParameterFormatter.java b/core/src/main/java/org/densy/polyglot/core/formatter/PatternArrayParameterFormatter.java
new file mode 100644
index 0000000..8b435e0
--- /dev/null
+++ b/core/src/main/java/org/densy/polyglot/core/formatter/PatternArrayParameterFormatter.java
@@ -0,0 +1,60 @@
+package org.densy.polyglot.core.formatter;
+
+import org.densy.polyglot.api.formatter.context.TranslationFormatContext;
+import org.densy.polyglot.api.formatter.TranslationFormatter;
+import org.densy.polyglot.core.parameter.ArrayTranslationParameters;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Formatter for simple parameters in the format {0}, {1}, {2}...
+ */
+public class PatternArrayParameterFormatter implements TranslationFormatter {
+ public static final Pattern DEFAULT_PATTERN = Pattern.compile("(\\\\*)\\{(\\d+)}");
+
+ private final Pattern pattern;
+
+ public PatternArrayParameterFormatter() {
+ this(DEFAULT_PATTERN);
+ }
+
+ public PatternArrayParameterFormatter(Pattern pattern) {
+ this.pattern = pattern;
+ }
+
+ @Override
+ public String format(String text, TranslationFormatContext context) {
+ if (!(context.getParameters() instanceof ArrayTranslationParameters simpleParams)) {
+ return text;
+ }
+
+ Object[] params = simpleParams.getParameters();
+ Matcher matcher = pattern.matcher(text);
+
+ StringBuilder result = new StringBuilder();
+ while (matcher.find()) {
+ String fullMatch = matcher.group(0);
+ String backslashes = matcher.group(1);
+ String indexStr = matcher.group(2);
+ int index = Integer.parseInt(indexStr);
+ int backslashCount = backslashes.length();
+
+ boolean isEscaped = backslashCount % 2 == 1;
+
+ String replacement;
+ if (isEscaped || index < 0 || index >= params.length) {
+ // if escaped or parameter not found, so leave it as it is
+ replacement = fullMatch;
+ } else {
+ // replace the parameter
+ replacement = backslashes + params[index];
+ }
+
+ matcher.appendReplacement(result, Matcher.quoteReplacement(replacement));
+ }
+ matcher.appendTail(result);
+
+ return result.toString();
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/java/org/densy/polyglot/core/formatter/PatternKeyedParameterFormatter.java b/core/src/main/java/org/densy/polyglot/core/formatter/PatternKeyedParameterFormatter.java
new file mode 100644
index 0000000..077fdc2
--- /dev/null
+++ b/core/src/main/java/org/densy/polyglot/core/formatter/PatternKeyedParameterFormatter.java
@@ -0,0 +1,68 @@
+package org.densy.polyglot.core.formatter;
+
+import org.densy.polyglot.api.context.TranslationContext;
+import org.densy.polyglot.api.formatter.context.TranslationFormatContext;
+import org.densy.polyglot.api.formatter.TranslationFormatter;
+import org.densy.polyglot.core.parameter.KeyedTranslationParameters;
+
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Formatter for named parameters in the format {key}
+ */
+public class PatternKeyedParameterFormatter implements TranslationFormatter {
+ public static final Pattern DEFAULT_PATTERN = Pattern.compile("(\\\\*)\\{([^}]+)}");
+
+ private final TranslationContext context;
+ private final Pattern pattern;
+
+ public PatternKeyedParameterFormatter(TranslationContext context) {
+ this(context, DEFAULT_PATTERN);
+ }
+
+ public PatternKeyedParameterFormatter(TranslationContext context, Pattern pattern) {
+ this.context = context;
+ this.pattern = pattern;
+ }
+
+ @Override
+ public String format(String text, TranslationFormatContext formatContext) {
+ KeyedTranslationParameters keyedParams;
+ if (formatContext.getParameters() instanceof KeyedTranslationParameters keyedTrParameters) {
+ KeyedTranslationParameters merged = new KeyedTranslationParameters(context.getGlobalParameters());
+ merged = merged.merge(keyedTrParameters);
+ keyedParams = merged;
+ } else {
+ keyedParams = new KeyedTranslationParameters(context.getGlobalParameters());
+ }
+
+ Map params = keyedParams.getParameters();
+ Matcher matcher = pattern.matcher(text);
+ StringBuilder result = new StringBuilder();
+
+ while (matcher.find()) {
+ String fullMatch = matcher.group(0);
+ String backslashes = matcher.group(1);
+ String key = matcher.group(2);
+ int backslashCount = backslashes.length();
+
+ boolean isEscaped = backslashCount % 2 == 1;
+
+ String replacement;
+ if (isEscaped || !params.containsKey(key)) {
+ // if escaped or parameter not found, so leave it as it is
+ replacement = fullMatch;
+ } else {
+ // replace the parameter
+ replacement = backslashes + params.get(key);
+ }
+
+ matcher.appendReplacement(result, Matcher.quoteReplacement(replacement));
+ }
+
+ matcher.appendTail(result);
+ return result.toString();
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/java/org/densy/polyglot/core/formatter/context/TranslationFormatContextImpl.java b/core/src/main/java/org/densy/polyglot/core/formatter/context/TranslationFormatContextImpl.java
new file mode 100644
index 0000000..10357ef
--- /dev/null
+++ b/core/src/main/java/org/densy/polyglot/core/formatter/context/TranslationFormatContextImpl.java
@@ -0,0 +1,18 @@
+package org.densy.polyglot.core.formatter.context;
+
+import lombok.Data;
+import org.densy.polyglot.api.Translation;
+import org.densy.polyglot.api.formatter.context.TranslationFormatContext;
+import org.densy.polyglot.api.language.Language;
+import org.densy.polyglot.api.parameter.TranslationParameters;
+
+/**
+ * Implementation of the translation formatting context.
+ */
+@Data
+public class TranslationFormatContextImpl implements TranslationFormatContext {
+ private final String key;
+ private final Language language;
+ private final Translation translation;
+ private final TranslationParameters parameters;
+}
diff --git a/core/src/main/java/org/densy/polyglot/core/parameter/ArrayTranslationParameters.java b/core/src/main/java/org/densy/polyglot/core/parameter/ArrayTranslationParameters.java
new file mode 100644
index 0000000..77671db
--- /dev/null
+++ b/core/src/main/java/org/densy/polyglot/core/parameter/ArrayTranslationParameters.java
@@ -0,0 +1,17 @@
+package org.densy.polyglot.core.parameter;
+
+import lombok.Getter;
+import org.densy.polyglot.api.parameter.TranslationParameters;
+
+/**
+ * Simple indexed translation parameters.
+ */
+@Getter
+public class ArrayTranslationParameters implements TranslationParameters {
+
+ private final Object[] parameters;
+
+ public ArrayTranslationParameters(Object... parameters) {
+ this.parameters = parameters;
+ }
+}
diff --git a/core/src/main/java/org/densy/polyglot/core/parameter/KeyedTrParameters.java b/core/src/main/java/org/densy/polyglot/core/parameter/KeyedTranslationParameters.java
similarity index 55%
rename from core/src/main/java/org/densy/polyglot/core/parameter/KeyedTrParameters.java
rename to core/src/main/java/org/densy/polyglot/core/parameter/KeyedTranslationParameters.java
index ce10714..cec5ffe 100644
--- a/core/src/main/java/org/densy/polyglot/core/parameter/KeyedTrParameters.java
+++ b/core/src/main/java/org/densy/polyglot/core/parameter/KeyedTranslationParameters.java
@@ -1,8 +1,7 @@
package org.densy.polyglot.core.parameter;
-import org.densy.polyglot.api.parameter.TrParameters;
-import org.densy.polyglot.api.parameter.formatter.TrParameterFormatter;
import lombok.Getter;
+import org.densy.polyglot.api.parameter.TranslationParameters;
import java.util.HashMap;
import java.util.Map;
@@ -11,39 +10,41 @@
* Key-value translation parameters.
*/
@Getter
-public class KeyedTrParameters implements TrParameters {
+public class KeyedTranslationParameters implements TranslationParameters {
private final Map parameters;
- public KeyedTrParameters() {
+ public KeyedTranslationParameters() {
this.parameters = new HashMap<>();
}
- public KeyedTrParameters(Map parameters) {
+ public KeyedTranslationParameters(Map parameters) {
this.parameters = new HashMap<>(parameters != null ? parameters : Map.of());
}
- public KeyedTrParameters put(String key, Object value) {
+ /**
+ * Sets the value of the parameter by key.
+ *
+ * @param key parameter key
+ * @param value parameter value
+ * @return this object instance
+ */
+ public KeyedTranslationParameters put(String key, Object value) {
parameters.put(key, value);
return this;
}
- @Override
- public String applyTo(String text, TrParameterFormatter formatter) {
- return formatter.format(text, this);
- }
-
/**
* Merges the current KeyedTrParameters with another KeyedTrParameters.
*
* @param other Another KeyedTrParameters
* @return Merged KeyedTrParameters
*/
- public KeyedTrParameters merge(KeyedTrParameters other) {
+ public KeyedTranslationParameters merge(KeyedTranslationParameters other) {
if (other == null) return this;
Map merged = new HashMap<>(other.getParameters());
merged.putAll(this.parameters);
- return new KeyedTrParameters(merged);
+ return new KeyedTranslationParameters(merged);
}
}
diff --git a/core/src/main/java/org/densy/polyglot/core/parameter/SimpleTrParameters.java b/core/src/main/java/org/densy/polyglot/core/parameter/SimpleTrParameters.java
deleted file mode 100644
index a2c07be..0000000
--- a/core/src/main/java/org/densy/polyglot/core/parameter/SimpleTrParameters.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package org.densy.polyglot.core.parameter;
-
-import org.densy.polyglot.api.parameter.TrParameters;
-import org.densy.polyglot.api.parameter.formatter.TrParameterFormatter;
-import lombok.Getter;
-
-/**
- * Simple indexed translation parameters.
- */
-@Getter
-public class SimpleTrParameters implements TrParameters {
-
- private final Object[] parameters;
-
- public SimpleTrParameters(Object... parameters) {
- this.parameters = parameters;
- }
-
- @Override
- public String applyTo(String text, TrParameterFormatter formatter) {
- return formatter.format(text, this);
- }
-}
diff --git a/core/src/main/java/org/densy/polyglot/core/parameter/TrParameters.java b/core/src/main/java/org/densy/polyglot/core/parameter/TrParameters.java
new file mode 100644
index 0000000..a7858b9
--- /dev/null
+++ b/core/src/main/java/org/densy/polyglot/core/parameter/TrParameters.java
@@ -0,0 +1,33 @@
+package org.densy.polyglot.core.parameter;
+
+import lombok.experimental.UtilityClass;
+
+import java.util.Map;
+
+/**
+ * A utility class containing basic parameters for translations.
+ */
+@UtilityClass
+public class TrParameters {
+
+ /**
+ * Creates an array parameter.
+ */
+ public ArrayTranslationParameters array(Object... parameters) {
+ return new ArrayTranslationParameters(parameters);
+ }
+
+ /**
+ * Creates a keyed parameters from map.
+ */
+ public KeyedTranslationParameters keyed(Map parameters) {
+ return new KeyedTranslationParameters(parameters);
+ }
+
+ /**
+ * Creates an empty keyed parameters.
+ */
+ public KeyedTranslationParameters keyed() {
+ return new KeyedTranslationParameters();
+ }
+}
diff --git a/core/src/main/java/org/densy/polyglot/core/parameter/formatter/BraceKeyedParameterFormatter.java b/core/src/main/java/org/densy/polyglot/core/parameter/formatter/BraceKeyedParameterFormatter.java
deleted file mode 100644
index d5a5469..0000000
--- a/core/src/main/java/org/densy/polyglot/core/parameter/formatter/BraceKeyedParameterFormatter.java
+++ /dev/null
@@ -1,45 +0,0 @@
-package org.densy.polyglot.core.parameter.formatter;
-
-import org.densy.polyglot.api.parameter.TrParameters;
-import org.densy.polyglot.api.parameter.formatter.TrParameterFormatter;
-import org.densy.polyglot.core.parameter.KeyedTrParameters;
-
-import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Formatter for named parameters in the format {key}
- */
-public class BraceKeyedParameterFormatter implements TrParameterFormatter {
- private static final Pattern PATTERN = Pattern.compile("\\{([^}]+)}");
-
- @Override
- public String format(String text, TrParameters parameters) {
- if (!(parameters instanceof KeyedTrParameters keyedParams)) {
- return text;
- }
-
- Map params = keyedParams.getParameters();
- Matcher matcher = PATTERN.matcher(text);
-
- StringBuilder result = new StringBuilder();
- while (matcher.find()) {
- String key = matcher.group(1);
- String replacement = "";
- if (params.containsKey(key)) {
- replacement = String.valueOf(params.get(key));
- }
- matcher.appendReplacement(result, Matcher.quoteReplacement(replacement));
- }
- matcher.appendTail(result);
-
- return result.toString();
- }
-
- @Override
- public Class extends TrParameters> getSupportedParameterType() {
- return KeyedTrParameters.class;
- }
-}
-
diff --git a/core/src/main/java/org/densy/polyglot/core/parameter/formatter/BracketSimpleParameterFormatter.java b/core/src/main/java/org/densy/polyglot/core/parameter/formatter/BracketSimpleParameterFormatter.java
deleted file mode 100644
index be61ec3..0000000
--- a/core/src/main/java/org/densy/polyglot/core/parameter/formatter/BracketSimpleParameterFormatter.java
+++ /dev/null
@@ -1,44 +0,0 @@
-package org.densy.polyglot.core.parameter.formatter;
-
-import org.densy.polyglot.api.parameter.TrParameters;
-import org.densy.polyglot.api.parameter.formatter.TrParameterFormatter;
-import org.densy.polyglot.core.parameter.SimpleTrParameters;
-
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Formatter for simple parameters in the format [0], [1], [2]...
- */
-public class BracketSimpleParameterFormatter implements TrParameterFormatter {
- private static final Pattern PATTERN = Pattern.compile("\\[(\\d+)]");
-
- @Override
- public String format(String text, TrParameters parameters) {
- if (!(parameters instanceof SimpleTrParameters simpleParams)) {
- return text;
- }
-
- Object[] params = simpleParams.getParameters();
- Matcher matcher = PATTERN.matcher(text);
-
- StringBuilder result = new StringBuilder();
- while (matcher.find()) {
- int index = Integer.parseInt(matcher.group(1));
- String replacement = "";
- if (index >= 0 && index < params.length) {
- replacement = String.valueOf(params[index]);
- }
- matcher.appendReplacement(result, Matcher.quoteReplacement(replacement));
- }
- matcher.appendTail(result);
-
- return result.toString();
- }
-
- @Override
- public Class extends TrParameters> getSupportedParameterType() {
- return SimpleTrParameters.class;
- }
-}
-