mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Translation helper: substitute react components and component wrapping substrings (#8129)
* Update i18n-helper to allow substitutions of react components and wrapping of translation substrings * Simplify code in i18n-helper.js related to substitutions, including react substitutions. * Remove wrapper support from i18n in favour of using translations in substitutions. * Fix i18n-helper substitution logic: ensure correct index of substitution is applied * Throw error if there are not enough substitutions for a translation phrase * Adds unit tests for now i18n-helper substitution functionality * Fix grammar, react element line spacing and test layout+readability in i18n-helper.test.js
This commit is contained in:
parent
7686edadb0
commit
92e6338b78
@ -637,6 +637,14 @@
|
||||
"gasPriceNoDenom": {
|
||||
"message": "Gas Price"
|
||||
},
|
||||
"gdprMessage": {
|
||||
"message": "This data is aggregated and is therefore anonymous for the purposes of General Data Protection Regulation (EU) 2016/679. For more information in relation to our privacy practices, please see our $1.",
|
||||
"description": "$1 refers to the gdprMessagePrivacyPolicy message, the translation of which is meant to be used exclusively in the context of gdprMessage"
|
||||
},
|
||||
"gdprMessagePrivacyPolicy": {
|
||||
"message": "Privacy Policy here",
|
||||
"description": "this translation is intended to be exclusively used as the replacement for the $1 in the gdprMessage translation"
|
||||
},
|
||||
"general": {
|
||||
"message": "General"
|
||||
},
|
||||
|
@ -1,4 +1,5 @@
|
||||
// cross-browser connection to extension i18n API
|
||||
import React from 'react'
|
||||
import log from 'loglevel'
|
||||
|
||||
import * as Sentry from '@sentry/browser'
|
||||
@ -39,13 +40,30 @@ export const getMessage = (localeCode, localeMessages, key, substitutions) => {
|
||||
}
|
||||
const entry = localeMessages[key]
|
||||
let phrase = entry.message
|
||||
|
||||
const hasSubstitutions = Boolean(substitutions && substitutions.length)
|
||||
const hasReactSubstitutions = hasSubstitutions &&
|
||||
substitutions.some((element) => typeof element === 'function' || typeof element === 'object')
|
||||
|
||||
// perform substitutions
|
||||
if (substitutions && substitutions.length) {
|
||||
substitutions.forEach((substitution, index) => {
|
||||
const regex = new RegExp(`\\$${index + 1}`, 'g')
|
||||
phrase = phrase.replace(regex, substitution)
|
||||
if (hasSubstitutions) {
|
||||
const parts = phrase.split(/(\$\d)/g)
|
||||
const partsToReplace = phrase.match(/(\$\d)/g)
|
||||
|
||||
if (partsToReplace.length > substitutions.length) {
|
||||
throw new Error(`Insufficient number of substitutions for message: '${phrase}'`)
|
||||
}
|
||||
|
||||
const substitutedParts = parts.map((part) => {
|
||||
const subMatch = part.match(/\$(\d)/)
|
||||
return subMatch ? substitutions[Number(subMatch[1]) - 1] : part
|
||||
})
|
||||
|
||||
phrase = hasReactSubstitutions
|
||||
? <span> { substitutedParts } </span>
|
||||
: substitutedParts.join('')
|
||||
}
|
||||
|
||||
return phrase
|
||||
}
|
||||
|
||||
|
160
ui/app/helpers/utils/i18n-helper.test.js
Normal file
160
ui/app/helpers/utils/i18n-helper.test.js
Normal file
@ -0,0 +1,160 @@
|
||||
import { getMessage } from './i18n-helper'
|
||||
import React from 'react'
|
||||
import { shallow } from 'enzyme'
|
||||
import assert from 'assert'
|
||||
|
||||
describe('i18n helper', function () {
|
||||
const TEST_LOCALE_CODE = 'TEST_LOCALE_CODE'
|
||||
|
||||
const TEST_KEY_1 = 'TEST_KEY_1'
|
||||
const TEST_KEY_2 = 'TEST_KEY_2'
|
||||
const TEST_KEY_3 = 'TEST_KEY_3'
|
||||
const TEST_KEY_4 = 'TEST_KEY_4'
|
||||
const TEST_KEY_5 = 'TEST_KEY_5'
|
||||
const TEST_KEY_6 = 'TEST_KEY_6'
|
||||
const TEST_KEY_6_HELPER = 'TEST_KEY_6_HELPER'
|
||||
const TEST_KEY_7 = 'TEST_KEY_7'
|
||||
const TEST_KEY_7_HELPER_1 = 'TEST_KEY_7_HELPER_1'
|
||||
const TEST_KEY_7_HELPER_2 = 'TEST_KEY_7_HELPER_2'
|
||||
const TEST_KEY_8 = 'TEST_KEY_8'
|
||||
const TEST_KEY_8_HELPER_1 = 'TEST_KEY_8_HELPER_1'
|
||||
const TEST_KEY_8_HELPER_2 = 'TEST_KEY_8_HELPER_2'
|
||||
|
||||
const TEST_SUBSTITUTION_1 = 'TEST_SUBSTITUTION_1'
|
||||
const TEST_SUBSTITUTION_2 = 'TEST_SUBSTITUTION_2'
|
||||
const TEST_SUBSTITUTION_3 = 'TEST_SUBSTITUTION_3'
|
||||
const TEST_SUBSTITUTION_4 = 'TEST_SUBSTITUTION_4'
|
||||
const TEST_SUBSTITUTION_5 = 'TEST_SUBSTITUTION_5'
|
||||
|
||||
const testLocaleMessages = {
|
||||
[TEST_KEY_1]: {
|
||||
message: 'This is a simple message.',
|
||||
expectedResult: 'This is a simple message.',
|
||||
},
|
||||
[TEST_KEY_2]: {
|
||||
message: 'This is a message with a single non-react substitution $1.',
|
||||
},
|
||||
[TEST_KEY_3]: {
|
||||
message: 'This is a message with two non-react substitutions $1 and $2.',
|
||||
},
|
||||
[TEST_KEY_4]: {
|
||||
message: '$1 - $2 - $3 - $4 - $5',
|
||||
},
|
||||
[TEST_KEY_5]: {
|
||||
message: '$1 - $2 - $3',
|
||||
},
|
||||
[TEST_KEY_6]: {
|
||||
'message': 'Testing a react substitution $1.',
|
||||
},
|
||||
[TEST_KEY_6_HELPER]: {
|
||||
'message': TEST_SUBSTITUTION_1,
|
||||
},
|
||||
[TEST_KEY_7]: {
|
||||
'message': 'Testing a react substitution $1 and another $2.',
|
||||
},
|
||||
[TEST_KEY_7_HELPER_1]: {
|
||||
'message': TEST_SUBSTITUTION_1,
|
||||
},
|
||||
[TEST_KEY_7_HELPER_2]: {
|
||||
'message': TEST_SUBSTITUTION_2,
|
||||
},
|
||||
[TEST_KEY_8]: {
|
||||
'message': 'Testing a mix $1 of react substitutions $2 and string substitutions $3 + $4.',
|
||||
},
|
||||
[TEST_KEY_8_HELPER_1]: {
|
||||
'message': TEST_SUBSTITUTION_3,
|
||||
},
|
||||
[TEST_KEY_8_HELPER_2]: {
|
||||
'message': TEST_SUBSTITUTION_4,
|
||||
},
|
||||
}
|
||||
const t = getMessage.bind(null, TEST_LOCALE_CODE, testLocaleMessages)
|
||||
|
||||
const TEST_SUBSTITUTION_6 = (
|
||||
<div
|
||||
style={{ color: 'red' }}
|
||||
key="test-react-substitutions-1"
|
||||
>
|
||||
{ t(TEST_KEY_6_HELPER) }
|
||||
</div>
|
||||
)
|
||||
const TEST_SUBSTITUTION_7_1 = (
|
||||
<div
|
||||
style={{ color: 'red' }}
|
||||
key="test-react-substitutions-1"
|
||||
>
|
||||
{ t(TEST_KEY_7_HELPER_1) }
|
||||
</div>
|
||||
)
|
||||
const TEST_SUBSTITUTION_7_2 = (
|
||||
<div
|
||||
style={{ color: 'blue' }}
|
||||
key="test-react-substitutions-1"
|
||||
>
|
||||
{ t(TEST_KEY_7_HELPER_2) }
|
||||
</div>
|
||||
)
|
||||
const TEST_SUBSTITUTION_8_1 = (
|
||||
<div
|
||||
style={{ color: 'orange' }}
|
||||
key="test-react-substitutions-1"
|
||||
>
|
||||
{ t(TEST_KEY_8_HELPER_1) }
|
||||
</div>
|
||||
)
|
||||
const TEST_SUBSTITUTION_8_2 = (
|
||||
<div
|
||||
style={{ color: 'pink' }}
|
||||
key="test-react-substitutions-1"
|
||||
>
|
||||
{ t(TEST_KEY_8_HELPER_2) }
|
||||
</div>
|
||||
)
|
||||
|
||||
describe('getMessage', function () {
|
||||
it('should return the exact message paired with key if there are no substitutions', function () {
|
||||
const result = t(TEST_KEY_1)
|
||||
assert.equal(result, 'This is a simple message.')
|
||||
})
|
||||
|
||||
it('should return the correct message when a single non-react substitution is made', function () {
|
||||
const result = t(TEST_KEY_2, [ TEST_SUBSTITUTION_1 ])
|
||||
assert.equal(result, `This is a message with a single non-react substitution ${TEST_SUBSTITUTION_1}.`)
|
||||
})
|
||||
|
||||
it('should return the correct message when two non-react substitutions are made', function () {
|
||||
const result = t(TEST_KEY_3, [ TEST_SUBSTITUTION_1, TEST_SUBSTITUTION_2 ])
|
||||
assert.equal(result, `This is a message with two non-react substitutions ${TEST_SUBSTITUTION_1} and ${TEST_SUBSTITUTION_2}.`)
|
||||
})
|
||||
|
||||
it('should return the correct message when multiple non-react substitutions are made', function () {
|
||||
const result = t(TEST_KEY_4, [ TEST_SUBSTITUTION_1, TEST_SUBSTITUTION_2, TEST_SUBSTITUTION_3, TEST_SUBSTITUTION_4, TEST_SUBSTITUTION_5 ])
|
||||
assert.equal(result, `${TEST_SUBSTITUTION_1} - ${TEST_SUBSTITUTION_2} - ${TEST_SUBSTITUTION_3} - ${TEST_SUBSTITUTION_4} - ${TEST_SUBSTITUTION_5}`)
|
||||
})
|
||||
|
||||
it('should throw an error when not passed as many substitutions as a message requires', function () {
|
||||
assert.throws(
|
||||
() => {
|
||||
t(TEST_KEY_5, [ TEST_SUBSTITUTION_1, TEST_SUBSTITUTION_2 ])
|
||||
},
|
||||
Error,
|
||||
`Insufficient number of substitutions for message: '$1 - $2 - $3'`
|
||||
)
|
||||
})
|
||||
|
||||
it('should return the correct message when a single react substitution is made', function () {
|
||||
const result = t(TEST_KEY_6, [ TEST_SUBSTITUTION_6 ])
|
||||
assert.equal(shallow(result).html(), '<span> Testing a react substitution <div style="color:red">TEST_SUBSTITUTION_1</div>. </span>')
|
||||
})
|
||||
|
||||
it('should return the correct message when two react substitutions are made', function () {
|
||||
const result = t(TEST_KEY_7, [ TEST_SUBSTITUTION_7_1, TEST_SUBSTITUTION_7_2 ])
|
||||
assert.equal(shallow(result).html(), '<span> Testing a react substitution <div style="color:red">TEST_SUBSTITUTION_1</div> and another <div style="color:blue">TEST_SUBSTITUTION_2</div>. </span>')
|
||||
})
|
||||
|
||||
it('should return the correct message when substituting a mix of react elements and strings', function () {
|
||||
const result = t(TEST_KEY_8, [ TEST_SUBSTITUTION_1, TEST_SUBSTITUTION_8_1, TEST_SUBSTITUTION_2, TEST_SUBSTITUTION_8_2 ])
|
||||
assert.equal(shallow(result).html(), '<span> Testing a mix TEST_SUBSTITUTION_1 of react substitutions <div style="color:orange">TEST_SUBSTITUTION_3</div> and string substitutions TEST_SUBSTITUTION_2 + <div style="color:pink">TEST_SUBSTITUTION_4</div>. </span>')
|
||||
})
|
||||
})
|
||||
})
|
@ -14,10 +14,11 @@ export default class MetaMetricsOptIn extends Component {
|
||||
|
||||
static contextTypes = {
|
||||
metricsEvent: PropTypes.func,
|
||||
t: PropTypes.func,
|
||||
}
|
||||
|
||||
render () {
|
||||
const { metricsEvent } = this.context
|
||||
const { metricsEvent, t } = this.context
|
||||
const {
|
||||
nextRoute,
|
||||
history,
|
||||
@ -142,14 +143,15 @@ export default class MetaMetricsOptIn extends Component {
|
||||
disabled={false}
|
||||
/>
|
||||
<div className="metametrics-opt-in__bottom-text">
|
||||
This data is aggregated and is therefore anonymous for the purposes of General Data Protection Regulation (EU) 2016/679. For more information in relation to our privacy practices, please see our
|
||||
<a
|
||||
href="https://metamask.io/privacy.html"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Privacy Policy here
|
||||
</a>.
|
||||
{ t('gdprMessage', [
|
||||
<a
|
||||
key="metametrics-bottom-text-wrapper"
|
||||
href="https://metamask.io/privacy.html"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>{ t('gdprMessagePrivacyPolicy') }
|
||||
</a> ])
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user