11#!/usr/bin/env node
22
3+ const loaderUtils = require ( "loader-utils" ) ;
4+
35// copies the resources into the webapp directory.
46//
57
@@ -61,12 +63,6 @@ const COPY_LIST = [
6163 [ "./config.json" , "webapp" , { directwatch : 1 } ] ,
6264] ;
6365
64- INCLUDE_LANGS . forEach ( function ( l ) {
65- COPY_LIST . push ( [
66- l . value , "webapp/i18n/" , { lang : 1 } ,
67- ] ) ;
68- } ) ;
69-
7066const parseArgs = require ( 'minimist' ) ;
7167const Cpx = require ( 'cpx' ) ;
7268const chokidar = require ( 'chokidar' ) ;
@@ -77,8 +73,8 @@ const argv = parseArgs(
7773 process . argv . slice ( 2 ) , { }
7874) ;
7975
80- var watch = argv . w ;
81- var verbose = argv . v ;
76+ const watch = argv . w ;
77+ const verbose = argv . v ;
8278
8379function errCheck ( err ) {
8480 if ( err ) {
@@ -136,39 +132,11 @@ function next(i, err) {
136132 . on ( 'change' , copy )
137133 . on ( 'ready' , cb )
138134 . on ( 'error' , errCheck ) ;
139- } else if ( opts . lang ) {
140- const reactSdkFile = 'node_modules/matrix-react-sdk/src/i18n/strings/' + source + '.json' ;
141- const riotWebFile = 'src/i18n/strings/' + source + '.json' ;
142-
143- // XXX: Use a debounce because for some reason if we read the language
144- // file immediately after the FS event is received, the file contents
145- // appears empty. Possibly https://github.com/nodejs/node/issues/6112
146- let makeLangDebouncer ;
147- const makeLang = ( ) => {
148- if ( makeLangDebouncer ) {
149- clearTimeout ( makeLangDebouncer ) ;
150- }
151- makeLangDebouncer = setTimeout ( ( ) => {
152- genLangFile ( source , dest ) ;
153- } , 500 ) ;
154- } ;
155-
156- [ reactSdkFile , riotWebFile ] . forEach ( function ( f ) {
157- chokidar . watch ( f )
158- . on ( 'add' , makeLang )
159- . on ( 'change' , makeLang )
160- //.on('ready', cb) We'd have to do this when both files are ready
161- . on ( 'error' , errCheck ) ;
162- } ) ;
163- next ( i + 1 , err ) ;
164135 } else {
165136 cpx . on ( 'watch-ready' , cb ) ;
166137 cpx . on ( "watch-error" , cb ) ;
167138 cpx . watch ( ) ;
168139 }
169- } else if ( opts . lang ) {
170- genLangFile ( source , dest ) ;
171- next ( i + 1 , err ) ;
172140 } else {
173141 cpx . copy ( cb ) ;
174142 }
@@ -195,21 +163,28 @@ function genLangFile(lang, dest) {
195163
196164 translations = weblateToCounterpart ( translations ) ;
197165
198- fs . writeFileSync ( dest + lang + '.json' , JSON . stringify ( translations , null , 4 ) ) ;
166+ const json = JSON . stringify ( translations , null , 4 ) ;
167+ const jsonBuffer = Buffer . from ( json ) ;
168+ const digest = loaderUtils . getHashDigest ( jsonBuffer , null , null , 7 ) ;
169+ const filename = `${ lang } .${ digest } .json` ;
170+
171+ fs . writeFileSync ( dest + filename , json ) ;
199172 if ( verbose ) {
200- console . log ( "Generated language file: " + lang ) ;
173+ console . log ( "Generated language file: " + filename ) ;
201174 }
175+
176+ return filename ;
202177}
203178
204- function genLangList ( ) {
179+ function genLangList ( langFileMap ) {
205180 const languages = { } ;
206181 INCLUDE_LANGS . forEach ( function ( lang ) {
207182 const normalizedLanguage = lang . value . toLowerCase ( ) . replace ( "_" , "-" ) ;
208183 const languageParts = normalizedLanguage . split ( '-' ) ;
209184 if ( languageParts . length == 2 && languageParts [ 0 ] == languageParts [ 1 ] ) {
210- languages [ languageParts [ 0 ] ] = { 'fileName' : lang . value + '.json' , 'label' : lang . label } ;
185+ languages [ languageParts [ 0 ] ] = { 'fileName' : langFileMap [ lang . value ] , 'label' : lang . label } ;
211186 } else {
212- languages [ normalizedLanguage ] = { 'fileName' : lang . value + '.json' , 'label' : lang . label } ;
187+ languages [ normalizedLanguage ] = { 'fileName' : langFileMap [ lang . value ] , 'label' : lang . label } ;
213188 }
214189 } ) ;
215190 fs . writeFile ( 'webapp/i18n/languages.json' , JSON . stringify ( languages , null , 4 ) , function ( err ) {
@@ -257,5 +232,50 @@ function weblateToCounterpart(inTrs) {
257232 return outTrs ;
258233}
259234
260- genLangList ( ) ;
235+ /**
236+ watch the input files for a given language,
237+ regenerate the file, adding its content-hashed filename to langFileMap
238+ and regenerating languages.json with the new filename
239+ */
240+ function watchLanguage ( lang , dest , langFileMap ) {
241+ const reactSdkFile = 'node_modules/matrix-react-sdk/src/i18n/strings/' + lang + '.json' ;
242+ const riotWebFile = 'src/i18n/strings/' + lang + '.json' ;
243+
244+ // XXX: Use a debounce because for some reason if we read the language
245+ // file immediately after the FS event is received, the file contents
246+ // appears empty. Possibly https://github.com/nodejs/node/issues/6112
247+ let makeLangDebouncer ;
248+ const makeLang = ( ) => {
249+ if ( makeLangDebouncer ) {
250+ clearTimeout ( makeLangDebouncer ) ;
251+ }
252+ makeLangDebouncer = setTimeout ( ( ) => {
253+ const filename = genLangFile ( lang , dest ) ;
254+ langFileMap [ lang ] = filename ;
255+ genLangList ( langFileMap ) ;
256+ } , 500 ) ;
257+ } ;
258+
259+ [ reactSdkFile , riotWebFile ] . forEach ( function ( f ) {
260+ chokidar . watch ( f )
261+ . on ( 'add' , makeLang )
262+ . on ( 'change' , makeLang )
263+ . on ( 'error' , errCheck ) ;
264+ } ) ;
265+ }
266+
267+ // language resources
268+ const I18N_DEST = "webapp/i18n/" ;
269+ const I18N_FILENAME_MAP = INCLUDE_LANGS . reduce ( ( m , l ) => {
270+ const filename = genLangFile ( l . value , I18N_DEST ) ;
271+ m [ l . value ] = filename ;
272+ return m ;
273+ } , { } ) ;
274+ genLangList ( I18N_FILENAME_MAP ) ;
275+
276+ if ( watch ) {
277+ INCLUDE_LANGS . forEach ( l => watchLanguage ( l . value , I18N_DEST , I18N_FILENAME_MAP ) ) ;
278+ }
279+
280+ // non-language resources
261281next ( 0 ) ;
0 commit comments