feat(i18n): translate server messages (#40626)

This commit is contained in:
Tom
2021-01-07 12:36:51 -06:00
committed by Mrugesh Mohapatra
parent 77022b8a56
commit 072a7ce6c6
11 changed files with 64 additions and 98 deletions

View File

@ -60,34 +60,6 @@ export function getFallbackFrontEndDate(completedChallenges, completedDate) {
return latestCertDate ? latestCertDate : completedDate; return latestCertDate ? latestCertDate : completedDate;
} }
const noNameMessage = dedent`
We need your name so we can put it on your certification.
Add your name to your account settings and click the save button.
Then we can issue your certification.
`;
const notCertifiedMessage = name => dedent`
It looks like you have not completed the necessary steps.
Please complete the required projects to claim the
${name} Certification
`;
const alreadyClaimedMessage = name => dedent`
It looks like you already have claimed the ${name} Certification
`;
const successMessage = (username, name) => dedent`
@${username}, you have successfully claimed
the ${name} Certification!
Congratulations on behalf of the freeCodeCamp.org team!
`;
const failureMessage = name => dedent`
Something went wrong with the verification of ${name}, please try again.
If you continue to receive this error, you can send a message to
support@freeCodeCamp.org to get help.
`;
function ifNoSuperBlock404(req, res, next) { function ifNoSuperBlock404(req, res, next) {
const { superBlock } = req.body; const { superBlock } = req.body;
if (superBlock && superBlocks.includes(superBlock)) { if (superBlock && superBlocks.includes(superBlock)) {
@ -309,19 +281,31 @@ function createVerifyCert(certTypeIds, app) {
.flatMap(challenge => { .flatMap(challenge => {
const certName = certText[certType]; const certName = certText[certType];
if (user[certType]) { if (user[certType]) {
return Observable.just(alreadyClaimedMessage(certName)); return Observable.just({
type: 'info',
message: 'flash.msg-27',
variables: { name: certName }
});
} }
// certificate doesn't exist or // certificate doesn't exist or
// connection error // connection error
if (!challenge) { if (!challenge) {
reportError(`Error claiming ${certName}`); reportError(`Error claiming ${certName}`);
return Observable.just(failureMessage(certName)); return Observable.just({
type: 'danger',
message: 'flash.msg-29',
variables: { name: certName }
});
} }
const { id, tests, challengeType } = challenge; const { id, tests, challengeType } = challenge;
if (!canClaim(tests, user.completedChallenges)) { if (!canClaim(tests, user.completedChallenges)) {
return Observable.just(notCertifiedMessage(certName)); return Observable.just({
type: 'info',
message: 'flash.msg-26',
variables: { name: certName }
});
} }
const updateData = { const updateData = {
@ -337,7 +321,10 @@ function createVerifyCert(certTypeIds, app) {
}; };
if (!user.name) { if (!user.name) {
return Observable.just(noNameMessage); return Observable.just({
type: 'info',
message: 'flash.msg-25'
});
} }
// set here so sendCertifiedEmail works properly // set here so sendCertifiedEmail works properly
// not used otherwise // not used otherwise
@ -362,15 +349,19 @@ function createVerifyCert(certTypeIds, app) {
log(pledgeOrMessage); log(pledgeOrMessage);
} }
log('Certificates updated'); log('Certificates updated');
return successMessage(user.username, certName); return {
type: 'success',
message: 'flash.msg-28',
variables: {
username: user.username,
name: certName
}
};
}); });
}) })
.subscribe(message => { .subscribe(message => {
return res.status(200).json({ return res.status(200).json({
response: { response: message,
type: message.includes('Congratulations') ? 'success' : 'info',
message
},
isCertMap: getUserIsCertMap(user), isCertMap: getUserIsCertMap(user),
// send back the completed challenges // send back the completed challenges
// NOTE: we could just send back the latest challenge, but this // NOTE: we could just send back the latest challenge, but this
@ -426,8 +417,8 @@ function createShowCert(app) {
messages: [ messages: [
{ {
type: 'info', type: 'info',
message: message: 'flash.msg-31',
'We could not find a user with the username "' + username + '"' variables: { username: username }
} }
] ]
}); });
@ -439,10 +430,7 @@ function createShowCert(app) {
messages: [ messages: [
{ {
type: 'info', type: 'info',
message: dedent` message: 'flash.msg-32'
This user needs to add their name to their account
in order for others to be able to view their certification.
`
} }
] ]
}); });
@ -453,9 +441,7 @@ function createShowCert(app) {
messages: [ messages: [
{ {
type: 'info', type: 'info',
message: message: 'flash.msg-33'
'This user is not eligible for freeCodeCamp.org ' +
'certifications at this time'
} }
] ]
}); });
@ -466,11 +452,8 @@ function createShowCert(app) {
messages: [ messages: [
{ {
type: 'info', type: 'info',
message: dedent` message: 'flash.msg-34',
${username} has chosen to make their portfolio variables: { username: username }
private. They will need to make their portfolio public
in order for others to be able to view their certification.
`
} }
] ]
}); });
@ -481,11 +464,8 @@ function createShowCert(app) {
messages: [ messages: [
{ {
type: 'info', type: 'info',
message: dedent` message: 'flash.msg-35',
${username} has chosen to make their certifications variables: { username: username }
private. They will need to make their certifications public
in order for others to be able to view them.
`
} }
] ]
}); });
@ -496,9 +476,8 @@ function createShowCert(app) {
messages: [ messages: [
{ {
type: 'info', type: 'info',
message: dedent` message: 'flash.msg-36',
${username} has not yet agreed to our Academic Honesty Pledge. variables: { username: username }
`
} }
] ]
}); });
@ -545,9 +524,8 @@ function createShowCert(app) {
messages: [ messages: [
{ {
type: 'info', type: 'info',
message: ` message: 'flash.msg-37',
It looks like user ${username} is not ${certText[certType]} certified variables: { username: username, cert: certText[certType] }
`
} }
] ]
}); });

