]> BookStack Code Mirror - bookstack/blob - resources/js/services/translations.js
Comments: Updated reply-to and general styling
[bookstack] / resources / js / services / translations.js
1 /**
2  *  Translation Manager
3  *  Handles the JavaScript side of translating strings
4  *  in a way which fits with Laravel.
5  */
6 class Translator {
7
8     constructor() {
9         this.store = new Map();
10         this.parseTranslations();
11     }
12
13     /**
14      * Parse translations out of the page and place into the store.
15      */
16     parseTranslations() {
17         const translationMetaTags = document.querySelectorAll('meta[name="translation"]');
18         for (const tag of translationMetaTags) {
19             const key = tag.getAttribute('key');
20             const value = tag.getAttribute('value');
21             this.store.set(key, value);
22         }
23     }
24
25     /**
26      * Get a translation, Same format as Laravel's 'trans' helper
27      * @param key
28      * @param replacements
29      * @returns {*}
30      */
31     get(key, replacements) {
32         const text = this.getTransText(key);
33         return this.performReplacements(text, replacements);
34     }
35
36     /**
37      * Get pluralised text, Dependent on the given count.
38      * Same format at Laravel's 'trans_choice' helper.
39      * @param key
40      * @param count
41      * @param replacements
42      * @returns {*}
43      */
44     getPlural(key, count, replacements) {
45         const text = this.getTransText(key);
46         return this.parsePlural(text, count, replacements);
47     }
48
49     /**
50      * Parse the given translation and find the correct plural option
51      * to use. Similar format at Laravel's 'trans_choice' helper.
52      * @param {String} translation
53      * @param {Number} count
54      * @param {Object} replacements
55      * @returns {String}
56      */
57     parsePlural(translation, count, replacements) {
58         const splitText = translation.split('|');
59         const exactCountRegex = /^{([0-9]+)}/;
60         const rangeRegex = /^\[([0-9]+),([0-9*]+)]/;
61         let result = null;
62
63         for (const t of splitText) {
64             // Parse exact matches
65             const exactMatches = t.match(exactCountRegex);
66             if (exactMatches !== null && Number(exactMatches[1]) === count) {
67                 result = t.replace(exactCountRegex, '').trim();
68                 break;
69             }
70
71             // Parse range matches
72             const rangeMatches = t.match(rangeRegex);
73             if (rangeMatches !== null) {
74                 const rangeStart = Number(rangeMatches[1]);
75                 if (rangeStart <= count && (rangeMatches[2] === '*' || Number(rangeMatches[2]) >= count)) {
76                     result = t.replace(rangeRegex, '').trim();
77                     break;
78                 }
79             }
80         }
81
82         if (result === null && splitText.length > 1) {
83             result = (count === 1) ? splitText[0] : splitText[1];
84         }
85
86         if (result === null) {
87             result = splitText[0];
88         }
89
90         return this.performReplacements(result, replacements);
91     }
92
93     /**
94      * Fetched translation text from the store for the given key.
95      * @param key
96      * @returns {String|Object}
97      */
98     getTransText(key) {
99         const value = this.store.get(key);
100
101         if (value === undefined) {
102             console.warn(`Translation with key "${key}" does not exist`);
103         }
104
105         return value;
106     }
107
108     /**
109      * Perform replacements on a string.
110      * @param {String} string
111      * @param {Object} replacements
112      * @returns {*}
113      */
114     performReplacements(string, replacements) {
115         if (!replacements) return string;
116         const replaceMatches = string.match(/:(\S+)/g);
117         if (replaceMatches === null) return string;
118         let updatedString = string;
119
120         replaceMatches.forEach(match => {
121             const key = match.substring(1);
122             if (typeof replacements[key] === 'undefined') return;
123             updatedString = updatedString.replace(match, replacements[key]);
124         });
125
126         return updatedString;
127     }
128
129 }
130
131 export default Translator;