1 | import _ from "lodash"
|
2 | import xlsx from "xlsx"
|
3 |
|
4 | export interface LocalizedString {
|
5 | _base: string
|
6 | [language: string]: string
|
7 | }
|
8 |
|
9 | export interface Locale {
|
10 |
|
11 | code: string
|
12 |
|
13 |
|
14 | name: string
|
15 | }
|
16 |
|
17 |
|
18 | export function extractLocalizedStrings(obj: any): LocalizedString[] {
|
19 | if (obj == null) {
|
20 | return []
|
21 | }
|
22 |
|
23 |
|
24 | if (obj._base != null) {
|
25 | return [obj]
|
26 | }
|
27 |
|
28 | let strs: any = []
|
29 |
|
30 |
|
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 |
|
46 | export 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 |
|
62 |
|
63 |
|
64 | export function changeBaseLocale(strs: LocalizedString[], fromLocale: string, toLocale: string): void {
|
65 | for (let str of strs) {
|
66 |
|
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 |
|
84 | export function updateLocalizedStrings(strs: LocalizedString[], updates: LocalizedString[]): void {
|
85 |
|
86 | const regularize = (str: any) => {
|
87 | if (!str) {
|
88 | return str
|
89 | }
|
90 | return str.replace(/\r/g, "").trim()
|
91 | }
|
92 |
|
93 |
|
94 | const updateMap = {}
|
95 | for (let update of updates) {
|
96 | updateMap[update._base + ":" + regularize(update[update._base])] = update
|
97 | }
|
98 |
|
99 |
|
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 |
|
107 |
|
108 | if (value) {
|
109 | str[key] = regularize(value)
|
110 | } else {
|
111 | delete str[key]
|
112 | }
|
113 | }
|
114 | }
|
115 | }
|
116 | }
|
117 | }
|
118 |
|
119 |
|
120 | export 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 |
|
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 |
|
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 |
|
156 | for (locale of locales) {
|
157 | addCell(0, localeCount++, locale.name)
|
158 | }
|
159 |
|
160 |
|
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 |
|
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 |
|
184 | if (range.s.c < 10000000) {
|
185 | ws["!ref"] = xlsx.utils.encode_range(range)
|
186 | }
|
187 |
|
188 |
|
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 |
|
197 | export 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 |
|
203 |
|
204 | if (!_.findWhere(locales, { code: "en" })) {
|
205 | locales = locales.concat([{ code: "en", name: "English" }])
|
206 | }
|
207 |
|
208 | const strs = []
|
209 |
|
210 |
|
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 |
|
217 | for (let i = 1, end = totalRows, asc = 1 <= end; asc ? i < end : i > end; asc ? i++ : i--) {
|
218 |
|
219 | const base = _.findWhere(locales, { name: ws[xlsx.utils.encode_cell({ c: 0, r: i })]?.v })
|
220 |
|
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 |
|
237 | if (val != null && val !== "" && val === val) {
|
238 |
|
239 | val = String(val)
|
240 | } else {
|
241 |
|
242 | val = ""
|
243 | }
|
244 |
|
245 |
|
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 |
|
253 | if (str[str._base]) {
|
254 | strs.push(str)
|
255 | }
|
256 | }
|
257 |
|
258 | return strs
|
259 | }
|