1+ package fr .sandro642 .github .misc ;
2+
3+ import fr .sandro642 .github .ConnectLib ;
4+
5+ import java .io .BufferedReader ;
6+ import java .io .IOException ;
7+ import java .io .InputStream ;
8+ import java .io .InputStreamReader ;
9+ import java .nio .charset .StandardCharsets ;
10+ import java .util .HashMap ;
11+ import java .util .Map ;
12+ import java .util .regex .Matcher ;
13+ import java .util .regex .Pattern ;
14+
15+ /**
16+ * LangManager handles loading and retrieving language messages
17+ * from .lang files with placeholder support.
18+ *
19+ * @author Sandro642
20+ * @version 1.0
21+ */
22+ public class LangManager {
23+
24+ private final Map <String , Map <String , String >> messages ;
25+ private static final Pattern CATEGORY_PATTERN = Pattern .compile ("\\ [(.+)]" );
26+ private static final Pattern MESSAGE_PATTERN = Pattern .compile ("(.+?):\\ s*(.+)" );
27+ private boolean loadedSuccessfully ;
28+ private String loadError ;
29+
30+ /**
31+ * LangManager constructor.
32+ * Automatically loads the language file configured in ConnectLib.
33+ */
34+ public LangManager () {
35+ this .messages = new HashMap <>();
36+ this .loadedSuccessfully = false ;
37+ this .loadError = null ;
38+
39+ try {
40+ String langFilePath = ConnectLib .LangSupport ().getPathFile ();
41+
42+ if (langFilePath == null ) {
43+ this .loadError = "LangType has not been defined. Call LangSupport().setLangTypeVariable() first." ;
44+ System .err .println ("Error loading language file: " + loadError );
45+ return ;
46+ }
47+
48+ loadLangFile (langFilePath );
49+ this .loadedSuccessfully = true ;
50+ } catch (IllegalArgumentException e ) {
51+ this .loadError = e .getMessage ();
52+ System .err .println ("Error loading language file: " + e .getMessage ());
53+ } catch (Exception e ) {
54+ this .loadError = e .getMessage ();
55+ System .err .println ("Error loading language file: " + e .getMessage ());
56+ e .printStackTrace ();
57+ }
58+ }
59+
60+ /**
61+ * Loads the language file and parses its content.
62+ *
63+ * @param langFilePath path to the language file retrieved from ConnectLib
64+ */
65+ private void loadLangFile (String langFilePath ) {
66+ try {
67+ // Remove leading / if present for getResourceAsStream
68+ String resourcePath = langFilePath .startsWith ("/" ) ? langFilePath .substring (1 ) : langFilePath ;
69+
70+ InputStream inputStream = getClass ().getClassLoader ().getResourceAsStream (resourcePath );
71+
72+ if (inputStream == null ) {
73+ throw new RuntimeException ("Language file not found: " + langFilePath + " (searched: " + resourcePath + ")" );
74+ }
75+
76+ try (BufferedReader reader = new BufferedReader (new InputStreamReader (inputStream , StandardCharsets .UTF_8 ))) {
77+ String currentCategory = null ;
78+ String line ;
79+
80+ while ((line = reader .readLine ()) != null ) {
81+ line = line .trim ();
82+
83+ // Ignore empty lines and comments
84+ if (line .isEmpty () || line .startsWith ("#" )) {
85+ continue ;
86+ }
87+
88+ // Detect a category
89+ Matcher categoryMatcher = CATEGORY_PATTERN .matcher (line );
90+ if (categoryMatcher .matches ()) {
91+ currentCategory = categoryMatcher .group (1 );
92+ messages .putIfAbsent (currentCategory , new HashMap <>());
93+ continue ;
94+ }
95+
96+ // Detect a message
97+ Matcher messageMatcher = MESSAGE_PATTERN .matcher (line );
98+ if (messageMatcher .matches () && currentCategory != null ) {
99+ String key = messageMatcher .group (1 ).trim ();
100+ String value = messageMatcher .group (2 ).trim ();
101+ messages .get (currentCategory ).put (key , value );
102+ }
103+ }
104+ }
105+ } catch (IOException e ) {
106+ throw new RuntimeException ("Error reading language file" , e );
107+ }
108+ }
109+
110+ /**
111+ * Retrieves a translated message with argument replacement (varargs version).
112+ * Arguments are passed as pairs: placeholder_name, value
113+ *
114+ * @param category The message category (e.g., "connectlib.class")
115+ * @param messagePath The message path (e.g., "initialise.catcherror")
116+ * @param arguments Arguments to replace in the message (e.g., "exception", "NullPointerException")
117+ * @return The formatted message with replaced arguments
118+ *
119+ * @example
120+ * <pre>
121+ * getMessage("connectlib.class", "initialise.catcherror", "exception", "NullPointerException")
122+ * </pre>
123+ */
124+ public String getMessage (String category , String messagePath , String ... arguments ) {
125+ if (arguments == null || arguments .length == 0 ) {
126+ return getMessage (category , messagePath , (Map <String , String >) null );
127+ }
128+
129+ // Convert varargs to Map
130+ Map <String , String > argsMap = new HashMap <>();
131+ for (int i = 0 ; i < arguments .length - 1 ; i += 2 ) {
132+ argsMap .put (arguments [i ], arguments [i + 1 ]);
133+ }
134+
135+ return getMessage (category , messagePath , argsMap );
136+ }
137+
138+ /**
139+ * Retrieves a translated message with argument replacement (Map version).
140+ *
141+ * @param category The message category (e.g., "connectlib.class")
142+ * @param messagePath The message path (e.g., "initialise.catcherror")
143+ * @param arguments Map containing key-value pairs to replace placeholders
144+ * @return The formatted message with replaced arguments
145+ *
146+ * @example
147+ * <pre>
148+ * Map<String, String> args = new HashMap<>();
149+ * args.put("exception", "NullPointerException");
150+ * getMessage("connectlib.class", "initialise.catcherror", args)
151+ *
152+ * // Or with Map.of() (Java 9+)
153+ * getMessage("connectlib.class", "initialise.catcherror", Map.of("exception", "NullPointerException"))
154+ * </pre>
155+ */
156+ public String getMessage (String category , String messagePath , Map <String , String > arguments ) {
157+ if (!loadedSuccessfully ) {
158+ return "Error: language file not loaded (" + (loadError != null ? loadError : "unknown reason" ) + ")" ;
159+ }
160+
161+ Map <String , String > categoryMessages = messages .get (category );
162+
163+ if (categoryMessages == null ) {
164+ return "Message not found: category [" + category + "] does not exist" ;
165+ }
166+
167+ String message = categoryMessages .get (messagePath );
168+
169+ if (message == null ) {
170+ return "Message not found: [" + category + "] " + messagePath ;
171+ }
172+
173+ // Replace arguments
174+ if (arguments != null && !arguments .isEmpty ()) {
175+ for (Map .Entry <String , String > entry : arguments .entrySet ()) {
176+ String placeholder = "%" + entry .getKey () + "%" ;
177+ String value = entry .getValue ();
178+ message = message .replace (placeholder , value );
179+ }
180+ }
181+
182+ return message ;
183+ }
184+
185+ /**
186+ * Checks if the language file was loaded successfully.
187+ *
188+ * @return true if loading succeeded, false otherwise
189+ */
190+ public boolean isLoadedSuccessfully () {
191+ return loadedSuccessfully ;
192+ }
193+
194+ /**
195+ * Returns the loading error if there is one.
196+ *
197+ * @return The error message or null if no error
198+ */
199+ public String getLoadError () {
200+ return loadError ;
201+ }
202+
203+ /**
204+ * Checks if a category exists in the language file.
205+ *
206+ * @param category The category name to check
207+ * @return true if the category exists, false otherwise
208+ */
209+ public boolean hasCategory (String category ) {
210+ return messages .containsKey (category );
211+ }
212+
213+ /**
214+ * Checks if a message exists in a category.
215+ *
216+ * @param category The category name
217+ * @param messagePath The message path in the category
218+ * @return true if the message exists, false otherwise
219+ */
220+ public boolean hasMessage (String category , String messagePath ) {
221+ return messages .containsKey (category ) &&
222+ messages .get (category ).containsKey (messagePath );
223+ }
224+
225+ /**
226+ * Retrieves all available categories.
227+ *
228+ * @return A Set containing all category names
229+ */
230+ public java .util .Set <String > getCategories () {
231+ return messages .keySet ();
232+ }
233+
234+ /**
235+ * Retrieves all messages from a category.
236+ *
237+ * @param category The category name
238+ * @return A Map containing all messages in the category, or null if the category doesn't exist
239+ */
240+ public Map <String , String > getMessagesInCategory (String category ) {
241+ return messages .get (category );
242+ }
243+ }
0 commit comments