UNPKG

6.33 kBPlain TextView Raw
1import _ from "lodash"
2import xlsx from "xlsx"
3
4export interface LocalizedString {
5 _base: string
6 [language: string]: string // Localizations
7}
8
9export interface Locale {
10 /** ISO code for locale (e.g. "en") */
11 code: string
12
13 /** Local name for locale (e.g. Espanol) */
14 name: string
15}
16
17/** Extracts localized strings from a plain object */
18export function extractLocalizedStrings(obj: any): LocalizedString[] {
19 if (obj == null) {
20 return []
21 }
22
23 // Return self if string
24 if (obj._base != null) {
25 return [obj]
26 }
27
28 let strs: any = []
29
30 // If array, concat each
31 if (_.isArray(obj)) {
32 for (let item of obj) {
33 strs = strs.concat(extractLocalizedStrings(item))
34 }
35 } else if (_.isObject(obj)) {
36 for (let key in obj) {
37 const value = obj[key]
38 strs = strs.concat(extractLocalizedStrings(value))
39 }
40 }
41
42 return strs
43}
44
45/** Keep unique base language string combinations */
46export function dedupLocalizedStrings(strs: LocalizedString[]): LocalizedString[] {
47 const out = []
48
49 const keys = {}
50 for (let str of strs) {
51 const key = str._base + ":" + str[str._base]
52 if (keys[key]) {
53 continue
54 }
55 keys[key] = true
56 out.push(str)
57 }
58 return out
59}
60
61/** Change the base locale for a set of localizations.
62 * Works by making whatever the user sees as the toLocale base
63 */
64export function changeBaseLocale(strs: LocalizedString[], fromLocale: string, toLocale: string): void {
65 for (let str of strs) {
66 // Get displayed
67 var displayed
68 if (str[fromLocale]) {
69 displayed = str[fromLocale]
70 delete str[fromLocale]
71 } else if (str[str._base]) {
72 displayed = str[str._base]
73 delete str[str._base]
74 }
75
76 if (displayed) {
77 str[toLocale] = displayed
78 str._base = toLocale
79 }
80 }
81}
82
83/** Update a set of strings based on newly localized ones */
84export function updateLocalizedStrings(strs: LocalizedString[], updates: LocalizedString[]): void {
85 // Regularize CR/LF and trim
86 const regularize = (str: any) => {
87 if (!str) {
88 return str
89 }
90 return str.replace(/\r/g, "").trim()
91 }
92
93 // Map updates by key
94 const updateMap = {}
95 for (let update of updates) {
96 updateMap[update._base + ":" + regularize(update[update._base])] = update
97 }
98
99 // Apply to each str
100 for (let str of strs) {
101 const match = updateMap[str._base + ":" + regularize(str[str._base])]
102 if (match != null) {
103 for (let key in match) {
104 const value = match[key]
105 if (key !== "_base" && key !== str._base && key !== "_unused") {
106 // Also ignore unused
107 // Remove blank values
108 if (value) {
109 str[key] = regularize(value)
110 } else {
111 delete str[key]
112 }
113 }
114 }
115 }
116 }
117}
118
119/** Exports localized strings for specified locales to XLSX file. Returns base64 */
120export function exportXlsx(locales: Locale[], strs: LocalizedString[]): string {
121 let locale
122 const wb: any = { SheetNames: [], Sheets: {} }
123
124 const range = { s: { c: 10000000, r: 10000000 }, e: { c: 0, r: 0 } }
125 const ws = {}
126
127 function addCell(row: any, column: any, value: any) {
128 // Update ranges
129 if (range.s.r > row) {
130 range.s.r = row
131 }
132 if (range.s.c > column) {
133 range.s.c = column
134 }
135 if (range.e.r < row) {
136 range.e.r = row
137 }
138 if (range.e.c < column) {
139 range.e.c = column
140 }
141
142 // Create cell
143 const cell = { v: value, t: "s" }
144 const cell_ref = xlsx.utils.encode_cell({ c: column, r: row })
145 return (ws[cell_ref] = cell)
146 }
147
148 let localeCount = 0
149 addCell(0, localeCount++, "Original Language")
150
151 if (!_.findWhere(locales, { code: "en" })) {
152 locales = locales.concat([{ code: "en", name: "English" }])
153 }
154
155 // Add locale columns
156 for (locale of locales) {
157 addCell(0, localeCount++, locale.name)
158 }
159
160 // Add rows
161 let rows = 0
162 for (let str of strs) {
163 if (str._unused) {
164 continue
165 }
166
167 const base = _.findWhere(locales, { code: str._base })
168
169 // Skip if unknown
170 if (!base) {
171 continue
172 }
173
174 let columns = 0
175 rows++
176
177 addCell(rows, columns++, base.name)
178 for (locale of locales) {
179 addCell(rows, columns++, str[locale.code] || "")
180 }
181 }
182
183 // Encode range
184 if (range.s.c < 10000000) {
185 ws["!ref"] = xlsx.utils.encode_range(range)
186 }
187
188 // Add worksheet to workbook */
189 wb.SheetNames.push("Translation")
190 wb.Sheets["Translation"] = ws
191
192 const wbout = xlsx.write(wb, { bookType: "xlsx", bookSST: true, type: "base64" })
193 return wbout
194}
195
196/** Import from base64 excel */
197export function importXlsx(locales: Locale[], xlsxFile: string): LocalizedString[] {
198 const wb = xlsx.read(xlsxFile, { type: "base64" })
199
200 const ws = wb.Sheets[wb.SheetNames[0]]!
201
202 // If English is not a locale, append it, as built-in form elements
203 // are specified in English
204 if (!_.findWhere(locales, { code: "en" })) {
205 locales = locales.concat([{ code: "en", name: "English" }])
206 }
207
208 const strs = []
209
210 // Get the range of cells
211 const lastCell = ws["!ref"]!.split(":")[1]
212
213 const totalColumns = xlsx.utils.decode_cell(lastCell).c + 1
214 const totalRows = xlsx.utils.decode_cell(lastCell).r + 1
215
216 // For each rows
217 for (let i = 1, end = totalRows, asc = 1 <= end; asc ? i < end : i > end; asc ? i++ : i--) {
218 // Get base locale
219 const base = _.findWhere(locales, { name: ws[xlsx.utils.encode_cell({ c: 0, r: i })]?.v })
220 // Skip if unknown
221 if (!base) {
222 continue
223 }
224
225 const str = { _base: base.code }
226
227 for (let col = 1, end1 = totalColumns, asc1 = 1 <= end1; asc1 ? col < end1 : col > end1; asc1 ? col++ : col--) {
228 const cell = ws[xlsx.utils.encode_cell({ c: col, r: i })]
229
230 if (!cell) {
231 continue
232 }
233
234 let val = cell.v
235
236 // If value and not NaN, store in string
237 if (val != null && val !== "" && val === val) {
238 // Convert to string
239 val = String(val)
240 } else {
241 // Any invalid value is considered empty
242 val = ""
243 }
244
245 // Get locale of cell
246 const locale = _.findWhere(locales, { name: ws[xlsx.utils.encode_cell({ c: col, r: 0 })]?.v })
247 if (locale) {
248 str[locale.code] = val
249 }
250 }
251
252 // Ignore if base language blank
253 if (str[str._base]) {
254 strs.push(str)
255 }
256 }
257
258 return strs
259}