=== modified file 'src/com/goldencode/p2j/util/TranslationManager.java'
--- old/src/com/goldencode/p2j/util/TranslationManager.java	2023-05-12 10:05:12 +0000
+++ new/src/com/goldencode/p2j/util/TranslationManager.java	2023-09-15 09:42:33 +0000
@@ -94,6 +94,21 @@
    /** Logger **/
    private static CentralLogger LOG = CentralLogger.get(TranslationManager.class.getSimpleName());
 
+   private static ResourceBundle NULL_BUNDLE = new ResourceBundle()
+   {
+      @Override
+      protected Object handleGetObject(String key)
+      {
+         return null;
+      }
+
+      @Override
+      public Enumeration<String> getKeys()
+      {
+         return null;
+      }
+   };
+
    /** Context local data */
    private static final ContextLocal<TranslationManager> instance = new ContextLocal<TranslationManager>()
    {
@@ -134,6 +149,8 @@
    /** String expansion ratio */
    private double expansionRatio = 0;
 
+   private HashMap<String, ResourceBundle> fwdRuntimeBundles = new HashMap<>();
+
    /**
     * Returns the context local instance.
     *
@@ -259,10 +276,15 @@
    {
       return languages.stream().<Entry<String, String>>map(
                (lang) -> new SimpleImmutableEntry<String, String>(lang,
-                                                                  bundleBaseName + "_" + getIdentifier(lang)))
+                                                                  getBundleName(bundleBaseName, lang)))
                                                                   .iterator();
    }
 
+   public static String getBundleName(String bundleBaseName, String lang)
+   {
+      return bundleBaseName + "_" + getIdentifier(lang);
+   }
+
    /**
     * Default constructor.
     */
@@ -275,6 +297,43 @@
       expansionRatio = percent / 100.0;
    }
 
+   public String tr(String msgid, Object... args)
+   {
+      for (String lang : currentLanguages)
+      {
+         ResourceBundle b = fwdRuntimeBundles.get(lang);
+         if (b == null)
+         {
+            String bName = getBundleName("com.goldencode.p2j.util.tr.Translations", lang);
+            try
+            {
+               b = ResourceBundle.getBundle(bName, bundleControl);
+            }
+            catch (Exception ex)
+            {
+               b = NULL_BUNDLE;
+            }
+
+            fwdRuntimeBundles.put(lang, b);
+         }
+
+         if (b != NULL_BUNDLE)
+         {
+            try
+            {
+               msgid = b.getString(msgid);
+               return String.format(msgid, args);
+            }
+            catch (MissingResourceException ex)
+            {
+               return msgid;
+            }
+         }
+      }
+
+      return String.format(msgid, args);
+   }
+
    /**
     * Initializes supplied class (aka referent class) for the purpose of i18n. The method returns a unique
     * identifier associated with the supplied referent class. Any further i18n requests must contain this
@@ -439,7 +498,6 @@
    {
       currentLanguages = parseLanguages(languages, false);
    }
-
    /**
     * Resolves a resource bundle based on the current languages set with {@link #setCurrentLanguages(String)}
     * and associates it with the supplied referent. Any further translation requests for the referent will be

=== added file 'test/com/goldencode/p2j/util/TranslationManagerTest.java'
--- old/test/com/goldencode/p2j/util/TranslationManagerTest.java	1970-01-01 00:00:00 +0000
+++ new/test/com/goldencode/p2j/util/TranslationManagerTest.java	2023-09-15 10:58:23 +0000
@@ -0,0 +1,55 @@
+package com.goldencode.p2j.util;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+public class TranslationManagerTest
+{
+   private TranslationManager instance;
+
+   // instance can be used directly, but with files with many translations it may be more manageable
+   // to declare a tr wrapper method.
+   private String tr(String msgid, Object... args)
+   {
+      return instance.tr(msgid, args);
+   }
+
+   @BeforeEach
+   public void setUp() {
+      instance = new TranslationManager();
+      // You may want to set up some initial values or mocks here
+   }
+
+   @Test
+   public void testTranslationWithExistingLanguage() {
+      instance.setCurrentLanguages("cs");
+      String result = tr("Hello!");
+      assertEquals("Ahoj!", result);
+   }
+
+   @Test
+   public void testTranslationWithMissingLanguage() {
+      // Assuming there's no translation for "missing" language
+      instance.setCurrentLanguages("missing");
+      String msgid = "Hello!";
+      String result = tr(msgid);
+      assertEquals("Hello!", result);
+   }
+
+   @Test
+   public void testTranslationWithArguments() {
+      instance.setCurrentLanguages("cs");
+      String result = tr("Greetings, %s!", "John");
+      assertEquals("Ahoj, John!", result);
+   }
+
+   @Test
+   public void testMissingMessageID() {
+      instance.setCurrentLanguages("cs");
+      String missingMsgid = "missing";
+      String result = tr(missingMsgid);
+      assertEquals(missingMsgid, result);
+   }
+}

=== added directory 'test/com/goldencode/p2j/util/tr'
=== added file 'test/com/goldencode/p2j/util/tr/Translations_cs.java'
--- old/test/com/goldencode/p2j/util/tr/Translations_cs.java	1970-01-01 00:00:00 +0000
+++ new/test/com/goldencode/p2j/util/tr/Translations_cs.java	2023-09-15 10:37:04 +0000
@@ -0,0 +1,42 @@
+package com.goldencode.p2j.util.tr;
+
+/* Automatically generated by GNU msgfmt.  Do not modify!  */
+public class Translations_cs extends java.util.ResourceBundle {
+  private static final java.lang.String[] table;
+  static {
+    java.lang.String[] t = new java.lang.String[8];
+    t[0] = "";
+    t[1] = "Content-Type: text/plain; charset=UTF-8\nLanguage: cs\nPlural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n";
+    t[2] = "Greetings, %s!";
+    t[3] = "Ahoj, %s!";
+    t[6] = "Hello!";
+    t[7] = "Ahoj!";
+    table = t;
+  }
+  public java.lang.Object handleGetObject (java.lang.String msgid) throws java.util.MissingResourceException {
+    int hash_val = msgid.hashCode() & 0x7fffffff;
+    int idx = (hash_val % 4) << 1;
+    java.lang.Object found = table[idx];
+    if (found != null && msgid.equals(found))
+      return table[idx + 1];
+    return null;
+  }
+  public java.util.Enumeration getKeys () {
+    return
+      new java.util.Enumeration() {
+        private int idx = 0;
+        { while (idx < 8 && table[idx] == null) idx += 2; }
+        public boolean hasMoreElements () {
+          return (idx < 8);
+        }
+        public java.lang.Object nextElement () {
+          java.lang.Object key = table[idx];
+          do idx += 2; while (idx < 8 && table[idx] == null);
+          return key;
+        }
+      };
+  }
+  public java.util.ResourceBundle getParent () {
+    return parent;
+  }
+}

=== added file 'test/com/goldencode/p2j/util/tr/Translations_cs.po'
--- old/test/com/goldencode/p2j/util/tr/Translations_cs.po	1970-01-01 00:00:00 +0000
+++ new/test/com/goldencode/p2j/util/tr/Translations_cs.po	2023-09-15 10:35:31 +0000
@@ -0,0 +1,11 @@
+msgid ""
+msgstr ""
+"Content-Type: text/plain; charset=UTF-8\n"
+"Language: cs\n"
+"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
+
+msgid "Hello!"
+msgstr "Ahoj!"
+
+msgid "Greetings, %s!"
+msgstr "Ahoj, %s!"
\ No newline at end of file