View File

@ -53,13 +53,12 @@ export default function settingsController(app) {
const standardErrorMessage = { const standardErrorMessage = {
type: 'danger', type: 'danger',
message: message: 'flash.msg-9'
'Something went wrong updating your account. Please check and try again'
}; };
const standardSuccessMessage = { const standardSuccessMessage = {
type: 'success', type: 'success',
message: 'We have updated your preferences' message: 'flash.msg-10'
}; };
const createStandardHandler = (req, res, next) => err => { const createStandardHandler = (req, res, next) => err => {
@ -196,7 +195,7 @@ function createUpdateMyUsername(app) {
if (username === user.username) { if (username === user.username) {
return res.json({ return res.json({
type: 'info', type: 'info',
message: 'Username is already associated with this account' message: 'flash.msg-16'
}); });
} }
const validation = isValidUsername(username); const validation = isValidUsername(username);
@ -213,7 +212,7 @@ function createUpdateMyUsername(app) {
if (exists) { if (exists) {
return res.json({ return res.json({
type: 'info', type: 'info',
message: 'Username is already associated with a different account' message: 'flash.msg-17'
}); });
} }
@ -222,9 +221,11 @@ function createUpdateMyUsername(app) {
res.status(500).json(standardErrorMessage); res.status(500).json(standardErrorMessage);
return next(err); return next(err);
} }
return res.status(200).json({ return res.status(200).json({
type: 'success', type: 'success',
message: `We have updated your username to ${username}` message: `flash.msg-18`,
variables: { username: username }
}); });
}); });
}; };
@ -244,10 +245,7 @@ const updatePrivacyTerms = (req, res, next) => {
res.status(500).json(standardErrorMessage); res.status(500).json(standardErrorMessage);
return next(err); return next(err);
} }
return res.status(200).json({ return res.status(200).json(standardSuccessMessage);
type: 'success',
message: `We have updated your preferences.`
});
}); });
}; };

View File

@ -216,7 +216,7 @@ function createPostReportUserProfile(app) {
if (!username || !report || report === '') { if (!username || !report || report === '') {
return res.json({ return res.json({
type: 'danger', type: 'danger',
message: 'Check if you have provided a username and a report' message: 'flash.msg-44'
}); });
} }
return Email.send$( return Email.send$(
@ -246,8 +246,9 @@ function createPostReportUserProfile(app) {
} }
return res.json({ return res.json({
typer: 'info', type: 'info',
message: `A report was sent to the team with ${user.email} in copy.` message: 'flash.msg-45',
variables: { email: user.email }
}); });
} }
); );

View File

