1 /**
2  * A library for i18n in D
3  * Author: KonstantIMP <mihedovkos@gmail.com>
4  * Date: 22 Aug 2021
5  */
6 module di.i18n;
7 
8 import std.json, std.file, std.exception;
9 
10 ///> Aliases for better use
11 alias _  = I18n.get;
12 alias _f = I18n.getFallback;
13 
14 /** 
15  * An exception for I18n errors
16  */
17 public class I18nException : Exception {
18     /** 
19      * Creates and throws new I18nException
20      * Params:
21      *   err = Error message for the exception
22      */
23     public this (string err) { super (err); }
24 }
25 
26 /** 
27  * Struct for locale description
28  * To load locale use JSON file with the struct :
29  * { "name" : "locale`s name", "tr" : [{"id" : "id for the text", "tr" : "translation for the id"}, ...] }
30  */
31 private struct Locale {
32     ///> Locale`s name (like 'en')
33     public string name = "";
34     ///> Loaded translations for this locale
35     public string [string] tr;
36 }
37 
38 /** 
39  * Class for actual i18n
40  */
41 public static class I18n {
42     ///> List of the loaded locales
43     private static Locale [string] loadedLocales;
44     ///> Currently used locale
45     private static string currentLocale;
46     
47     /**
48      * Inits the I18n module
49      */
50     public static this () {
51         import core.stdc.locale : setlocale, LC_ALL;
52         import std.conv : to;
53 
54         currentLocale = "";
55         
56         foreach (f; ["./i18n/", "./po/", "./locale/"]) {
57             try loadLocales(f);
58             catch (Exception) {}
59         }
60 
61         string tmp = (to!string(setlocale(LC_ALL, "")))[0 .. 2];
62         if (tmp in loadedLocales) currentLocale = tmp;
63         else if (loadedLocales.keys.length) currentLocale = loadedLocales.keys[0];
64     }
65 
66     /** 
67      * Loads locale from JSON file
68      * Params:
69      *   path = Path to the JSON locale
70      * Throws:
71      *   FileException if the file doesn`t exsists
72      *   I18nException if was given incorrect locale file
73      */
74     public static void loadLocale (string path) {
75         import std.conv : to;
76         loadLocaleFromMemory (to!string(read (path)));
77     }
78 
79     /** 
80      * Loads locale from data
81      * Params:
82      *   data = Data for loading
83      * Throws:
84      *   I18nException if was given incorrect locale file
85      *   JSONException if was given incorrect locale file
86      */
87     public static void loadLocaleFromMemory (string data) {
88         JSONValue lj = parseJSON ((data));
89 
90         Locale ll; ll.name = lj["name"].str();
91 
92         foreach (tr; lj["tr"].array) {
93             ll.tr[tr["id"].str()] = tr["tr"].str();
94         }
95         
96         loadedLocales[ll.name] = ll;
97     }
98 
99     /** 
100      * Loads all locales from the folder (non recursive)
101      * Params:
102      *   path = Path to the folder for loading
103      * Throws:
104      *   FileException if cannot read a file with the locale
105      *   I18nException if was found an incorrect locale file
106      */
107     public static void loadLocales (string path) {
108         foreach (DirEntry en; dirEntries (path, SpanMode.shallow)) {
109             if (en.isFile) loadLocale (en.name);
110         }
111     }
112 
113     /** 
114      * Getter for the translation
115      * Params:
116      *   id = ID for the translation
117      *   locale = Locale for translation getting 
118      * Returns: Translation for the ID
119      * Throws: I18nException if cannot find locale or id
120      */
121     public static string get (string id, string locale = currentLocale) {
122         if (locale !in loadedLocales) throw new I18nException("Cannot find locale : " ~ locale);
123         if (id !in loadedLocales[locale].tr) throw new I18nException("Cannot find the id : " ~ id);
124         return loadedLocales[locale].tr[id];
125     }
126 
127     /** 
128      * Getter for the translation
129      * Params:
130      *   id = ID for the translation
131      *   fallback = Translation if the error was catched
132      *   locale = Locale for translation getting 
133      * Returns: Translation for the ID or fallback
134      */
135     public static string getFallback (string id, string fallback, string locale = currentLocale) {
136         if (locale !in loadedLocales) return fallback;
137         if (id !in loadedLocales[locale].tr) return fallback;
138         return loadedLocales[locale].tr[id];
139     }
140 
141     /** 
142      * Returns: List of the loaded locales
143      */
144     public static string [] getLoadedLocales () { return loadedLocales.keys; }
145 
146     /** 
147      * Returns: Currently used locale
148      */
149     public static string getCurrentLocale () { return currentLocale; }
150 }