Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LF-4400 Add new languages to i18n framework #3409

Merged
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 4 additions & 0 deletions packages/api/dev.export.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ cp ../webapp/public/locales/en/crop.json src/jobs/locales/en
cp ../webapp/public/locales/es/crop.json src/jobs/locales/es
cp ../webapp/public/locales/pt/crop.json src/jobs/locales/pt
cp ../webapp/public/locales/fr/crop.json src/jobs/locales/fr
cp ../webapp/public/locales/fr/crop.json src/jobs/locales/de
Duncan-Brain marked this conversation as resolved.
Show resolved Hide resolved
cp ../webapp/public/locales/fr/crop.json src/jobs/locales/hi
cp ../webapp/public/locales/fr/crop.json src/jobs/locales/pa
cp ../webapp/public/locales/fr/crop.json src/jobs/locales/ml

# Give nodemon time to restart the API
sleep 10
Expand Down
2 changes: 1 addition & 1 deletion packages/api/src/jobs/locales/i18n.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Backend from 'i18next-fs-backend';
i18n.use(Backend).init(
{
fallbackLng: 'en',
preload: ['en', 'es', 'pt', 'fr'],
preload: ['en', 'es', 'pt', 'fr', 'de', 'hi', 'pa', 'ml'],
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we have an array of languages somewhere on the api side like you did on webapp, so that we don't have to alter that many files next time we add a new language?

ns: ['translation', 'crop'],
defaultNS: 'translation',
nsSeparator: ':',
Expand Down
2 changes: 1 addition & 1 deletion packages/api/src/jobs/locales/i18next-parser.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ export default {
output: 'src/jobs/locales/$LOCALE/$NAMESPACE.json',
sort: true,
defaultValue: 'MISSING',
locales: ['en', 'es', 'pt', 'fr'],
locales: ['en', 'es', 'pt', 'fr', 'de', 'hi', 'pa', 'ml'],
};
2 changes: 1 addition & 1 deletion packages/api/src/templates/sendEmailTemplate.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ const emailTransporter = new EmailTemplates({
root: path.join(dir, 'emails'),
},
i18n: {
locales: ['en', 'es', 'fr', 'pt'],
locales: ['en', 'es', 'fr', 'pt', 'de', 'hi', 'pa', 'ml'],
directory: path.join(dir, 'locales'),
objectNotation: true,
},
Expand Down
4 changes: 4 additions & 0 deletions packages/webapp/.storybook/preview.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ export const globalTypes = {
{ value: 'es', title: 'Spanish' },
{ value: 'fr', title: 'French' },
{ value: 'pt', title: 'Portuguese' },
{ value: 'de', title: 'German' },
{ value: 'hi', title: 'Hindi' },
{ value: 'pa', title: 'Punjabi' },
{ value: 'ml', title: 'Malayalam' },
],
showName: true,
},
Expand Down
8 changes: 4 additions & 4 deletions packages/webapp/public/locales/de/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -323,8 +323,8 @@
"BIRTH_YEAR_ERROR": "Das Geburtsjahr muss zwischen 1900 und",
"BIRTH_YEAR_TOOLTIP": "Altersangaben werden nur zu Forschungszwecken gesammelt und nur weitergegeben, wenn die persönlichen Daten entfernt wurden.",
"CREATE_BUTTON": "Konto erstellen",
"DEFAULT_LANGUAGE": "Englisch",
"DEFAULT_LANGUAGE_VALUE": "en",
"DEFAULT_LANGUAGE": "Deutsch",
"DEFAULT_LANGUAGE_VALUE": "de",
"EMAIL": "E-Mail",
"FULL_NAME": "Vollständiger Name",
"GENDER": "Geschlecht",
Expand Down Expand Up @@ -1020,8 +1020,8 @@
"BIRTH_YEAR_ERROR": "Das Geburtsjahr muss zwischen 1900 und",
"BIRTH_YEAR_TOOLTIP": "Altersangaben werden nur zu Forschungszwecken gesammelt und nur weitergegeben, wenn personenbezogene Daten entfernt wurden",
"CHOOSE_ROLE": "Rolle wählen",
"DEFAULT_LANGUAGE": "Englisch",
"DEFAULT_LANGUAGE_VALUE": "en",
"DEFAULT_LANGUAGE": "Deutsch",
"DEFAULT_LANGUAGE_VALUE": "de",
"EMAIL": "E-Mail",
"EMAIL_INFO": "Benutzer ohne E-Mail können sich nicht anmelden",
"FULL_NAME": "Vollständiger Name",
Expand Down
8 changes: 4 additions & 4 deletions packages/webapp/public/locales/hi/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -323,8 +323,8 @@
"BIRTH_YEAR_ERROR": "जन्म वर्ष १९०० और बीच में होना चाहिए",
"BIRTH_YEAR_TOOLTIP": "आयु संबंधित जानकारी केवल अनुसंधान उद्देश्यों के लिए एकत्र की जाती है और व्यक्तिगत पहचान सूचना के बिना ही साझा की जाएगी",
"CREATE_BUTTON": "खाता बनाएं",
"DEFAULT_LANGUAGE": "अंग्रेज़ी",
"DEFAULT_LANGUAGE_VALUE": "अंग्रेज़ी",
"DEFAULT_LANGUAGE": "हिंदी",
"DEFAULT_LANGUAGE_VALUE": "hi",
"EMAIL": "ईमेल",
"FULL_NAME": "पूरा नाम",
"GENDER": "लिंग",
Expand Down Expand Up @@ -1020,8 +1020,8 @@
"BIRTH_YEAR_ERROR": "जन्म वर्ष 1900 से बीच होना चाहिए",
"BIRTH_YEAR_TOOLTIP": "आयु संबंधी जानकारी केवल अनुसंधान प्रयोजनों के लिए एकत्रित की जाती है और व्यक्तिगत पहचान सूचना हटाने के साथ ही साझा की जाएगी",
"CHOOSE_ROLE": "भूमिका चुनें",
"DEFAULT_LANGUAGE": "अंग्रेजी",
"DEFAULT_LANGUAGE_VALUE": "अंग्रेजी",
"DEFAULT_LANGUAGE": "हिंदी",
"DEFAULT_LANGUAGE_VALUE": "hi",
"EMAIL": "ईमेल",
"EMAIL_INFO": "ईमेल के बिना उपयोगकर्ता लॉग इन नहीं कर पाएंगे",
"FULL_NAME": "पूरा नाम",
Expand Down
8 changes: 4 additions & 4 deletions packages/webapp/public/locales/pa/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -323,8 +323,8 @@
"BIRTH_YEAR_ERROR": "ਜਨਮ ਸਾਲ 1900 ਅਤੇ ਵਿਚਕਾਰ ਹੋਣਾ ਚਾਹੀਦਾ ਹੈ",
"BIRTH_YEAR_TOOLTIP": "ਉਮਰ ਦੀ ਜਾਣਕਾਰੀ ਸਿਰਫ਼ ਖੋਜ ਦੇ ਉਦੇਸ਼ਾਂ ਲਈ ਇਕੱਠੀ ਕੀਤੀ ਜਾਂਦੀ ਹੈ ਅਤੇ ਸਿਰਫ਼ ਨਿੱਜੀ ਤੌਰ 'ਤੇ ਪਛਾਣ ਕਰਨ ਵਾਲੀ ਜਾਣਕਾਰੀ ਨਾਲ ਹੀ ਸਾਂਝੀ ਕੀਤੀ ਜਾਵੇਗੀ",
"CREATE_BUTTON": "ਖਾਤਾ ਬਣਾਓ",
"DEFAULT_LANGUAGE": "ਅੰਗਰੇਜ਼ੀ",
"DEFAULT_LANGUAGE_VALUE": "ਅੰਗਰੇਜ਼ੀ",
"DEFAULT_LANGUAGE": "ਪੰਜਾਬੀ",
"DEFAULT_LANGUAGE_VALUE": "pa",
"EMAIL": "ਈਮੇਲ",
"FULL_NAME": "ਪੂਰਾ ਨਾਂਮ",
"GENDER": "ਲਿੰਗ",
Expand Down Expand Up @@ -1020,8 +1020,8 @@
"BIRTH_YEAR_ERROR": "ਜਨਮ ਸਾਲ 1900 ਅਤੇ ਵਿਚਕਾਰ ਹੋਣਾ ਚਾਹੀਦਾ ਹੈ",
"BIRTH_YEAR_TOOLTIP": "ਉਮਰ ਦੀ ਜਾਣਕਾਰੀ ਸਿਰਫ਼ ਖੋਜ ਦੇ ਉਦੇਸ਼ਾਂ ਲਈ ਇਕੱਠੀ ਕੀਤੀ ਜਾਂਦੀ ਹੈ ਅਤੇ ਸਿਰਫ਼ ਨਿੱਜੀ ਤੌਰ 'ਤੇ ਪਛਾਣ ਕਰਨ ਵਾਲੀ ਜਾਣਕਾਰੀ ਨਾਲ ਹੀ ਸਾਂਝੀ ਕੀਤੀ ਜਾਵੇਗੀ",
"CHOOSE_ROLE": "ਰੋਲ ਚੁਣੋ",
"DEFAULT_LANGUAGE": "ਅੰਗਰੇਜ਼ੀ",
"DEFAULT_LANGUAGE_VALUE": "ਅੰਗਰੇਜ਼ੀ",
"DEFAULT_LANGUAGE": "ਪੰਜਾਬੀ",
"DEFAULT_LANGUAGE_VALUE": "pa",
"EMAIL": "ਈਮੇਲ",
"EMAIL_INFO": "ਈਮੇਲ ਤੋਂ ਬਿਨਾਂ ਉਪਭੋਗਤਾ ਲੌਗਇਨ ਕਰਨ ਦੇ ਯੋਗ ਨਹੀਂ ਹੋਣਗੇ",
"FULL_NAME": "ਪੂਰਾ ਨਾਂਮ",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ const getDate = (date, language = 'en') => {
return new Intl.DateTimeFormat(language, { dateStyle: 'medium' }).format(new Date(date));
};

import useLanguageOptions, { languageCodes } from '../../../hooks/useLanguageOptions';

export const PureTaskCard = ({
taskType,
status,
Expand Down Expand Up @@ -173,5 +175,5 @@ PureTaskCard.propTypes = {
onClickAssignee: PropTypes.func,
onClickCompleteOrDueDate: PropTypes.func,
selected: PropTypes.bool,
language: PropTypes.oneOf(['en', 'es', 'fr', 'pt']),
language: PropTypes.oneOf(languageCodes),
};
9 changes: 2 additions & 7 deletions packages/webapp/src/components/CreateUserAccount/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import ReactSelect from '../Form/ReactSelect';
import { useTranslation } from 'react-i18next';
import i18n from '../../locales/i18n';
import useGenderOptions from '../../hooks/useGenderOptions';
import useLanguageOptions from '../../hooks/useLanguageOptions';

export default function PureCreateUserAccount({ onSignUp, email, onGoBack, isNotSSO }) {
const {
Expand Down Expand Up @@ -41,13 +42,7 @@ export default function PureCreateUserAccount({ onSignUp, email, onGoBack, isNot
} = validatePasswordWithErrors(password);

const genderOptions = useGenderOptions();

const languageOptions = [
{ value: 'en', label: t('PROFILE.ACCOUNT.ENGLISH') },
{ value: 'es', label: t('PROFILE.ACCOUNT.SPANISH') },
{ value: 'pt', label: t('PROFILE.ACCOUNT.PORTUGUESE') },
{ value: 'fr', label: t('PROFILE.ACCOUNT.FRENCH') },
];
const languageOptions = useLanguageOptions();

const getLanguageOption = (language) => {
return languageOptions.findIndex((object) => object.value === language);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { getNewDate } from '../Form/InputDuration/utils';
import 'rc-year-calendar/locales/rc-year-calendar.es';
import 'rc-year-calendar/locales/rc-year-calendar.pt';
import 'rc-year-calendar/locales/rc-year-calendar.fr';
import 'rc-year-calendar/locales/rc-year-calendar.de';
Copy link
Collaborator

@Duncan-Brain Duncan-Brain Sep 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we need a PR to either find a new Calendar package or add these files to our locales then copy them to node_modules to augment package. Thanks for doing that!


function FullYearCalendarView({
seed_date,
Expand Down
9 changes: 3 additions & 6 deletions packages/webapp/src/components/InviteUser/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import ReactSelect from '../Form/ReactSelect';
import { useTranslation } from 'react-i18next';
import { getFirstNameLastName } from '../../util';
import useGenderOptions from '../../hooks/useGenderOptions';
import useLanguageOptions from '../../hooks/useLanguageOptions';

export default function PureInviteUser({ onInvite, onGoBack, userFarmEmails, roleOptions = [] }) {
const {
Expand Down Expand Up @@ -41,13 +42,9 @@ export default function PureInviteUser({ onInvite, onGoBack, userFarmEmails, rol
}, [selectedRoleId]);
const { t } = useTranslation(['translation', 'common', 'gender']);
const title = t('INVITE_USER.TITLE');

const genderOptions = useGenderOptions();
const languageOptions = [
{ value: 'en', label: t('PROFILE.ACCOUNT.ENGLISH') },
{ value: 'es', label: t('PROFILE.ACCOUNT.SPANISH') },
{ value: 'pt', label: t('PROFILE.ACCOUNT.PORTUGUESE') },
{ value: 'fr', label: t('PROFILE.ACCOUNT.FRENCH') },
];
const languageOptions = useLanguageOptions();

const disabled = !isValid || !isDirty;
const onSubmit = (data) => {
Expand Down
9 changes: 2 additions & 7 deletions packages/webapp/src/components/Profile/Account/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,10 @@ import Button from '../../Form/Button';
import PropTypes from 'prop-types';
import ProfileLayout from '../ProfileLayout';
import useGenderOptions from '../../../hooks/useGenderOptions';
import useLanguageOptionsMap from '../../../hooks/useLanguageOptions';

const useLanguageOptions = (language_preference) => {
const { t } = useTranslation();
const languageOptionMap = {
en: { label: t('PROFILE.ACCOUNT.ENGLISH'), value: 'en' },
es: { label: t('PROFILE.ACCOUNT.SPANISH'), value: 'es' },
pt: { label: t('PROFILE.ACCOUNT.PORTUGUESE'), value: 'pt' },
fr: { label: t('PROFILE.ACCOUNT.FRENCH'), value: 'fr' },
};
const languageOptionMap = useLanguageOptionsMap();
const languageOptions = Object.values(languageOptionMap);
const languagePreferenceOptionRef = useRef();
languagePreferenceOptionRef.current =
Expand Down
9 changes: 3 additions & 6 deletions packages/webapp/src/components/Profile/EditUser/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import Checkbox from '../../Form/Checkbox';
import { useSelector } from 'react-redux';
import { userFarmsByFarmSelector } from '../../../containers/userFarmSlice';
import useGenderOptions from '../../../hooks/useGenderOptions';
import useLanguageOptions from '../../../hooks/useLanguageOptions';

export default function PureEditUser({
userFarm,
Expand Down Expand Up @@ -43,12 +44,8 @@ export default function PureEditUser({
const adminRoles = [1, 2, 5];

const genderOptions = useGenderOptions();
const languageOptions = [
{ value: 'en', label: t('PROFILE.ACCOUNT.ENGLISH') },
{ value: 'es', label: t('PROFILE.ACCOUNT.SPANISH') },
{ value: 'pt', label: t('PROFILE.ACCOUNT.PORTUGUESE') },
{ value: 'fr', label: t('PROFILE.ACCOUNT.FRENCH') },
];
const languageOptions = useLanguageOptions();

const isPseudoUser = userFarm.role_id === 4;
const roleOptions = Object.keys(dropDownMap).map((role_id) => ({
value: role_id,
Expand Down
12 changes: 12 additions & 0 deletions packages/webapp/src/containers/Consent/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ import PortugueseOwnerConsent from './locales/pt/Owner.Consent.md';
import PortugueseWorkerConsent from './locales/pt/Worker.Consent.md';
import SpanishOwnerConsent from './locales/es/Owner.Consent.md';
import SpanishWorkerConsent from './locales/es/Worker.Consent.md';
import GermanOwnerConsent from './locales/de/Owner.Consent.md';
import GermanWorkerConsent from './locales/de/Worker.Consent.md';
import HindiOwnerConsent from './locales/hi/Owner.Consent.md';
import HindiWorkerConsent from './locales/hi/Worker.Consent.md';
import PunjabiOwnerConsent from './locales/pa/Owner.Consent.md';
import PunjabiWorkerConsent from './locales/pa/Worker.Consent.md';
import MalayalamOwnerConsent from './locales/ml/Owner.Consent.md';
import MalayalamWorkerConsent from './locales/ml/Worker.Consent.md';
import { getLanguageFromLocalStorage } from '../../util/getLanguageFromLocalStorage';
import { CONSENT_VERSION } from '../../util/constants';

Expand All @@ -21,6 +29,10 @@ const languageConsent = {
fr: { worker: <FrenchWorkerConsent />, owner: <FrenchOwnerConsent /> },
es: { worker: <SpanishWorkerConsent />, owner: <SpanishOwnerConsent /> },
pt: { worker: <PortugueseWorkerConsent />, owner: <PortugueseOwnerConsent /> },
de: { worker: <GermanWorkerConsent />, owner: <GermanOwnerConsent /> },
hi: { worker: <HindiWorkerConsent />, owner: <HindiOwnerConsent /> },
pa: { worker: <PunjabiWorkerConsent />, owner: <PunjabiOwnerConsent /> },
ml: { worker: <MalayalamWorkerConsent />, owner: <MalayalamOwnerConsent /> },
};

const getLanguageConsent = (language) => languageConsent[language] || languageConsent.en;
Expand Down
40 changes: 40 additions & 0 deletions packages/webapp/src/hooks/useLanguageOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright 2024 LiteFarm.org
* This file is part of LiteFarm.
*
* LiteFarm is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LiteFarm is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details, see <https://www.gnu.org/licenses/>.
*/
import { useTranslation } from 'react-i18next';

// TODO: LF-4056
const supportedLanguages = [
['en', 'PROFILE.ACCOUNT.ENGLISH'],
['de', 'PROFILE.ACCOUNT.GERMAN'],
['es', 'PROFILE.ACCOUNT.SPANISH'],
['pt', 'PROFILE.ACCOUNT.PORTUGUESE'],
['fr', 'PROFILE.ACCOUNT.FRENCH'],
['hi', 'PROFILE.ACCOUNT.HINDI'],
['pa', 'PROFILE.ACCOUNT.PUNJABI'],
['ml', 'PROFILE.ACCOUNT.MALAYALAM'],
];

const useLanguageOptions = () => {
const { t } = useTranslation();

return supportedLanguages.map(([value, text]) => ({
value,
label: t(text),
}));
};

export const languageCodes = supportedLanguages.map(([code]) => code);

export default useLanguageOptions;
2 changes: 1 addition & 1 deletion packages/webapp/src/locales/i18n.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ i18n
defaultNS: 'translation',
nsSeparator: ':',
fallbackLng: 'en',
locales: ['en', 'pt', 'es', 'fr'],
locales: ['en', 'pt', 'es', 'fr', 'de', 'hi', 'pa', 'ml'],
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we get the array of languages here and in the i18next-parser file from the supportedLanguages array? I love how you minimized having to have a list of the languages in a thousand places and thinking if we could extend that further.

debug: false,
detection: {
order: ['localStorage', 'navigator', 'querystring'],
Expand Down
2 changes: 1 addition & 1 deletion packages/webapp/src/locales/i18next-parser.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ module.exports = {
output: 'public/locales/$LOCALE/$NAMESPACE.json',
sort: true,
defaultValue: 'MISSING',
locales: ['en', 'es', 'pt', 'fr'],
locales: ['en', 'es', 'pt', 'fr', 'de', 'hi', 'pa', 'ml'],
};
Loading