Skip to content
This repository has been archived by the owner on Mar 23, 2023. It is now read-only.

feat: allow plugins to add languages #1848

Merged
merged 15 commits into from
Mar 20, 2020
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions __tests__/unit/App.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ beforeEach(() => {
mocks: {
$store: {
getters: {
'session/language': 'en-US',
'session/theme': 'dark',
'session/profile': {
id: 'test-profile'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,9 @@ describe('PluginDetailsModal', () => {
expect(wrapper.emitted('show-permissions')).toBeTruthy()
})

it('should toggle the status', () => {
it('should emit change-status', () => {
wrapper.vm.toggleStatus(true)
expect(mockDispatch).toHaveBeenCalledWith('plugin/setEnabled', { enabled: true, pluginId: 'test' })
expect(wrapper.emitted('change-status', true, wrapper.props('plugin').id)).toBeTruthy()
})

it('should report the plugin', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ describe('PluginManagerSideMenu', () => {

describe('otherCategories', () => {
it('should return other categories', () => {
expect(wrapper.vm.otherCategories).toEqual(['theme'])
expect(wrapper.vm.otherCategories).toEqual(['theme', 'language'])
})
})
})
Expand Down
118 changes: 118 additions & 0 deletions __tests__/unit/i18n/i18n-setup.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import i18n from '@/i18n'
import i18nSetup from '@/i18n/i18n-setup'

jest.mock('fs', () => ({
readFileSync: jest.fn(() => JSON.stringify({
locale: 'new-language',
messages: { test: 'translation' },
numberFormats: { currency: {} },
dateTimeFormats: { short: {} }
}))
}))

const language = {
locale: 'language',
messages: { test: 'translation' },
numberFormats: { currency: {} },
dateTimeFormats: { short: {} }
}

const setupLanguage = language => {
i18n.setLocaleMessage(language.locale, language.messages)
i18n.setNumberFormat(language.locale, language.numberFormats)
i18n.setDateTimeFormat(language.locale, language.dateTimeFormats)

i18n.locale = language.locale
}

describe('i18n > i18n-setup', () => {
let spyMessages
let spyNumberFormats
let spyDateTimeFormats

beforeEach(() => {
spyMessages = jest.spyOn(i18n, 'setLocaleMessage')
spyNumberFormats = jest.spyOn(i18n, 'setNumberFormat')
spyDateTimeFormats = jest.spyOn(i18n, 'setDateTimeFormat')
})

afterEach(() => {
spyMessages.mockRestore()
spyNumberFormats.mockRestore()
spyDateTimeFormats.mockRestore()
})

describe('setLanguage', () => {
beforeEach(() => {
setupLanguage(language)
})

it('should set the given language', () => {
const newLocale = 'new-language'

expect(i18n.locale).not.toBe(newLocale)
i18nSetup.setLanguage(newLocale)
expect(i18n.locale).toBe(newLocale)
})

it('should unload other languages', () => {
const newLocale = 'new-language'

expect(i18n.getLocaleMessage(language.locale)).toEqual(language.messages)

i18nSetup.setLanguage(newLocale)

expect(i18n.getLocaleMessage(language.messages)).toEqual({})
})
})

describe('unloadLanguage', () => {
it('should unload a language', () => {
setupLanguage(language)

expect(i18n.getLocaleMessage(language.locale)).toEqual(language.messages)
expect(i18n.getNumberFormat(language.locale)).toEqual(language.numberFormats)
expect(i18n.getDateTimeFormat(language.locale)).toEqual(language.dateTimeFormats)

i18nSetup.unloadLanguage(language.locale)

expect(spyMessages).toHaveBeenCalledWith(language.locale, undefined)
expect(spyNumberFormats).toHaveBeenCalledWith(language.locale, undefined)
expect(spyDateTimeFormats).toHaveBeenCalledWith(language.locale, undefined)

expect(i18n.getLocaleMessage(language.locale)).toEqual({})
expect(i18n.getNumberFormat(language.locale)).toEqual({})
expect(i18n.getDateTimeFormat(language.locale)).toEqual({})
})
})

describe('loadLanguage', () => {
it('should return early if no pluginLanguage is given', () => {
expect(i18nSetup.loadLanguage(language.locale)).toBe(undefined)
})

it('should return early if the current locale is the given language', () => {
expect(i18nSetup.loadLanguage(language.locale, {})).toBe(undefined)
})

it('should load the language if it isn\'t loaded already', () => {
const newLocale = 'new-language'

i18nSetup.loadLanguage(newLocale, { languagePath: 'foobar' })

expect(spyMessages).toHaveBeenCalledWith(newLocale, language.messages)
expect(spyNumberFormats).toHaveBeenCalledWith(newLocale, language.numberFormats)
expect(spyDateTimeFormats).toHaveBeenCalledWith(newLocale, language.dateTimeFormats)
})

it('should not load the language if it is loaded already', () => {
const newLocale = 'en-US'

i18nSetup.loadLanguage('en-US', { languagePath: 'foobar' })

expect(spyMessages).not.toHaveBeenCalledWith(newLocale, language.messages)
expect(spyNumberFormats).not.toHaveBeenCalledWith(newLocale, language.numberFormats)
expect(spyDateTimeFormats).not.toHaveBeenCalledWith(newLocale, language.dateTimeFormats)
})
})
})
100 changes: 100 additions & 0 deletions __tests__/unit/store/modules/plugin.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -842,6 +842,55 @@ describe('PluginModule', () => {
})
})

describe('languages', () => {
beforeAll(() => {
store.replaceState(JSON.parse(JSON.stringify(initialState)))
})

it('should return an empty object if there are no loaded plugins', () => {
expect(store.getters['plugin/languages']).toEqual({})
})

it('should return an empty object if there are no loaded plugins with languages', () => {
store.replaceState(merge(
JSON.parse(JSON.stringify(initialState)),
{
plugin: {
loaded: {
[profile1.id]: {
[availablePlugins[0].config.id]: {}
}
}
}
}
))

expect(store.getters['plugin/languages']).toEqual({})
})

it('should retrieve all languages of loaded plugins', () => {
store.replaceState(merge(
JSON.parse(JSON.stringify(initialState)),
{
plugin: {
loaded: {
[profile1.id]: {
[availablePlugins[0].config.id]: {},
[availablePlugins[1].config.id]: {
languages: {
'language-1': {}
}
}
}
}
}
}
))

expect(store.getters['plugin/languages']).toEqual({ 'language-1': {} })
})
})

describe('walletTabs', () => {
beforeEach(() => {
store.replaceState(merge(
Expand Down Expand Up @@ -1556,6 +1605,57 @@ describe('PluginModule', () => {
})
})

describe('setLanguages', () => {
beforeEach(() => {
store.replaceState(merge(
JSON.parse(JSON.stringify(initialState)),
{
plugin: {
enabled: {
[profile1.id]: {
[availablePlugins[0].config.id]: true,
[availablePlugins[2].config.id]: true
}
},
loaded: {
[profile1.id]: {
[availablePlugins[0].config.id]: {
languages: {}
},
[availablePlugins[2].config.id]: {}
}
}
}
}
))
})

it('should throw an error if the plugin is not enabled', () => {
try {
store.dispatch('plugin/setLanguages', {
pluginId: availablePlugins[1].config.id,
profileId: profile1.id
})
} catch (e) {
expect(e.message).toBe('Plugin is not enabled')
}
})

it.each([null, profile1.id])('should set the languages if the plugin is enabled', (profileId) => {
expect(store.getters['plugin/languages']).toEqual({})

store.dispatch('plugin/setLanguages', {
pluginId: availablePlugins[0].config.id,
profileId,
languages: {
'language-1': {}
}
})

expect(store.getters['plugin/languages']).toEqual({ 'language-1': {} })
})
})

describe('setWalletTabs', () => {
beforeEach(() => {
store.replaceState(merge(
Expand Down
1 change: 1 addition & 0 deletions config/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ exports.PLUGINS = {
categories: [
'gaming',
'theme',
'language',
'utility',
'other'
],
Expand Down
28 changes: 28 additions & 0 deletions src/renderer/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,12 @@ import CleanCss from 'clean-css'
import { remote, ipcRenderer } from 'electron'
import { pull, uniq } from 'lodash'
import { AppFooter, AppIntro, AppSidemenu } from '@/components/App'
import { I18N } from '@config'
import AlertMessage from '@/components/AlertMessage'
import { TransactionModal } from '@/components/Transaction'
import URIHandler from '@/services/uri-handler'
import priceApi from '@/services/price-api'
import i18nSetup from '@/i18n/i18n-setup'

const Menu = remote.Menu

Expand Down Expand Up @@ -187,6 +189,18 @@ export default {
},
themeClass () {
return `theme-${this.theme}`
},
pluginLanguages () {
return this.$store.getters['plugin/languages']
},
language () {
const language = this.$store.getters['session/language']
const defaultLocale = I18N.defaultLocale

// Ensure that the plugin language is available (not deleted from the file system)
return defaultLocale === language || this.pluginLanguages[language]
? language
: defaultLocale
}
},

Expand Down Expand Up @@ -234,6 +248,12 @@ export default {
},
theme (value) {
this.applyPluginTheme(value)
},
pluginLanguages () {
this.applyPluginLanguage(this.language)
},
language (value) {
this.applyPluginLanguage(value)
}
},

Expand Down Expand Up @@ -418,6 +438,14 @@ export default {
$style.innerHTML = null
}
}
},

applyPluginLanguage (languageName) {
if (languageName === I18N.defaultLocale) {
i18nSetup.setLanguage(languageName)
} else if (languageName && this.pluginLanguages[languageName]) {
i18nSetup.loadLanguage(languageName, this.pluginLanguages[languageName])
}
}
}
}
Expand Down
1 change: 1 addition & 0 deletions src/renderer/assets/images/flags/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
These icons were downloaded from https://www.flaticon.com/packs/international-flags
Loading