Skip to content

Commit

Permalink
Merge pull request #571 from nextcloud/fix/loadTranslations
Browse files Browse the repository at this point in the history
  • Loading branch information
skjnldsv committed Jan 26, 2023
2 parents 50381bf + 0101ebe commit 34d5979
Show file tree
Hide file tree
Showing 4 changed files with 195 additions and 11 deletions.
23 changes: 12 additions & 11 deletions lib/translation.ts
Expand Up @@ -146,25 +146,28 @@ export function translatePlural(
* @return {Promise} promise
*/
export function loadTranslations(appName: string, callback: (...args: []) => unknown) {
// already available ?
interface TranslationBundle {
translations: Translations
pluralForm: string
}

if (hasAppTranslations(appName) || getLocale() === 'en') {
return Promise.resolve().then(callback)
}

const url = generateFilePath(appName, 'l10n', getLocale() + '.json')

const promise = new Promise<{
translations: Translations
pluralForm: string
}>((resolve, reject) => {
const promise = new Promise<TranslationBundle>((resolve, reject) => {
const request = new XMLHttpRequest()
request.open('GET', url, false)
request.open('GET', url, true)
request.onerror = () => {
reject(new Error(request.statusText))
reject(new Error(request.statusText || 'Network error'))
}
request.onload = () => {
if (request.status >= 200 && request.status < 300) {
resolve(JSON.parse(request.responseText))
const bundle = JSON.parse(request.responseText)
if (bundle?.translations) resolve(bundle)
else reject(new Error('Invalid content of translation bundle'))
} else {
reject(new Error(request.statusText))
}
Expand All @@ -175,9 +178,7 @@ export function loadTranslations(appName: string, callback: (...args: []) => unk
// load JSON translation bundle per AJAX
return promise
.then((result) => {
if (result.translations) {
register(appName, result.translations)
}
register(appName, result.translations)
return result
})
.then(callback)
Expand Down
16 changes: 16 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Expand Up @@ -53,6 +53,7 @@
"gettext-parser": "^6.0.0",
"jest": "^29.3.1",
"jest-environment-jsdom": "^29.3.1",
"mock-xmlhttprequest": "^8.1.0",
"rollup": "^3.9.1",
"ts-jest": "^29.0.3",
"tslib": "^2.4.1",
Expand Down
166 changes: 166 additions & 0 deletions tests/loadTranslations.test.ts
@@ -0,0 +1,166 @@
import { MockXhrServer, newServer } from 'mock-xmlhttprequest'

import { loadTranslations, register, translate, _unregister } from '../lib/translation'

const setLocale = (locale) => document.documentElement.setAttribute('data-locale', locale)

describe('loadTranslations', () => {
let server: MockXhrServer

beforeEach(() => {
setLocale('de')
server = newServer()
.addHandler('GET', '/myapp/l10n/de.json', {
status: 200,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
translations: {
'Hello world!': 'Hallo Welt!',
},
}),
})
.addHandler('GET', '/invalid/l10n/de.json', {
status: 200,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
strings: {
'Hello world!': 'Hallo Welt!',
},
}),
})
.addHandler('GET', '/empty/l10n/de.json', {
status: 200,
headers: { 'Content-Type': 'application/json' },
body: '',
})
.addHandler('GET', '/404/l10n/de.json', {
status: 404,
statusText: 'Not Found',
})
.addHandler('GET', '/500/l10n/de.json', {
status: 500,
statusText: 'Internal Server Error',
})
.addHandler('GET', '/networkissue/l10n/de.json', (req) => req.setNetworkError())
.setDefault404()
server
.disableTimeout()
server
.install()
})

afterEach(() => {
server.remove()
jest.clearAllMocks()
})

it('calls callback if app already exists', async () => {
register('myapp', {
Bye: 'Tschüss',
})

const callback = jest.fn()
try {
await loadTranslations('myapp', callback)
// Callback called
expect(callback).toBeCalledTimes(1)
// No requests done
expect(server.getRequestLog().length).toBe(0)
// Old translations work
expect(translate('myapp', 'Bye')).toBe('Tschüss')
// does not override translations
expect(translate('myapp', 'Hello world!')).toBe('Hello world!')
} catch (e) {
expect(e).toBe('Unexpected error')
} finally {
_unregister('myapp')
}
})

it('calls callback if locale is English', async () => {
setLocale('en')
const callback = jest.fn()

try {
await loadTranslations('myapp', callback)
// Callback called
expect(callback).toBeCalledTimes(1)
// No requests done
expect(server.getRequestLog().length).toBe(0)
} catch (e) {
expect(e).toBe('Unexpected error')
}
})

it('registers new translations', async () => {
const callback = jest.fn()
try {
await loadTranslations('myapp', callback)
// Callback called
expect(callback).toBeCalledTimes(1)
// No requests done
expect(server.getRequestLog().length).toBe(1)
// New translations work
expect(translate('myapp', 'Hello world!')).toBe('Hallo Welt!')
} catch (e) {
expect(e).toBe('Unexpected error')
} finally {
console.warn(server.getRequestLog()[0])
}
})

it('does reject on network error', async () => {
const callback = jest.fn()
try {
await loadTranslations('networkissue', callback)
expect('').toBe('Unexpected pass')
} catch (e) {
expect(e instanceof Error).toBe(true)
expect((<Error>e).message).toBe('Network error')
}
})

it('does reject on server error', async () => {
const callback = jest.fn()
try {
await loadTranslations('500', callback)
expect('').toBe('Unexpected pass')
} catch (e) {
expect(e instanceof Error).toBe(true)
expect((<Error>e).message).toBe('Internal Server Error')
}
})

it('does reject on unavailable bundle', async () => {
const callback = jest.fn()
try {
await loadTranslations('404', callback)
expect('').toBe('Unexpected pass')
} catch (e) {
expect(e instanceof Error).toBe(true)
expect((<Error>e).message).toBe('Not Found')
}
})

it('does reject on invalid bundle', async () => {
const callback = jest.fn()
try {
await loadTranslations('invalid', callback)
expect('').toBe('Unexpected pass')
} catch (e) {
expect(e instanceof Error).toBe(true)
expect((<Error>e).message).toBe('Invalid content of translation bundle')
}
})

it('does reject on empty bundle', async () => {
const callback = jest.fn()
try {
await loadTranslations('invalid', callback)
expect('').toBe('Unexpected pass')
} catch (e) {
expect(e instanceof Error).toBe(true)
expect((<Error>e).message).toBe('Invalid content of translation bundle')
}
})
})

0 comments on commit 34d5979

Please sign in to comment.