Skip to content

Commit 8d9239a

Browse files
committed
add custom file parser
1 parent f730b53 commit 8d9239a

File tree

11 files changed

+288
-55
lines changed

11 files changed

+288
-55
lines changed

README.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,27 @@ export interface IModuleTranslationOptions {
107107
* The translation module configurations
108108
*/
109109
modules: IModuleTranslation[]
110+
/**
111+
* Custom parser after retrieving a response from the server in 'text' format.
112+
* By using this property you can parse raw text into the required TranslationObject.
113+
*
114+
* This parser will be used for every 'module' unless you specificy a fileParser at 'module' level.
115+
*/
116+
fileParser?: {
117+
/**
118+
* This property will append the file extension to the url when fetching from the server.
119+
* For example: ./assets/i18n/feature1/en.json5 when you set it as json5
120+
*/
121+
fileExtension: 'json5' | 'xml' | string
122+
/**
123+
* The parser function that can parse a string to a TranslationObject
124+
*
125+
* For example: for a json file you would typically use: JSON.parse()
126+
*
127+
* @param translation the raw translation file as text
128+
*/
129+
parseFn: (translation: string) => TranslationObject
130+
}
110131
/**
111132
* By default, each module gets its own namespace so it doesn't conflict with other modules
112133
*/
@@ -175,6 +196,25 @@ export interface IModuleTranslation {
175196
* @param translation the resolved translation file
176197
*/
177198
translateMap?: (translation: TranslationObject) => TranslationObject
199+
/**
200+
* Custom parser after retrieving a response from the server in 'text' format.
201+
* By using this property you can parse raw text into the required TranslationObject.
202+
*/
203+
fileParser?: {
204+
/**
205+
* This property will append the file extension to the url when fetching from the server.
206+
* For example: ./assets/i18n/feature1/en.json5 when you set it as json5
207+
*/
208+
fileExtension: 'json5' | 'xml' | string
209+
/**
210+
* The parser function that can parse a string to a TranslationObject
211+
*
212+
* For example: for a json file you would typically use: JSON.parse()
213+
*
214+
* @param translation the raw translation file as text
215+
*/
216+
parseFn: (translation: string) => TranslationObject
217+
}
178218
/**
179219
* Custom path template for fetching translations
180220
* @example
@@ -229,3 +269,30 @@ const options: IModuleTranslationOptions = {
229269
]
230270
};
231271
```
272+
273+
## Custom file parser
274+
275+
You can provide a custom file parser, this parser kicks in after retrieving the file from the server in text format. This allows you to use JSON5 or XML instead of the default JSON format.
276+
277+
```ts
278+
const options: IModuleTranslationOptions = {
279+
// global fileParser, applied to every module, unless you specify fileParser at 'module' level
280+
fileParser: {
281+
fileExtension: 'json5',
282+
parseFn: (text) => JSON5.parse(text)
283+
},
284+
modules: [
285+
{ baseTranslateUrl },
286+
// fileParser only applied to this module
287+
{
288+
baseTranslateUrl,
289+
moduleName: 'feature1',
290+
fileParser: {
291+
fileExtension: 'xml',
292+
parseFn: (text) => XML.parse(text)
293+
}
294+
},
295+
{ baseTranslateUrl, moduleName: 'feature2' }
296+
]
297+
}
298+
```

package-lock.json

Lines changed: 53 additions & 19 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"@angular/platform-browser-dynamic": "20.3.1",
1919
"@angular/router": "20.3.1",
2020
"@ngx-translate/core": "17.0.0",
21+
"json5": "2.2.3",
2122
"rxjs": "7.8.2",
2223
"tslib": "2.8.1",
2324
"zone.js": "0.15.1"

projects/ngx-translate-module-loader/src/lib/module-translate-loader.spec.ts

Lines changed: 63 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -230,26 +230,24 @@ describe('ModuleTranslateLoader', () => {
230230
moduleName: undefined,
231231
baseTranslateUrl: './assets/i18n',
232232
translateMap: (translation: TranslationObject) => {
233-
return Object.keys(translation)
234-
.reduce((acc, curr) => {
235-
return {
236-
...acc,
237-
[curr.toUpperCase()]: translation[curr]
238-
}
239-
}, Object())
233+
return Object.keys(translation).reduce((acc, curr) => {
234+
return {
235+
...acc,
236+
[curr.toUpperCase()]: translation[curr]
237+
}
238+
}, Object())
240239
}
241240
},
242241
{
243242
moduleName: 'feature1',
244243
baseTranslateUrl: './assets/i18n',
245244
translateMap: (translation: TranslationObject) => {
246-
return Object.keys(translation)
247-
.reduce((acc, curr) => {
248-
return {
249-
...acc,
250-
[curr.toUpperCase()]: translation[curr]
251-
}
252-
}, {})
245+
return Object.keys(translation).reduce((acc, curr) => {
246+
return {
247+
...acc,
248+
[curr.toUpperCase()]: translation[curr]
249+
}
250+
}, {})
253251
}
254252
}
255253
]
@@ -603,8 +601,57 @@ describe('ModuleTranslateLoader', () => {
603601
mock.flush(translation)
604602
})
605603

606-
function createTestRequest(path: string): TestRequest {
607-
return httpMock.expectOne(path.concat('.json'))
604+
it('should use global fileParser when fileParser at module level is not defined', (done) => {
605+
const globalParseFn = jest.fn()
606+
const moduleParseFn = jest.fn()
607+
608+
const options: IModuleTranslationOptions = {
609+
fileParser: {
610+
fileExtension: 'json5',
611+
parseFn: globalParseFn
612+
},
613+
modules: [
614+
{
615+
moduleName: 'feature1',
616+
baseTranslateUrl: './assets/i18n',
617+
fileParser: {
618+
fileExtension: 'xml',
619+
parseFn: moduleParseFn
620+
}
621+
},
622+
{
623+
moduleName: 'feature2',
624+
baseTranslateUrl: './assets/i18n'
625+
}
626+
]
627+
}
628+
629+
const language = 'en'
630+
const loader = new ModuleTranslateLoader(httpClient, options)
631+
632+
loader.getTranslation(language).subscribe(() => done())
633+
634+
let mock = createTestRequest(
635+
getTranslatePath(options.modules[0].baseTranslateUrl, options.modules[0].moduleName!, language),
636+
'xml'
637+
)
638+
expect(mock.request.method).toEqual('GET')
639+
mock.flush(translation)
640+
641+
expect(moduleParseFn).toHaveBeenCalledTimes(1)
642+
643+
mock = createTestRequest(
644+
getTranslatePath(options.modules[1].baseTranslateUrl, options.modules[1].moduleName!, language),
645+
'json5'
646+
)
647+
expect(mock.request.method).toEqual('GET')
648+
mock.flush(translation)
649+
650+
expect(globalParseFn).toHaveBeenCalledTimes(1)
651+
})
652+
653+
function createTestRequest(path: string, extension = 'json'): TestRequest {
654+
return httpMock.expectOne(path.concat(`.${extension}`))
608655
}
609656

610657
function getTranslatePath(baseTranslateUrl: string, moduleName: string, language: string): string {

0 commit comments

Comments
 (0)