@ -451,7 +451,7 @@
"msg-33": "This user is not eligible for freeCodeCamp.org certifications at this time.", "msg-33": "This user is not eligible for freeCodeCamp.org certifications at this time.",
"msg-34": "{{username}} has chosen to make their portfolio private. They will need to make their portfolio public in order for others to be able to view their certification.", "msg-34": "{{username}} has chosen to make their portfolio private. They will need to make their portfolio public in order for others to be able to view their certification.",
"msg-35": "{{username}} has chosen to make their certifications private. They will need to make their certifications public in order for others to be able to view them.", "msg-35": "{{username}} has chosen to make their certifications private. They will need to make their certifications public in order for others to be able to view them.",
"msg-36": "{{username}} has not yet agrees to our Academic Honesty Pledge.", "msg-36": "{{username}} has not yet agreed to our Academic Honesty Pledge.",
"msg-37": "It looks like user {{username}} is not {{cert}} certified", "msg-37": "It looks like user {{username}} is not {{cert}} certified",
"msg-38": "That does not appear to be a valid challenge submission", "msg-38": "That does not appear to be a valid challenge submission",
"msg-39": "You have not provided the valid links for us to inspect your work.", "msg-39": "You have not provided the valid links for us to inspect your work.",

View File

@ -7,10 +7,7 @@ import { useTranslation } from 'react-i18next';
import './flash.css'; import './flash.css';
function Flash({ flashMessage, onClose }) { function Flash({ flashMessage, onClose }) {
// flash messages coming from the server are already translated const { type, message, id, variables = {} } = flashMessage;
// messages on the client get translated here and need a
// needsTranslating variable set to true with the object
const { type, message, id, needsTranslating = false } = flashMessage;
const { t } = useTranslation(); const { t } = useTranslation();
const [flashMessageHeight, setFlashMessageHeight] = useState(null); const [flashMessageHeight, setFlashMessageHeight] = useState(null);
@ -38,7 +35,7 @@ function Flash({ flashMessage, onClose }) {
className='flash-message' className='flash-message'
onDismiss={handleClose} onDismiss={handleClose}
> >
{needsTranslating ? t(`${message}`) : message} {t(message, variables)}
</Alert> </Alert>
</CSSTransition> </CSSTransition>
</TransitionGroup> </TransitionGroup>
@ -59,7 +56,7 @@ Flash.propTypes = {
id: PropTypes.string, id: PropTypes.string,
type: PropTypes.string, type: PropTypes.string,
message: PropTypes.string, message: PropTypes.string,
needsTranslating: PropTypes.bool variables: PropTypes.object
}), }),
onClose: PropTypes.func.isRequired onClose: PropTypes.func.isRequired
}; };

View File

@ -140,8 +140,7 @@ const isCertMapSelector = createSelector(
const honestyInfoMessage = { const honestyInfoMessage = {
type: 'info', type: 'info',
message: 'flash.msg-1', message: 'flash.msg-1'
needsTranslating: true
}; };
const initialState = { const initialState = {

View File

@ -12,8 +12,7 @@ function* deleteAccountSaga() {
yield put( yield put(
createFlashMessage({ createFlashMessage({
type: 'info', type: 'info',
message: 'flash.msg-5', message: 'flash.msg-5'
needsTranslating: true
}) })
); );
// remove current user information from application state // remove current user information from application state
@ -30,8 +29,7 @@ function* resetProgressSaga() {
yield put( yield put(
createFlashMessage({ createFlashMessage({
type: 'info', type: 'info',
message: 'flash.msg-6', message: 'flash.msg-6'
needsTranslating: true
}) })
); );
// refresh current user data in application state // refresh current user data in application state

View File

@ -33,16 +33,14 @@ export function handle400Error(e, options = { redirectTo: '/' }) {
return { return {
...flash, ...flash,
type: 'warn', type: 'warn',
message: 'flash.msg-7', message: 'flash.msg-7'
needsTranslating: true
}; };
} }
case 404: { case 404: {
return { return {
...flash, ...flash,
type: 'info', type: 'info',
message: 'flash.msg-8', message: 'flash.msg-8'
needsTranslating: true
}; };
} }
default: { default: {

View File

@ -1,5 +1,4 @@
export default { export default {
type: 'danger', type: 'danger',
message: 'flash.msg-2', message: 'flash.msg-2'
needsTranslating: true
}; };

View File

@ -1,5 +1,4 @@
export default { export default {
type: 'danger', type: 'danger',
message: 'flash.msg-3', message: 'flash.msg-3'
needsTranslating: true
}; };

View File

@ -1,5 +1,4 @@
export default { export default {
type: 'danger', type: 'danger',
message: 'flash.msg-4', message: 'flash.msg-4'
needsTranslating: true
}; };