@@ -8,6 +8,14 @@ import { merge, get } from 'lodash-es'
88import languages from '@/languages/lib/languages'
99import { correctTranslatedContentStrings } from '@/languages/lib/correct-translation-content'
1010
11+ interface YAMLException extends Error {
12+ mark ?: any
13+ }
14+
15+ interface FileSystemError extends Error {
16+ code ?: string
17+ }
18+
1119// If you run `export DEBUG_JIT_DATA_READS=true` in your terminal,
1220// next time it will mention every file it reads from disk.
1321const DEBUG_JIT_DATA_READS = Boolean ( JSON . parse ( process . env . DEBUG_JIT_DATA_READS || 'false' ) )
@@ -23,29 +31,33 @@ const ALWAYS_ENGLISH_MD_FILES = new Set([
2331] )
2432
2533// Returns all the things inside a directory
26- export const getDeepDataByLanguage = memoize ( ( dottedPath , langCode , dir = null ) => {
27- if ( ! ( langCode in languages ) )
28- throw new Error ( `langCode '${ langCode } ' not a recognized language code` )
34+ export const getDeepDataByLanguage = memoize (
35+ ( dottedPath : string , langCode : string , dir : string | null = null ) : any => {
36+ if ( ! ( langCode in languages ) ) {
37+ throw new Error ( `langCode '${ langCode } ' not a recognized language code` )
38+ }
2939
30- // The `dir` argument is only used for testing purposes.
31- // For example, our unit tests that depend on using a fixtures
32- // root.
33- // If we don't allow those tests to override the `dir` argument,
34- // it'll be stuck from the first time `languages.js` was imported.
35- if ( dir === null ) {
36- dir = languages [ langCode ] . dir
37- }
38- return getDeepDataByDir ( dottedPath , dir )
39- } )
40+ // The `dir` argument is only used for testing purposes.
41+ // For example, our unit tests that depend on using a fixtures
42+ // root.
43+ // If we don't allow those tests to override the `dir` argument,
44+ // it'll be stuck from the first time `languages.js` was imported.
45+ let actualDir = dir
46+ if ( actualDir === null ) {
47+ actualDir = languages [ langCode ] . dir
48+ }
49+ return getDeepDataByDir ( dottedPath , actualDir )
50+ } ,
51+ )
4052
4153// Doesn't need to be memoized because it's used by getDataKeysByLanguage
4254// which is already memoized.
43- function getDeepDataByDir ( dottedPath , dir ) {
55+ function getDeepDataByDir ( dottedPath : string , dir : string ) : any {
4456 const fullPath = [ 'data' ]
4557 const split = dottedPath . split ( / \. / g)
4658 fullPath . push ( ...split )
4759
48- const things = { }
60+ const things : any = { }
4961 const relPath = fullPath . join ( path . sep )
5062 for ( const dirent of getDirents ( dir , relPath ) ) {
5163 if ( dirent . name === 'README.md' ) continue
@@ -63,12 +75,12 @@ function getDeepDataByDir(dottedPath, dir) {
6375 return things
6476}
6577
66- function getDirents ( root , relPath ) {
78+ function getDirents ( root : string , relPath : string ) : fs . Dirent [ ] {
6779 const filePath = root ? path . join ( root , relPath ) : relPath
6880 return fs . readdirSync ( filePath , { withFileTypes : true } )
6981}
7082
71- export const getUIDataMerged = memoize ( ( langCode ) => {
83+ export const getUIDataMerged = memoize ( ( langCode : string ) : any => {
7284 const uiEnglish = getUIData ( 'en' )
7385 if ( langCode === 'en' ) return uiEnglish
7486 // Got to combine. Start with the English and put the translation on top.
@@ -77,21 +89,21 @@ export const getUIDataMerged = memoize((langCode) => {
7789 // swedish = {food: "Mat"}
7890 // =>
7991 // combind = {food: "Mat", drink: "Drink"}
80- const combined = { }
92+ const combined : any = { }
8193 merge ( combined , uiEnglish )
8294 merge ( combined , getUIData ( langCode ) )
8395 return combined
8496} )
8597
8698// Doesn't need to be memoized because it's used by another function
8799// that is memoized.
88- const getUIData = ( langCode ) => {
100+ const getUIData = ( langCode : string ) : any => {
89101 const fullPath = [ 'data' , 'ui.yml' ]
90102 const { dir } = languages [ langCode ]
91103 return getYamlContent ( dir , fullPath . join ( path . sep ) )
92104}
93105
94- export const getDataByLanguage = memoize ( ( dottedPath , langCode ) => {
106+ export const getDataByLanguage = memoize ( ( dottedPath : string , langCode : string ) : any => {
95107 if ( ! ( langCode in languages ) )
96108 throw new Error ( `langCode '${ langCode } ' not a recognized language code` )
97109 const { dir } = languages [ langCode ]
@@ -111,7 +123,7 @@ export const getDataByLanguage = memoize((dottedPath, langCode) => {
111123 }
112124 return value
113125 } catch ( error ) {
114- if ( error instanceof Error && error . mark && error . message ) {
126+ if ( error instanceof Error && ( error as YAMLException ) . mark && error . message ) {
115127 // It's a yaml.load() generated error!
116128 // Remember, the file that we read might have been a .yml or a .md
117129 // file. If it was a .md file, with corrupt front-matter that too
@@ -128,12 +140,17 @@ export const getDataByLanguage = memoize((dottedPath, langCode) => {
128140 throw error
129141 }
130142
131- if ( error . code === 'ENOENT' ) return undefined
143+ if ( ( error as FileSystemError ) . code === 'ENOENT' ) return undefined
132144 throw error
133145 }
134146} )
135147
136- function getDataByDir ( dottedPath , dir , englishRoot , langCode ) {
148+ function getDataByDir (
149+ dottedPath : string ,
150+ dir : string ,
151+ englishRoot ?: string ,
152+ langCode ?: string ,
153+ ) : any {
137154 const fullPath = [ 'data' ]
138155
139156 // Using English here because it doesn't matter. We just want to
@@ -159,29 +176,29 @@ function getDataByDir(dottedPath, dir, englishRoot, langCode) {
159176 // data/early-access/reusables/foo/bar.md
160177 //
161178 if ( split [ 0 ] === 'early-access' ) {
162- fullPath . push ( split . shift ( ) )
179+ fullPath . push ( split . shift ( ) ! )
163180 }
164181 const first = split [ 0 ]
165182
166183 if ( first === 'variables' ) {
167- const key = split . pop ( )
168- const basename = split . pop ( )
184+ const key = split . pop ( ) !
185+ const basename = split . pop ( ) !
169186 fullPath . push ( ...split )
170187 fullPath . push ( `${ basename } .yml` )
171188 const allData = getYamlContent ( dir , fullPath . join ( path . sep ) , englishRoot )
172- if ( allData ) {
189+ if ( allData && key ) {
173190 const value = allData [ key ]
174191 if ( value ) {
175192 return matter ( value ) . content
176193 }
177194 } else {
178195 console . warn ( `Unable to find variables Yaml file ${ fullPath . join ( path . sep ) } ` )
179196 }
180- return
197+ return undefined
181198 }
182199
183200 if ( first === 'reusables' ) {
184- const nakedname = split . pop ( )
201+ const nakedname = split . pop ( ) !
185202 fullPath . push ( ...split )
186203 fullPath . push ( `${ nakedname } .md` )
187204 const markdown = getMarkdownContent ( dir , fullPath . join ( path . sep ) , englishRoot )
@@ -205,7 +222,7 @@ function getDataByDir(dottedPath, dir, englishRoot, langCode) {
205222 // genuinely give it the English equivalent content, which it
206223 // sometimes uses to correct some Liquid tags. At least other
207224 // good corrections might happen.
208- if ( error . code !== 'ENOENT' ) {
225+ if ( ( error as FileSystemError ) . code !== 'ENOENT' ) {
209226 throw error
210227 }
211228 }
@@ -226,25 +243,25 @@ function getDataByDir(dottedPath, dir, englishRoot, langCode) {
226243 }
227244
228245 if ( first === 'product-examples' || first === 'glossaries' || first === 'release-notes' ) {
229- const basename = split . pop ( )
246+ const basename = split . pop ( ) !
230247 fullPath . push ( ...split )
231248 fullPath . push ( `${ basename } .yml` )
232249 return getYamlContent ( dir , fullPath . join ( path . sep ) , englishRoot )
233250 }
234251
235252 if ( first === 'learning-tracks' ) {
236- const key = split . pop ( )
237- const basename = split . pop ( )
253+ const key = split . pop ( ) !
254+ const basename = split . pop ( ) !
238255 fullPath . push ( ...split )
239256 fullPath . push ( `${ basename } .yml` )
240257 const allData = getYamlContent ( dir , fullPath . join ( path . sep ) , englishRoot )
241- return allData [ key ]
258+ return key ? allData [ key ] : undefined
242259 }
243260
244261 throw new Error ( `Can't find the key '${ dottedPath } ' in the scope.` )
245262}
246263
247- function getSmartSplit ( dottedPath ) {
264+ function getSmartSplit ( dottedPath : string ) : string [ ] {
248265 const split = dottedPath . split ( '.' )
249266 const bits = [ ]
250267 for ( let i = 0 , len = split . length ; i < len ; i ++ ) {
@@ -284,47 +301,55 @@ function getSmartSplit(dottedPath) {
284301// 2.1. read and parse data/variables/product.yml
285302// -> cache HIT (Yay!)
286303//
287- const getYamlContent = memoize ( ( root , relPath , englishRoot ) => {
288- // Certain Yaml files we know we always want the English one
289- // no matter what the specified language is.
290- // For example, we never want `data/variables/product.yml` translated
291- // so we know to immediately fall back to the English one.
292- if ( ALWAYS_ENGLISH_YAML_FILES . has ( relPath ) ) {
293- // This forces it to read from English. Later, when it goes
294- // into `getFileContent(...)` it will note that `root !== englishRoot`
295- // so it won't try to fall back.
296- root = englishRoot
297- }
298- const fileContent = getFileContent ( root , relPath , englishRoot )
299- return yaml . load ( fileContent , { filename : relPath } )
300- } )
304+ const getYamlContent = memoize (
305+ ( root : string | undefined , relPath : string , englishRoot ?: string ) : any => {
306+ // Certain Yaml files we know we always want the English one
307+ // no matter what the specified language is.
308+ // For example, we never want `data/variables/product.yml` translated
309+ // so we know to immediately fall back to the English one.
310+ if ( ALWAYS_ENGLISH_YAML_FILES . has ( relPath ) ) {
311+ // This forces it to read from English. Later, when it goes
312+ // into `getFileContent(...)` it will note that `root !== englishRoot`
313+ // so it won't try to fall back.
314+ root = englishRoot
315+ }
316+ const fileContent = getFileContent ( root , relPath , englishRoot )
317+ return yaml . load ( fileContent , { filename : relPath } )
318+ } ,
319+ )
301320
302321// The reason why this is memoized, is the same as for getYamlContent() above.
303- const getMarkdownContent = memoize ( ( root , relPath , englishRoot ) => {
304- // Certain reusables we never want to be pulled from the translations.
305- // For example, certain reusables don't contain any English prose. Just
306- // facts like numbers or hardcoded key words.
307- // If this is the case, forcibly always draw from the English files.
308- if ( ALWAYS_ENGLISH_MD_FILES . has ( relPath ) ) {
309- root = englishRoot
310- }
322+ const getMarkdownContent = memoize (
323+ ( root : string | undefined , relPath : string , englishRoot ?: string ) : string => {
324+ // Certain reusables we never want to be pulled from the translations.
325+ // For example, certain reusables don't contain any English prose. Just
326+ // facts like numbers or hardcoded key words.
327+ // If this is the case, forcibly always draw from the English files.
328+ if ( ALWAYS_ENGLISH_MD_FILES . has ( relPath ) ) {
329+ root = englishRoot
330+ }
311331
312- const fileContent = getFileContent ( root , relPath , englishRoot )
313- return matter ( fileContent ) . content . trimEnd ( )
314- } )
332+ const fileContent = getFileContent ( root , relPath , englishRoot )
333+ return matter ( fileContent ) . content . trimEnd ( )
334+ } ,
335+ )
315336
316- const getFileContent = ( root , relPath , englishRoot ) => {
337+ const getFileContent = (
338+ root : string | undefined ,
339+ relPath : string ,
340+ englishRoot ?: string ,
341+ ) : string => {
317342 const filePath = root ? path . join ( root , relPath ) : relPath
318343 if ( DEBUG_JIT_DATA_READS ) console . log ( 'READ' , filePath )
319344 try {
320345 return fs . readFileSync ( filePath , 'utf-8' )
321346 } catch ( err ) {
322347 // It might fail because that particular data entry doesn't yet
323348 // exist in a translation
324- if ( err . code === 'ENOENT' ) {
349+ if ( ( err as FileSystemError ) . code === 'ENOENT' ) {
325350 // If looking it up as a file fails, give it one more chance if the
326351 // read was for a translation.
327- if ( root !== englishRoot ) {
352+ if ( englishRoot && root !== englishRoot ) {
328353 // We can try again but this time using the English files
329354 return getFileContent ( englishRoot , relPath , englishRoot )
330355 }
@@ -333,9 +358,9 @@ const getFileContent = (root, relPath, englishRoot) => {
333358 }
334359}
335360
336- function memoize ( func ) {
337- const cache = new Map ( )
338- return ( ...args ) => {
361+ function memoize < T extends ( ... args : any [ ] ) => any > ( func : T ) : T {
362+ const cache = new Map < string , any > ( )
363+ return ( ( ...args : any [ ] ) => {
339364 if ( process . env . NODE_ENV === 'development' ) {
340365 // It is very possible that certain files, when caching is disabled,
341366 // are read multiple times in short succession. E.g. `product.yml`.
@@ -374,5 +399,5 @@ function memoize(func) {
374399 if ( Array . isArray ( value ) ) return [ ...value ]
375400 if ( typeof value === 'object' ) return { ...value }
376401 return value
377- }
402+ } ) as T
378403}
0 commit comments