all repos — caroster @ e22ee9c064d006eb9bd3af3cc9709ce4d28df633

[Octree] Group carpool to your event https://caroster.io

refactor: 🌐 Improve internationalization architecture 
Simon Mulquin simon@octree.ch
Mon, 26 Sep 2022 06:41:35 +0000
commit

e22ee9c064d006eb9bd3af3cc9709ce4d28df633

parent

068c5d4e57574e59dfa4cf72bf56584a62f46aa8

M backend/.strapi-updater.jsonbackend/.strapi-updater.json

@@ -1,5 +1,5 @@

{ - "latest": "4.3.8", - "lastUpdateCheck": 1663575220337, + "latest": "4.3.9", + "lastUpdateCheck": 1663848719748, "lastNotification": 1663340063046 }
M backend/src/extensions/users-permissions/content-types/user/schema.jsonbackend/src/extensions/users-permissions/content-types/user/schema.json

@@ -100,8 +100,8 @@ "default": false

}, "lang": { "type": "enumeration", - "enum": ["FR", "EN"], - "default": "FR" + "enum": ["fr", "en", "FR", "EN"], + "default": "fr" } } }
A backend/src/migrations/user-locales.js

@@ -0,0 +1,36 @@

+const Strapi = require("@strapi/strapi"); + +const main = async () => { + const appContext = await Strapi.compile(); + await Strapi(appContext).load(); + + const users = await strapi.entityService.findMany( + "plugin::users-permissions.user", + {} + ); + + for (let i = 0; i < users.length; i++) { + try { + await updateLanguage(users[i]); + } catch (error) { + console.error(error); + } + } + + strapi.log.debug("Done."); + process.exit(0); +}; + +const updateLanguage = async (user) => { + const lang = { EN: "en", FR: "fr" }[user.lang] || user.lang; + + return strapi.entityService.update( + "plugin::users-permissions.user", + user.id, + { + data: { ...user, lang }, + } + ); +}; + +main();
M frontend/codegen.ymlfrontend/codegen.yml

@@ -7,3 +7,6 @@ plugins:

- "typescript" - "typescript-operations" - "typescript-react-apollo" + config: + namingConvention: + enumValues: keep
M frontend/components/Logo/index.jsfrontend/components/Logo/index.js

@@ -1,3 +1,4 @@

+import Link from 'next/link'; import {makeStyles} from '@material-ui/core/styles'; import useProfile from '../../hooks/useProfile'; import useSettings from '../../hooks/useSettings';

@@ -9,13 +10,13 @@ const settings = useSettings();

const appLink = connected ? '/dashboard' : settings?.['about_link'] || ''; return ( <div className={classes.layout}> - <a href={appLink} className={classes.link}> + <Link href={appLink} className={classes.link}> <img src={'/assets/Caroster_beta.png'} alt="Caroster" className={classes.logo} /> - </a> + </Link> </div> ); };
M frontend/containers/CreateEvent/Step1.tsxfrontend/containers/CreateEvent/Step1.tsx

@@ -5,11 +5,12 @@ import TextField from '@material-ui/core/TextField';

import Button from '@material-ui/core/Button'; import Checkbox from '@material-ui/core/Checkbox'; import FormControlLabel from '@material-ui/core/FormControlLabel'; +import NextLink from 'next/link' import {useTranslation} from 'react-i18next'; +import {CardActions} from '@material-ui/core'; +import {useSession} from 'next-auth/react'; import useDebounce from '../../hooks/useDebounce'; -import {CardActions} from '@material-ui/core'; import {isValidEmail} from '../../lib/formValidation'; -import {useSession} from 'next-auth/react'; const Step1 = ({nextStep, event, addToEvent}) => { const {t} = useTranslation();

@@ -105,12 +106,16 @@ <Typography variant="body2">

{t('event.creation.addFromAccount.subtitle')} </Typography> <CardActions className={classes.actions}> - <Button variant="text" href="/auth/register"> - {t('event.creation.addFromAccount.actions.register')} - </Button> - <Button color="primary" href="/auth/login"> - {t('event.creation.addFromAccount.actions.login')} - </Button> + <NextLink href="/auth/register" passHref> + <Button variant="text"> + {t('event.creation.addFromAccount.actions.register')} + </Button> + </NextLink> + <NextLink href="/auth/login" passHref> + <Button color="primary"> + {t('event.creation.addFromAccount.actions.login')} + </Button> + </NextLink> </CardActions> </div> )}
M frontend/containers/Languages/Icon.tsxfrontend/containers/Languages/Icon.tsx

@@ -5,7 +5,7 @@ import Icon from '@material-ui/core/Icon';

import Menu from '@material-ui/core/Menu'; import MenuItem from '@material-ui/core/MenuItem'; import {useTranslation} from 'react-i18next'; -import {Enum_Userspermissionsuser_Lang as Lang} from '../../generated/graphql'; +import {Enum_Userspermissionsuser_Lang as SupportedLocales} from '../../generated/graphql'; import withLanguagesSelection, { LanguageSelectionComponentProps, } from './withLanguagesSelection';

@@ -22,7 +22,7 @@ const handleClick = event => {

setAnchorEl(event.currentTarget); }; - const onConfirm = (lang: Lang) => { + const onConfirm = (lang: SupportedLocales) => { setAnchorEl(null); onChangeLang(lang); };

@@ -52,12 +52,12 @@ open={Boolean(anchorEl)}

onClose={() => setAnchorEl(null)} > <MenuItem - disabled={language === Lang.Fr} - onClick={() => onConfirm(Lang.Fr)} + disabled={language === SupportedLocales['fr']} + onClick={() => onConfirm(SupportedLocales['fr'])} >{t`languages.fr`}</MenuItem> <MenuItem - disabled={language === Lang.En} - onClick={() => onConfirm(Lang.En)} + disabled={language === SupportedLocales['en']} + onClick={() => onConfirm(SupportedLocales['en'])} >{t`languages.en`}</MenuItem> </Menu> </>
M frontend/containers/Languages/MenuItem.tsxfrontend/containers/Languages/MenuItem.tsx

@@ -3,7 +3,7 @@ import MenuList from '@material-ui/core/MenuList';

import MenuItem from '@material-ui/core/MenuItem'; import {makeStyles} from '@material-ui/core/styles'; import {useTranslation} from 'react-i18next'; -import {Enum_Userspermissionsuser_Lang as Lang} from '../../generated/graphql'; +import {Enum_Userspermissionsuser_Lang as SupportedLocales} from '../../generated/graphql'; import withLanguagesSelection, { LanguageSelectionComponentProps, } from './withLanguagesSelection';

@@ -20,7 +20,7 @@ const handleClick = event => {

setSelecting(!isSelecting); }; - const onConfirm = (lang: Lang) => { + const onConfirm = (lang: SupportedLocales) => { setSelecting(false); onChangeLang(lang); };

@@ -30,12 +30,12 @@ <>

<MenuItem onClick={handleClick}>{t('menu.language')}</MenuItem> <MenuList className={languagesList} dense> <MenuItem - disabled={language === Lang.Fr} - onClick={() => onConfirm(Lang.Fr)} + disabled={language === SupportedLocales['fr']} + onClick={() => onConfirm(SupportedLocales['fr'])} >{t`languages.fr`}</MenuItem> <MenuItem - disabled={language === Lang.En} - onClick={() => onConfirm(Lang.En)} + disabled={language === SupportedLocales['en']} + onClick={() => onConfirm(SupportedLocales['en'])} >{t`languages.en`}</MenuItem> </MenuList> </>
M frontend/containers/Languages/withLanguagesSelection.tsxfrontend/containers/Languages/withLanguagesSelection.tsx

@@ -4,7 +4,7 @@ useUpdateMeMutation,

Enum_Userspermissionsuser_Lang as Lang, } from '../../generated/graphql'; import {useTranslation} from 'react-i18next'; -import {changeLang} from '../../lib/i18n'; +import useLocale from '../../hooks/useLocale'; export interface LanguageSelectionComponentProps { language: Lang;

@@ -21,13 +21,13 @@ ) =>

props => { const {connected} = useProfile(); const [updateProfile] = useUpdateMeMutation(); + const {changeLocale} = useLocale(); const {i18n} = useTranslation(); - const language = i18n.language.toUpperCase(); + const language = i18n.language; - const onChangeLang = (lang: Lang) => { - changeLang(lang); + const onChangeLang = async (lang: Lang) => { if (connected) { - updateProfile({ + await updateProfile({ variables: { userUpdate: { lang,

@@ -35,6 +35,7 @@ },

}, }); } + changeLocale(lang); }; return (
M frontend/containers/LostPassword/Success.jsfrontend/containers/LostPassword/Success.js

@@ -6,6 +6,7 @@ import Card from '@material-ui/core/Card';

import Typography from '@material-ui/core/Typography'; import CardActions from '@material-ui/core/CardActions'; import {makeStyles} from '@material-ui/core/styles'; +import Link from 'next/link'; const Success = ({email}) => { const {t} = useTranslation();

@@ -24,14 +25,11 @@ {t('lost_password.sent', {email})}

</Typography> </CardContent> <CardActions className={classes.actions}> - <Button - id="LostPasswordRegister" - href="/auth/login" - color="primary" - variant="contained" - > - {t('lost_password.actions.login')} - </Button> + <Link href="/auth/login" passHref> + <Button id="LostPasswordRegister" color="primary" variant="contained"> + {t('lost_password.actions.login')} + </Button> + </Link> </CardActions> </Card> );
M frontend/containers/SignInForm/index.tsxfrontend/containers/SignInForm/index.tsx

@@ -1,5 +1,5 @@

import {useState, useMemo} from 'react'; -import RouterLink from 'next/link'; +import NextLink from 'next/link'; import {makeStyles} from '@material-ui/core/styles'; import TextField from '@material-ui/core/TextField'; import Button from '@material-ui/core/Button';

@@ -87,18 +87,21 @@ name="password"

type="password" error={!!error} /> - <RouterLink href="/auth/lost-password"> + <NextLink href="/auth/lost-password" passHref> <Link> <Typography align="center" variant="body2"> {t('lost_password.message')} </Typography> </Link> - </RouterLink> + </NextLink> </CardContent> <CardActions className={classes.actions} align="center"> - <Button size="small" id="SignInRegister" href="/auth/register"> - {t('signin.register')} - </Button> + <NextLink href="/auth/register" passHref> + <Button size="small" id="SignInRegister"> + {t('signin.register')} + </Button> + </NextLink> + <Button color="primary" variant="contained"
M frontend/containers/SignUpForm/index.tsxfrontend/containers/SignUpForm/index.tsx

@@ -10,6 +10,7 @@ import CircularProgress from '@material-ui/core/CircularProgress';

import {makeStyles} from '@material-ui/core/styles'; import useToastsStore from '../../stores/useToastStore'; import {useRegisterMutation} from '../../generated/graphql'; +import Link from 'next/link'; const SignUp = () => { const {t, i18n} = useTranslation();

@@ -112,9 +113,11 @@ type="password"

/> </CardContent> <CardActions className={classes.actions}> - <Button id="SignUpLogin" href="/auth/login" variant="text"> - {t('signup.login')} - </Button> + <Link href="/auth/login" passHref> + <Button id="SignUpLogin" variant="text"> + {t('signup.login')} + </Button> + </Link> <Button color="primary" variant="contained"
M frontend/generated/graphql.tsxfrontend/generated/graphql.tsx

@@ -93,12 +93,14 @@ startsWith?: InputMaybe<Scalars['DateTime']>;

}; export enum Enum_Page_Type { - Tos = 'tos' + tos = 'tos' } export enum Enum_Userspermissionsuser_Lang { - En = 'EN', - Fr = 'FR' + EN = 'EN', + FR = 'FR', + en = 'en', + fr = 'fr' } export type EmailDesignerEmailTemplate = {

@@ -386,6 +388,7 @@ changePassword?: Maybe<UsersPermissionsLoginPayload>;

createEmailDesignerEmailTemplate?: Maybe<EmailDesignerEmailTemplateEntityResponse>; createEvent?: Maybe<EventEntityResponse>; createPage?: Maybe<PageEntityResponse>; + /** Create a passenger */ createPassenger?: Maybe<PassengerEntityResponse>; createSettingLocalization?: Maybe<SettingEntityResponse>; createTravel?: Maybe<TravelEntityResponse>;

@@ -1529,14 +1532,6 @@

export type RegisterMutation = { __typename?: 'Mutation', register: { __typename?: 'UsersPermissionsLoginPayload', jwt?: string | null, user: { __typename?: 'UsersPermissionsMe', id: string, username: string, email?: string | null, confirmed?: boolean | null } } }; -export type LoginMutationVariables = Exact<{ - identifier: Scalars['String']; - password: Scalars['String']; -}>; - - -export type LoginMutation = { __typename?: 'Mutation', login: { __typename?: 'UsersPermissionsLoginPayload', jwt?: string | null, user: { __typename?: 'UsersPermissionsMe', id: string, username: string, email?: string | null, confirmed?: boolean | null } } }; - export type ForgotPasswordMutationVariables = Exact<{ email: Scalars['String']; }>;

@@ -1858,43 +1853,6 @@ }

export type RegisterMutationHookResult = ReturnType<typeof useRegisterMutation>; export type RegisterMutationResult = Apollo.MutationResult<RegisterMutation>; export type RegisterMutationOptions = Apollo.BaseMutationOptions<RegisterMutation, RegisterMutationVariables>; -export const LoginDocument = gql` - mutation login($identifier: String!, $password: String!) { - login(input: {identifier: $identifier, password: $password}) { - jwt - user { - ...MeFields - } - } -} - ${MeFieldsFragmentDoc}`; -export type LoginMutationFn = Apollo.MutationFunction<LoginMutation, LoginMutationVariables>; - -/** - * __useLoginMutation__ - * - * To run a mutation, you first call `useLoginMutation` within a React component and pass it any options that fit your needs. - * When your component renders, `useLoginMutation` returns a tuple that includes: - * - A mutate function that you can call at any time to execute the mutation - * - An object with fields that represent the current status of the mutation's execution - * - * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; - * - * @example - * const [loginMutation, { data, loading, error }] = useLoginMutation({ - * variables: { - * identifier: // value for 'identifier' - * password: // value for 'password' - * }, - * }); - */ -export function useLoginMutation(baseOptions?: Apollo.MutationHookOptions<LoginMutation, LoginMutationVariables>) { - const options = {...defaultOptions, ...baseOptions} - return Apollo.useMutation<LoginMutation, LoginMutationVariables>(LoginDocument, options); - } -export type LoginMutationHookResult = ReturnType<typeof useLoginMutation>; -export type LoginMutationResult = Apollo.MutationResult<LoginMutation>; -export type LoginMutationOptions = Apollo.BaseMutationOptions<LoginMutation, LoginMutationVariables>; export const ForgotPasswordDocument = gql` mutation forgotPassword($email: String!) { forgotPassword(email: $email) {
A frontend/hooks/useLocale.ts

@@ -0,0 +1,28 @@

+import {Enum_Userspermissionsuser_Lang as SupportedLocales} from '../generated/graphql'; +import {useRouter} from 'next/router'; +import moment from 'moment'; + +const useLocale = (): { + locale: SupportedLocales; + changeLocale: (locale: SupportedLocales) => void; +} => { + const {pathname, query, asPath, push, locale} = useRouter(); + + const changeLocale = (newLocale: SupportedLocales) => { + moment.locale(newLocale); + if (typeof document !== 'undefined') { + document.cookie = `NEXT_LOCALE=${newLocale}; max-age=31536000; path=/`; + } + push({pathname, query}, asPath, {locale: newLocale}); + }; + + if (SupportedLocales[locale]) { + return {locale: SupportedLocales[locale], changeLocale}; + } + + const defaultLocale = SupportedLocales['en']; + changeLocale(defaultLocale); + return {locale: defaultLocale, changeLocale}; +}; + +export default useLocale;
M frontend/hooks/useSettings.tsfrontend/hooks/useSettings.ts

@@ -1,11 +1,10 @@

-import {useTranslation} from 'react-i18next'; import {useSettingQuery, SettingQuery} from '../generated/graphql'; +import useLocale from './useLocale'; const defaulData: SettingQuery = {}; const useSettings = () => { - const {i18n} = useTranslation(); - const locale = i18n.language; + const {locale} = useLocale(); const {data = defaulData} = useSettingQuery({variables: {locale}}); return data?.setting?.data?.attributes; };
M frontend/lib/i18n.tsfrontend/lib/i18n.ts

@@ -2,13 +2,9 @@ import i18n from 'i18next';

import {initReactI18next} from 'react-i18next'; import 'moment/locale/fr-ch'; import moment from 'moment'; +import {Enum_Userspermissionsuser_Lang as SupportedLocales} from '../../generated/graphql'; import translationFr from '../locales/fr.json'; import translationEn from '../locales/en.json'; -import {Enum_Userspermissionsuser_Lang as Lang} from '../generated/graphql'; - -const STORAGE_KEY = 'i18n-lang'; - -type AvailableLang = 'en' | 'fr'; const resources = { fr: {

@@ -19,44 +15,19 @@ translation: translationEn,

}, }; -export const getNavigatorLang = (): AvailableLang => { - if (typeof localStorage !== 'undefined') { - const storedLang = localStorage.getItem(STORAGE_KEY); - if (storedLang) return storedLang as AvailableLang; - } - if (typeof window !== 'undefined' && typeof window.navigator !== 'undefined') - if (navigator.language === 'fr' || navigator.language.includes('fr-')) - return 'fr'; - return 'fr'; +export const initI18Next = (locale: SupportedLocales) => { + i18n + .use(initReactI18next) // passes i18n down to react-i18next + .init({ + resources, + lng: locale, + supportedLngs: ['fr', 'en'], + interpolation: { + escapeValue: false, // react already safes from xss + }, + }); + moment.locale(i18n.language); }; -i18n - .use(initReactI18next) // passes i18n down to react-i18next - .init({ - resources, - lng: getNavigatorLang(), - supportedLngs: ['fr', 'en'], - interpolation: { - escapeValue: false, // react already safes from xss - }, - }); - -// Moment - On lang change -const changeMomentLang = (i18nLang: string) => { - const momentLang = i18nLang === 'fr' ? 'fr-ch' : 'en'; - moment.locale(momentLang); -}; - -// Moment - On page load -const i18nLang = getNavigatorLang(); -changeMomentLang(i18nLang); - -export const changeLang = (lang: Lang) => { - const i18nLang = lang.toLowerCase(); - i18n.changeLanguage(i18nLang); - changeMomentLang(i18nLang); - if (typeof localStorage !== 'undefined') - localStorage.setItem(STORAGE_KEY, i18nLang); -}; export default i18n;
M frontend/locales/en.jsonfrontend/locales/en.json

@@ -2,323 +2,233 @@ {

"drawer.information": "Information", "drawer.travels": "Travels", "drawer.waitingList": "Waiting list", - "generic": { - "me": "Me", - "loading": "Loading ...", - "close": "Close", - "create": "Create", - "delete": "Delete", - "cancel": "Cancel", - "remove": "Remove", - "select": "Select", - "save": "Save", - "confirm": "Confirm", - "clear": "Clear", - "errors": { - "date_min": "Select an upcoming date", - "unknown": "An unknown error occurred", - "rejected": "Something went wrong", - "bad_data": "Something is missing", - "unauthorized": "Authentication problem", - "forbidden": "You do not have the right to do this action", - "not_found": "Resource not found", - "server": "Problem on our servers", - "timeout": "Unstable connection, retry later" - } - }, - "languages": { - "fr": "Français", - "en": "English" - }, - "tour": { - "welcome": { - "title": "Welcome to Caroster!", - "text": "Would you like to take a feature tour?", - "nope": "Later", - "onboard": "OK, let's go!" - }, - "creator": { - "step1": "Add a new car by clicking on this button.", - "step2": "The waiting list includes passengers who do not yet have a seat in a car.", - "step3": "The event information can be modified from this menu.", - "step4": "The event can be edited by clicking on the edit button.", - "step5": "You can copy the link from now on to share it via email, whatsapp, telegram, etc." - }, - "user": { - "step1": "Add a new car by clicking on this button.", - "step2": "Would you like a place in a car? Register on the waiting list or directly in a car.", - "step3": "The event information can be accessed from this menu.", - "step4": "You can copy the link from now on to share it via email, whatsapp, telegram, etc." - } - }, - "joyride": { - "back": "Back", - "close": "Close", - "last": "Finish", - "next": "Next", - "skip": "Skip" - }, - "menu": { - "about": "About Caroster", - "tour": "Caroster tour", - "dashboard": "My Caroster", - "language": "Language", - "login": "Login", - "logout": "Logout", - "register": "Sign-Up", - "new_event": "Create a caroster", - "profile": "Profile" - }, - "event": { - "title": "{{title}} - Caroster", - "not_found": "Project not found", - "no_travel.title": "There are currently no cars", - "no_other_travel.title": "There are currently no other car", - "no_travel.desc": "Share the event and register to the waiting list to get notified when a car will be available!", - "details.modify": "Modify", - "details.save": "Save", - "details.aboutCaroster": "Learn more about Caroster", - "fields": { - "name": "Name of the event", - "date": "Event date", - "date_placeholder": "DD/MM/YYYY", - "description": "Description", - "address": "Event address", - "empty": "Not specified", - "link": "Share link", - "link_desc": "Share this link with other people", - "share": "Share", - "copyLink": "Copy link" - }, - "creation": { - "title": "New event", - "name": "Event name", - "date": "Date of the event", - "description": "Description", - "description_helper": "Optionnal", - "address": "Address of the event", - "creator_email": "Your e-mail", - "next": "Next", - "newsletter": "Keep me informed of developments in Caroster by e-mail", - "actions": { - "dashboard": "$t(menu.dashboard)", - "see_profile": "Profile", - "about": "About Caroster" - }, - "addFromAccount": { - "title": "Do you want to add this caroster to your events?", - "subtitle": "Create it from your account", - "actions": { - "register": "$t(menu.register)", - "login": "$t(menu.login)" - } - } - }, - "actions": { - "find_car": "Find a car", - "copied": "The link has been copied to your clipboard", - "noShareCapability": "Your browser cannot share nor copy to clipboard, please copy the page's URL instead.", - "add_to_my_events": "Add to my events", - "see_on_gmap": "See on a map" - }, - "errors": { - "cant_create": "Unable to create event", - "cant_update": "Unable to modify event" - }, - "add_to_my_events": { - "login": "$t(menu.login)", - "register": "$t(menu.register)", - "title": "You must be logged in", - "text_html": "To add <strong> {{eventName}} </strong> to your carosters you must be logged in or create an account." - } - }, - "travel": { - "fields": { - "meeting_point": "Meeting place", - "details": "Notes", - "phone": "Contact" - }, - "creation": { - "date": "Date of departure", - "time": "Departure time", - "title": "Add a car", - "car.title": "Car and driver", - "travel.title": "Travel", - "name": "Name of the car", - "seats": "Number of available seats", - "meeting": "Meeting place", - "phone": "Telephone number", - "phoneHelper.faq": "/en/faq", - "phoneHelper.why": "Why do we ask for a phone number ?", - "notes": "Additional information", - "created": "The car has been created", - "submit": "Add" - }, - "actions": { - "remove_alert": "Are you sure you want to remove this car and add the subscribers to the waiting list?", - "removed": "The car has been removed and its passengers moved to the waiting list of the event." - }, - "vehicle": { - "add": "Add a new vehicle", - "title": "My Vehicles", - "name": "Name of the vehicle", - "license_plate": "License plate", - "seats_number": "Seats number", - "empty": "There is no vehicle assigned to you. Click the button bellow in order to create one." - }, - "passengers": { - "empty": "Available seat", - "add": "Add a passenger", - "add_to_travel": "Add a passenger", - "add_to_waitingList": "Add someone", - "add_to_car": "Add to car", - "add_me": "Add me", - "register_to_waiting_list": "Register to waiting list", - "add_someone": "Add someone", - "location": "Meeting place", - "location_helper": "Indicate your preferred departure location", - "location_placeholder": "Meeting place (optionnal)", - "email": "Email", - "email_placeholder": "Email", - "email_placeholder_optional": "Email (optional)", - "email_helpertext": "Email is not valid", - "name": "Name", - "name_placeholder": "Name" - }, - "errors": { - "cant_create": "Unable to create the car", - "cant_update": "Unable to modify the car", - "cant_remove": "Unable to remove the car", - "cant_add_passenger": "Unable to add a passenger", - "cant_remove_passenger": "Unable to remove passenger", - "add_someone": "Add someone" - }, - "moved_to_waiting_list": "Passenger was moved to the waiting list of the event." - }, - "dashboard": { - "title": "$t(menu.dashboard)", - "actions": { - "see_event": "See more", - "add_event": "Create a caroster" - }, - "sections": { - "future": "Caroster to come", - "future_plural": "Carosters to come", - "past": "Caroster passed", - "past_plural": "Past carosters", - "noDate": "Caroster without date", - "noDate_plural": "Carosters without date" - }, - "noEvent": { - "title": "Welcome to Caroster", - "text_html": "Here you will see <strong> the carosters you are participating in </strong>, to start creating a Caroster!", - "create_event": "$t(menu.new_event)" - } - }, - "profile": { - "title": "Profile", - "firstName": "First name", - "lastName": "Last name", - "email": "Email", - "current_password": "Current password", - "new_password": "New password", - "password_changed": "Password updated", - "updated": "Profile updated", - "not_defined": "Not specified", - "actions": { - "save": "Save", - "edit": "Edit", - "change_password": "Change your password", - "logout": "Logout", - "cancel": "Cancel", - "save_new_password": "Update" - }, - "errors": { - "password_nomatch": "Wrong password" - } - }, - "passenger": { - "title": "Waiting list", - "availability": { - "seats": "{{count}} seat available", - "seats_plural": "{{count}} seats available" - }, - "creation": { - "seats": "Number of passengers: {{seats}}", - "departure": "Departure: ", - "assign": "Assign", - "available_cars": "Available cars", - "no_travel.title": "No available seats at the moment...", - "no_travel.desc": "{{name}} will receive an email when new cars will be available. You can share the event in the meantime." - }, - "actions": { - "remove_alert": "Are you sure you want to remove <italic> <bold> {{name}} </bold> </italic> from the waitlist?", - "place": "Assign" - }, - "errors": { - "cant_add_passenger": "Unable to add a passenger", - "cant_save_passengers": "Unable to update passengers", - "cant_remove_passenger": "Unable to remove the passenger", - "cant_select_car": "Unable to select the car" - }, - "success": { - "added_self_to_car": "You have been added to this car", - "added_to_car": "{{name}} has been added to this car", - "added_self_to_waitlist": "You have been added to the waitlist. You'll be notified when new cars will be added.", - "added_to_waitlist": "{{name}} has been added to the waitlist", - "goToTravels": "Go to travels" - }, - "input": { - "email": "Your email", - "email_helper": "Optional - Get notified if cars are added", - "email_helper_car": "Optional" - }, - "deleted": "The passenger has been deleted from the event." - }, - "signup": { - "title": "Sign up", - "email": "Email", - "firstName": "First name", - "lastName": "Last name", - "password": "Password", - "submit": "Create your account", - "login": "$t(menu.login)", - "errors": { - "email_taken": "This email is already associated with an account" - } - }, - "confirm": { - "title": "Confirm your account", - "text": "You have received an email with a link. Please click on this link to confirm your account.", - "login": "Return to the login screen" - }, - "signin": { - "title": "Sign in", - "email": "Email", - "password": "Password", - "login": "$t(menu.login)", - "register": "$t(menu.register)", - "errors": "Check your email and password", - "unconfirmed": "Your account has not been confirmed. Please check your emails", - "withGoogle": "Use a Google account" - }, - "lost_password": { - "title": "Password recovery", - "reset_title": "Definition of a new password", - "message": "Lost your password?", - "email": "Your email", - "password": "New password", - "password_confirmation": "Confirmation of the new password", - "sent": "An email has been sent to {{email}}, with a link to recover your password", - "error": "This email does not exist", - "change_success": "Your password has been changed", - "actions": { - "send": "Send a recovery email", - "cancel": "Cancel", - "login": "Return to the login screen", - "resend": "Send again", - "register": "Create an account?", - "save_new_password": "Update" - } - } -} + "generic.me": "Me", + "generic.loading": "Loading ...", + "generic.close": "Close", + "generic.create": "Create", + "generic.delete": "Delete", + "generic.cancel": "Cancel", + "generic.remove": "Remove", + "generic.select": "Select", + "generic.save": "Save", + "generic.confirm": "Confirm", + "generic.clear": "Clear", + "generic.errors.date_min": "Select an upcoming date", + "generic.errors.unknown": "An unknown error occurred", + "generic.errors.rejected": "Something went wrong", + "generic.errors.bad_data": "Something is missing", + "generic.errors.unauthorized": "Authentication problem", + "generic.errors.forbidden": "You do not have the right to do this action", + "generic.errors.not_found": "Resource not found", + "generic.errors.server": "Problem on our servers", + "generic.errors.timeout": "Unstable connection, retry later", + "languages.fr": "Français", + "languages.en": "English", + "tour.welcome.title": "Welcome to Caroster!", + "tour.welcome.text": "Would you like to take a feature tour?", + "tour.welcome.nope": "Later", + "tour.welcome.onboard": "OK, let's go!", + "tour.creator.step1": "Add a new car by clicking on this button.", + "tour.creator.step2": "The waiting list includes passengers who do not yet have a seat in a car.", + "tour.creator.step3": "The event information can be modified from this menu.", + "tour.creator.step4": "The event can be edited by clicking on the edit button.", + "tour.creator.step5": "You can copy the link from now on to share it via email, whatsapp, telegram, etc.", + "tour.user.step1": "Add a new car by clicking on this button.", + "tour.user.step2": "Would you like a place in a car? Register on the waiting list or directly in a car.", + "tour.user.step3": "The event information can be accessed from this menu.", + "tour.user.step4": "You can copy the link from now on to share it via email, whatsapp, telegram, etc.", + "joyride.back": "Back", + "joyride.close": "Close", + "joyride.last": "Finish", + "joyride.next": "Next", + "joyride.skip": "Skip", + "menu.about": "About Caroster", + "menu.tour": "Caroster tour", + "menu.dashboard": "My Caroster", + "menu.language": "Language", + "menu.login": "Login", + "menu.logout": "Logout", + "menu.register": "Sign-Up", + "menu.new_event": "Create a caroster", + "menu.profile": "Profile", + "event.title": "{{title}} - Caroster", + "event.not_found": "Project not found", + "event.no_travel.title": "There are currently no cars", + "event.no_other_travel.title": "There are currently no other car", + "event.no_travel.desc": "Share the event and register to the waiting list to get notified when a car will be available!", + "event.details.modify": "Modify", + "event.details.save": "Save", + "event.details.aboutCaroster": "Learn more about Caroster", + "event.fields.name": "Name of the event", + "event.fields.date": "Event date", + "event.fields.date_placeholder": "DD/MM/YYYY", + "event.fields.description": "Description", + "event.fields.address": "Event address", + "event.fields.empty": "Not specified", + "event.fields.link": "Share link", + "event.fields.link_desc": "Share this link with other people", + "event.fields.share": "Share", + "event.fields.copyLink": "Copy link", + "event.creation.title": "New event", + "event.creation.name": "Event name", + "event.creation.date": "Date of the event", + "event.creation.description": "Description", + "event.creation.description_helper": "Optionnal", + "event.creation.address": "Address of the event", + "event.creation.creator_email": "Your e-mail", + "event.creation.next": "Next", + "event.creation.newsletter": "Keep me informed of developments in Caroster by e-mail", + "event.creation.actions.dashboard": "$t(menu.dashboard)", + "event.creation.actions.see_profile": "Profile", + "event.creation.actions.about": "About Caroster", + "event.creation.addFromAccount.title": "Do you want to add this caroster to your events?", + "event.creation.addFromAccount.subtitle": "Create it from your account", + "event.creation.addFromAccount.actions.register": "$t(menu.register)", + "event.creation.addFromAccount.actions.login": "$t(menu.login)", + "event.actions.find_car": "Find a car", + "event.actions.copied": "The link has been copied to your clipboard", + "event.actions.noShareCapability": "Your browser cannot share nor copy to clipboard, please copy the page's URL instead.", + "event.actions.add_to_my_events": "Add to my events", + "event.actions.see_on_gmap": "See on a map", + "event.errors.cant_create": "Unable to create event", + "event.errors.cant_update": "Unable to modify event", + "event.add_to_my_events.login": "$t(menu.login)", + "event.add_to_my_events.register": "$t(menu.register)", + "event.add_to_my_events.title": "You must be logged in", + "event.add_to_my_events.text_html": "To add <strong> {{eventName}} </strong> to your carosters you must be logged in or create an account.", + "travel.fields.meeting_point": "Meeting place", + "travel.fields.details": "Notes", + "travel.fields.phone": "Contact", + "travel.creation.date": "Date of departure", + "travel.creation.time": "Departure time", + "travel.creation.title": "Add a car", + "travel.creation.car.title": "Car and driver", + "travel.creation.travel.title": "Travel", + "travel.creation.name": "Name of the car", + "travel.creation.seats": "Number of available seats", + "travel.creation.meeting": "Meeting place", + "travel.creation.phone": "Telephone number", + "travel.creation.phoneHelper.faq": "/en/faq", + "travel.creation.phoneHelper.why": "Why do we ask for a phone number ?", + "travel.creation.notes": "Additional information", + "travel.creation.created": "The car has been created", + "travel.creation.submit": "Add", + "travel.actions.remove_alert": "Are you sure you want to remove this car and add the subscribers to the waiting list?", + "travel.actions.removed": "The car has been removed and its passengers moved to the waiting list of the event.", + "travel.vehicle.add": "Add a new vehicle", + "travel.vehicle.title": "My Vehicles", + "travel.vehicle.name": "Name of the vehicle", + "travel.vehicle.license_plate": "License plate", + "travel.vehicle.seats_number": "Seats number", + "travel.vehicle.empty": "There is no vehicle assigned to you. Click the button bellow in order to create one.", + "travel.passengers.empty": "Available seat", + "travel.passengers.add": "Add a passenger", + "travel.passengers.add_to_travel": "Add a passenger", + "travel.passengers.add_to_waitingList": "Add someone", + "travel.passengers.add_to_car": "Add to car", + "travel.passengers.add_me": "Add me", + "travel.passengers.register_to_waiting_list": "Register to waiting list", + "travel.passengers.add_someone": "Add someone", + "travel.passengers.location": "Meeting place", + "travel.passengers.location_helper": "Indicate your preferred departure location", + "travel.passengers.location_placeholder": "Meeting place (optionnal)", + "travel.passengers.email": "Email", + "travel.passengers.email_placeholder": "Email", + "travel.passengers.email_placeholder_optional": "Email (optional)", + "travel.passengers.email_helpertext": "Email is not valid", + "travel.passengers.name": "Name", + "travel.passengers.name_placeholder": "Name", + "travel.errors.cant_create": "Unable to create the car", + "travel.errors.cant_update": "Unable to modify the car", + "travel.errors.cant_remove": "Unable to remove the car", + "travel.errors.cant_add_passenger": "Unable to add a passenger", + "travel.errors.cant_remove_passenger": "Unable to remove passenger", + "travel.errors.add_someone": "Add someone", + "travel.moved_to_waiting_list": "Passenger was moved to the waiting list of the event.", + "dashboard.title": "$t(menu.dashboard)", + "dashboard.actions.see_event": "See more", + "dashboard.actions.add_event": "Create a caroster", + "dashboard.sections.future": "Caroster to come", + "dashboard.sections.future_plural": "Carosters to come", + "dashboard.sections.past": "Caroster passed", + "dashboard.sections.past_plural": "Past carosters", + "dashboard.sections.noDate": "Caroster without date", + "dashboard.sections.noDate_plural": "Carosters without date", + "dashboard.noEvent.title": "Welcome to Caroster", + "dashboard.noEvent.text_html": "Here you will see <strong> the carosters you are participating in </strong>, to start creating a Caroster!", + "dashboard.noEvent.create_event": "$t(menu.new_event)", + "profile.title": "Profile", + "profile.firstName": "First name", + "profile.lastName": "Last name", + "profile.email": "Email", + "profile.current_password": "Current password", + "profile.new_password": "New password", + "profile.password_changed": "Password updated", + "profile.updated": "Profile updated", + "profile.not_defined": "Not specified", + "profile.actions.save": "Save", + "profile.actions.edit": "Edit", + "profile.actions.change_password": "Change your password", + "profile.actions.logout": "Logout", + "profile.actions.cancel": "Cancel", + "profile.actions.save_new_password": "Update", + "profile.errors.password_nomatch": "Wrong password", + "passenger.title": "Waiting list", + "passenger.availability.seats": "{{count}} seat available", + "passenger.availability.seats_plural": "{{count}} seats available", + "passenger.creation.seats": "Number of passengers: {{seats}}", + "passenger.creation.departure": "Departure: ", + "passenger.creation.assign": "Assign", + "passenger.creation.available_cars": "Available cars", + "passenger.creation.no_travel.title": "No available seats at the moment...", + "passenger.creation.no_travel.desc": "{{name}} will receive an email when new cars will be available. You can share the event in the meantime.", + "passenger.actions.remove_alert": "Are you sure you want to remove <italic> <bold> {{name}} </bold> </italic> from the waitlist?", + "passenger.actions.place": "Assign", + "passenger.errors.cant_add_passenger": "Unable to add a passenger", + "passenger.errors.cant_save_passengers": "Unable to update passengers", + "passenger.errors.cant_remove_passenger": "Unable to remove the passenger", + "passenger.errors.cant_select_car": "Unable to select the car", + "passenger.success.added_self_to_car": "You have been added to this car", + "passenger.success.added_to_car": "{{name}} has been added to this car", + "passenger.success.added_self_to_waitlist": "You have been added to the waitlist. You'll be notified when new cars will be added.", + "passenger.success.added_to_waitlist": "{{name}} has been added to the waitlist", + "passenger.success.goToTravels": "Go to travels", + "passenger.input.email": "Your email", + "passenger.input.email_helper": "Optional - Get notified if cars are added", + "passenger.input.email_helper_car": "Optional", + "passenger.deleted": "The passenger has been deleted from the event.", + "signup.title": "Sign up", + "signup.email": "Email", + "signup.firstName": "First name", + "signup.lastName": "Last name", + "signup.password": "Password", + "signup.submit": "Create your account", + "signup.login": "$t(menu.login)", + "signup.errors.email_taken": "This email is already associated with an account", + "confirm.title": "Confirm your account", + "confirm.text": "You have received an email with a link. Please click on this link to confirm your account.", + "confirm.login": "Return to the login screen", + "signin.title": "Sign in", + "signin.email": "Email", + "signin.password": "Password", + "signin.login": "$t(menu.login)", + "signin.register": "$t(menu.register)", + "signin.errors": "Check your email and password", + "signin.unconfirmed": "Your account has not been confirmed. Please check your emails", + "signin.withGoogle": "Use a Google account", + "lost_password.title": "Password recovery", + "lost_password.reset_title": "Definition of a new password", + "lost_password.message": "Lost your password?", + "lost_password.email": "Your email", + "lost_password.password": "New password", + "lost_password.password_confirmation": "Confirmation of the new password", + "lost_password.sent": "An email has been sent to {{email}}, with a link to recover your password", + "lost_password.error": "This email does not exist", + "lost_password.change_success": "Your password has been changed", + "lost_password.actions.send": "Send a recovery email", + "lost_password.actions.cancel": "Cancel", + "lost_password.actions.login": "Return to the login screen", + "lost_password.actions.resend": "Send again", + "lost_password.actions.register": "Create an account?", + "lost_password.actions.save_new_password": "Update" +}
M frontend/locales/fr.jsonfrontend/locales/fr.json

@@ -2,323 +2,233 @@ {

"drawer.information": "Information", "drawer.travels": "Trajets", "drawer.waitingList": "Liste d'attente", - "generic": { - "me": "Moi", - "loading": "Chargement...", - "close": "Fermer", - "create": "Créer", - "delete": "Supprimer", - "cancel": "Annuler", - "remove": "Supprimer", - "select": "Selectionner", - "save": "Enregistrer", - "confirm": "Confirmer", - "clear": "Effacer", - "errors": { - "date_min": "Sélectionnez une date à venir", - "unknown": "Une erreur inconnue c'est produite", - "rejected": "Quelque chose c'est mal passé", - "bad_data": "Il manque quelque chose", - "unauthorized": "Problème d'authentification", - "forbidden": "Vous n'avez pas le droit de faire cette action", - "not_found": "Ressource introuvable", - "server": "Problème sur nos serveurs", - "timeout": "Connexion instable, re-essayé plus tard" - } - }, - "languages": { - "fr": "Français", - "en": "English" - }, - "tour": { - "welcome": { - "title": "Bienvenue sur Caroster !", - "text": "Faire un tour des fonctionnalités.", - "nope": "Pas maintenant", - "onboard": "OK, c'est parti!" - }, - "creator": { - "step1": "Ajoutez une nouvelle voiture en cliquant directement sur ce bouton.", - "step2": "La liste d'attente regroupe les personnes qui n'ont pas encore été placées dans une voiture.", - "step3": "Vous pouvez voir et modifier les informations de l'événement depuis ce menu.✨", - "step4": "Editez l'événement en cliquant sur le bouton d'édition.", - "step5": "Copiez le lien de l'événement dès maintenant pour le partager par email, whatsapp, telegram, etc.. " - }, - "user": { - "step1": "Ajoutez une nouvelle voiture en cliquant directement sur ce bouton.", - "step2": "Vous aimeriez une place dans une voiture ? Inscrivez-vous dans la liste d'attente ou directement dans une voiture.", - "step3": "Les informations de l'événement sont accessibles depuis ce menu.", - "step4": "Copiez le lien de l'événement dès maintenant pour le partager par email, whatsapp, telegram, etc.." - } - }, - "joyride": { - "back": "Retour", - "close": "Fermer", - "last": "Terminer", - "next": "Suivant", - "skip": "Fermer" - }, - "menu": { - "about": "À propos de Caroster", - "tour": "Découverte de Caroster", - "dashboard": "Mes Caroster", - "language": "Langue", - "login": "Se connecter", - "logout": "Se déconnecter", - "register": "Créer un compte", - "new_event": "Créer un caroster", - "profile": "Profil" - }, - "event": { - "title": "{{title}} - Caroster", - "not_found": "Projet introuvable", - "no_travel.title": "Pas de voitures pour le moment", - "no_other_travel.title": "Pas d'autres voitures pour le moment", - "no_travel.desc": "Partagez l’événement et inscrivez-vous dans la liste d’attente pour être notifié lorsqu’une voiture sera ajoutée !", - "details.modify": "Modifier", - "details.save": "Enregistrer", - "details.aboutCaroster": "En savoir plus sur Caroster", - "fields": { - "name": "Nom de l'événement", - "date": "Date de l'événement", - "date_placeholder": "DD/MM/YYYY", - "description": "Description", - "address": "Adresse de l'événement", - "empty": "Non précisé", - "link": "Lien de partage", - "link_desc": "Partager l'évènement à d'autres personnes", - "share": "Partager", - "copyLink": "Copier le lien" - }, - "creation": { - "title": "Nouvel évènement", - "name": "Nom de l'événement", - "date": "Date de l'événement", - "description": "Description", - "description_helper": "Optionnel", - "address": "Adresse de l'événement", - "creator_email": "Votre e-mail", - "next": "Suivant", - "newsletter": "Me tenir informé des évolutions de Caroster par e-mail", - "actions": { - "dashboard": "$t(menu.dashboard)", - "see_profile": "Profil", - "about": "À propos de Caroster" - }, - "addFromAccount": { - "title": "Voulez-vous ajouter ce caroster à vos évènements ?", - "subtitle": "Créez-le depuis votre compte", - "actions": { - "register": "$t(menu.register)", - "login": "$t(menu.login)" - } - } - }, - "actions": { - "find_car": "Trouver une voiture", - "copied": "Le lien a été copié dans votre presse-papier", - "noShareCapability": "Votre navigateur ne permet pas de partager ou de copier dans le presse papier, veuillez copier l'URL de la page.", - "add_to_my_events": "Ajouter à mes évènements", - "see_on_gmap": "Voir sur une carte" - }, - "errors": { - "cant_create": "Impossible de créer l'événement", - "cant_update": "Impossible de modifier l'événement" - }, - "add_to_my_events": { - "login": "$t(menu.login)", - "register": "$t(menu.register)", - "title": "Vous devez être connecté", - "text_html": "Pour ajouter <strong>{{eventName}}</strong> à vos carosters vous devez être connecté ou créer un compte." - } - }, - "travel": { - "fields": { - "meeting_point": "Lieu de rencontre", - "details": "Notes", - "phone": "Contact" - }, - "creation": { - "date": "Date de départ", - "time": "Heure de départ", - "title": "Ajouter une voiture", - "car.title": "Voiture et contact", - "travel.title": "Trajet", - "name": "Nom de la voiture", - "seats": "Nombre de places disponibles", - "meeting": "Lieu de rencontre", - "phone": "Numéro de téléphone", - "phoneHelper.faq": "/fr/faq", - "phoneHelper.why": "Pourquoi le num. de tél. est-il demandé?", - "notes": "Infos complémentaires", - "created": "La voiture a été créée", - "submit": "Ajouter" - }, - "actions": { - "remove_alert": "Voulez-vous vraiment supprimer cette voiture et ajouter les inscrits à la liste d'attente ?", - "removed": "La voiture a été supprimée et ses passagers déplacés dans la liste d'attente de l'événement." - }, - "vehicle": { - "add": "Ajouter un nouveau véhicule", - "title": "Mes véhicules", - "name": "Nom du véhicule", - "license_plate": "Plaque d'immatriculation", - "seats_number": "Nombre de places", - "empty": "Vous n'avez aucun véhicule assigné. Utilisez le bouton ci-dessous pour en créer un." - }, - "passengers": { - "empty": "Place disponible", - "add": "Ajouter un passager", - "add_to_travel": "Ajouter un passager", - "add_to_waitingList": "Ajouter quelqu'un", - "add_me": "S'ajouter", - "add_to_car": "Ajouter à la voiture", - "register_to_waiting_list": "Inscription à la liste d'attente", - "add_someone": "Ajouter quelqu'un", - "location": "Lieu de rencontre", - "location_helper": "Indiquez votre lieu de départ de préférence", - "location_placeholder": "Lieu de rencontre (optionnel)", - "email": "Email", - "email_placeholder": "Email", - "email_placeholder_optional": "Email (optionnel)", - "email_helpertext": "Email non valide", - "name": "Nom", - "name_placeholder": "Nom" - }, - "errors": { - "cant_create": "Impossible de créer la voiture", - "cant_update": "Impossible de modifier la voiture", - "cant_remove": "Impossible de supprimer la voiture", - "cant_add_passenger": "Impossible d'ajouter un passager", - "cant_remove_passenger": "Impossible de supprimer le passager" - }, - "moved_to_waiting_list": "Le passager a été déplacé dans la liste d'attente de l'événement." - }, - "dashboard": { - "title": "$t(menu.dashboard)", - "actions": { - "see_event": "Accéder", - "add_event": "Créer un caroster" - }, - "sections": { - "future": "Caroster à venir", - "future_plural": "Carosters à venir", - "past": "Caroster passé", - "past_plural": "Carosters passés", - "noDate": "Caroster sans date", - "noDate_plural": "Carosters sans date" - }, - "noEvent": { - "title": "Bienvenue sur Caroster", - "text_html": "Ici, vous y verrez <strong>les carosters auxquels vous participez</strong>, pour commencer créer un Caroster !", - "create_event": "$t(menu.new_event)" - } - }, - "profile": { - "title": "Profil", - "firstName": "Prénom", - "lastName": "Nom", - "email": "Email", - "current_password": "Mot de passe actuel", - "new_password": "Nouveau mot de passe", - "password_changed": "Mot de passe mis à jour", - "updated": "Profil mis à jour", - "not_defined": "Non précisé", - "actions": { - "save": "Enregistrer", - "edit": "Editer", - "change_password": "Changer son mot de passe", - "logout": "Se déconnecter", - "cancel": "Annuler", - "save_new_password": "Mettre à jour" - }, - "errors": { - "password_nomatch": "Mot de passe erroné" - } - }, - "passenger": { - "title": "Liste d'attente", - "availability": { - "seats": "{{count}} place disponible", - "seats_plural": "{{count}} places disponibles" - }, - "creation": { - "seats": "Nombre de passagers: {{seats}}", - "departure": "Depart: ", - "assign": "Placer", - "available_cars": "Voitures disponibles", - "no_travel.title": "Pas de place disponible en ce moment...", - "no_travel.desc": "{{name}} recevra un email lorsqu’une voiture sera ajoutée. En attendant, partagez l’événement" - }, - "actions": { - "remove_alert": "Voulez-vous vraiment supprimer <italic><bold>{{name}}</bold></italic> de la liste d'attente ?", - "place": "Placer" - }, - "errors": { - "cant_add_passenger": "Impossible d'ajouter un passager", - "cant_save_passengers": "Impossible de mettre à jour passagers", - "cant_remove_passenger": "Impossible de retirer le passager", - "cant_select_car": "Impossible de sélectionner la voiture", - "car_full": "Voiture pleine. Impossible d'ajouter un passager supplémentaire" - }, - "success": { - "added_self_to_car": "Vous avez été ajouté à la voiture", - "added_to_car": "{{name}} a été ajouté à la voiture", - "added_self_to_waitlist": "Vous avez été ajouté à la liste d’attente. Vous serez notifié à l’ajout de nouvelles voitures", - "added_to_waitlist": "{{name}} ajouté à la liste d'attente", - "goToTravels": "Aller aux trajets" - }, - "input": { - "email": "Votre email", - "email_helper": "Optionnel - Soyez notifié si des voitures sont ajoutées", - "email_helper_car": "Optionnel" - }, - "deleted": "Le passager a été supprimé de l'événement." - }, - "signup": { - "title": "Inscription", - "email": "Email", - "firstName": "Prénom", - "lastName": "Nom", - "password": "Mot de passe", - "submit": "Créer son compte", - "login": "$t(menu.login)", - "errors": { - "email_taken": "Cet email est déjà associé à un compte" - } - }, - "confirm": { - "title": "Confirmez votre compte", - "text": "Vous avez reçu un email avec un lien. Merci de cliquer sur ce lien pour confirmer votre compte.", - "login": "Retour à l'écran de connexion" - }, - "signin": { - "title": "Connexion", - "email": "Email", - "password": "Mot de passe", - "login": "$t(menu.login)", - "register": "$t(menu.register)", - "errors": "Vérifier votre email et mot de passe", - "unconfirmed": "Votre compte n'a pas été confirmé. Merci de vérifier vos emails", - "withGoogle": "Utiliser un compte Google" - }, - "lost_password": { - "title": "Récupération de mot de passe", - "reset_title": "Définition d'un nouveau mot de passe", - "message": "Perdu son mot de passe ?", - "email": "Votre email", - "password": "Nouveau mot de passe", - "password_confirmation": "Confirmation du nouveau mot de passe", - "sent": "Un email a été envoyé à {{email}}, avec un lien pour récupérer votre mot de passe", - "error": "Cet email n'existe pas", - "change_success": "Votre mot de passe a été modifié", - "actions": { - "send": "Envoyer un email de récupération", - "cancel": "Annuler", - "login": "Retour à l'écran de connexion", - "resend": "Envoyer à nouveau", - "register": "Créer un compte ?", - "save_new_password": "Mettre à jour" - } - } -} + "generic.me": "Moi", + "generic.loading": "Chargement...", + "generic.close": "Fermer", + "generic.create": "Créer", + "generic.delete": "Supprimer", + "generic.cancel": "Annuler", + "generic.remove": "Supprimer", + "generic.select": "Selectionner", + "generic.save": "Enregistrer", + "generic.confirm": "Confirmer", + "generic.clear": "Effacer", + "generic.errors.date_min": "Sélectionnez une date à venir", + "generic.errors.unknown": "Une erreur inconnue c'est produite", + "generic.errors.rejected": "Quelque chose c'est mal passé", + "generic.errors.bad_data": "Il manque quelque chose", + "generic.errors.unauthorized": "Problème d'authentification", + "generic.errors.forbidden": "Vous n'avez pas le droit de faire cette action", + "generic.errors.not_found": "Ressource introuvable", + "generic.errors.server": "Problème sur nos serveurs", + "generic.errors.timeout": "Connexion instable, re-essayé plus tard", + "languages.fr": "Français", + "languages.en": "English", + "tour.welcome.title": "Bienvenue sur Caroster !", + "tour.welcome.text": "Faire un tour des fonctionnalités.", + "tour.welcome.nope": "Pas maintenant", + "tour.welcome.onboard": "OK, c'est parti!", + "tour.creator.step1": "Ajoutez une nouvelle voiture en cliquant directement sur ce bouton.", + "tour.creator.step2": "La liste d'attente regroupe les personnes qui n'ont pas encore été placées dans une voiture.", + "tour.creator.step3": "Vous pouvez voir et modifier les informations de l'événement depuis ce menu.✨", + "tour.creator.step4": "Editez l'événement en cliquant sur le bouton d'édition.", + "tour.creator.step5": "Copiez le lien de l'événement dès maintenant pour le partager par email, whatsapp, telegram, etc.. ", + "tour.user.step1": "Ajoutez une nouvelle voiture en cliquant directement sur ce bouton.", + "tour.user.step2": "Vous aimeriez une place dans une voiture ? Inscrivez-vous dans la liste d'attente ou directement dans une voiture.", + "tour.user.step3": "Les informations de l'événement sont accessibles depuis ce menu.", + "tour.user.step4": "Copiez le lien de l'événement dès maintenant pour le partager par email, whatsapp, telegram, etc..", + "joyride.back": "Retour", + "joyride.close": "Fermer", + "joyride.last": "Terminer", + "joyride.next": "Suivant", + "joyride.skip": "Fermer", + "menu.about": "À propos de Caroster", + "menu.tour": "Découverte de Caroster", + "menu.dashboard": "Mes Caroster", + "menu.language": "Langue", + "menu.login": "Se connecter", + "menu.logout": "Se déconnecter", + "menu.register": "Créer un compte", + "menu.new_event": "Créer un caroster", + "menu.profile": "Profil", + "event.title": "{{title}} - Caroster", + "event.not_found": "Projet introuvable", + "event.no_travel.title": "Pas de voitures pour le moment", + "event.no_other_travel.title": "Pas d'autres voitures pour le moment", + "event.no_travel.desc": "Partagez l’événement et inscrivez-vous dans la liste d’attente pour être notifié lorsqu’une voiture sera ajoutée !", + "event.details.modify": "Modifier", + "event.details.save": "Enregistrer", + "event.details.aboutCaroster": "En savoir plus sur Caroster", + "event.fields.name": "Nom de l'événement", + "event.fields.date": "Date de l'événement", + "event.fields.date_placeholder": "DD/MM/YYYY", + "event.fields.description": "Description", + "event.fields.address": "Adresse de l'événement", + "event.fields.empty": "Non précisé", + "event.fields.link": "Lien de partage", + "event.fields.link_desc": "Partager l'évènement à d'autres personnes", + "event.fields.share": "Partager", + "event.fields.copyLink": "Copier le lien", + "event.creation.title": "Nouvel évènement", + "event.creation.name": "Nom de l'événement", + "event.creation.date": "Date de l'événement", + "event.creation.description": "Description", + "event.creation.description_helper": "Optionnel", + "event.creation.address": "Adresse de l'événement", + "event.creation.creator_email": "Votre e-mail", + "event.creation.next": "Suivant", + "event.creation.newsletter": "Me tenir informé des évolutions de Caroster par e-mail", + "event.creation.actions.dashboard": "$t(menu.dashboard)", + "event.creation.actions.see_profile": "Profil", + "event.creation.actions.about": "À propos de Caroster", + "event.creation.addFromAccount.title": "Voulez-vous ajouter ce caroster à vos évènements ?", + "event.creation.addFromAccount.subtitle": "Créez-le depuis votre compte", + "event.creation.addFromAccount.actions.register": "$t(menu.register)", + "event.creation.addFromAccount.actions.login": "$t(menu.login)", + "event.actions.find_car": "Trouver une voiture", + "event.actions.copied": "Le lien a été copié dans votre presse-papier", + "event.actions.noShareCapability": "Votre navigateur ne permet pas de partager ou de copier dans le presse papier, veuillez copier l'URL de la page.", + "event.actions.add_to_my_events": "Ajouter à mes évènements", + "event.actions.see_on_gmap": "Voir sur une carte", + "event.errors.cant_create": "Impossible de créer l'événement", + "event.errors.cant_update": "Impossible de modifier l'événement", + "event.add_to_my_events.login": "$t(menu.login)", + "event.add_to_my_events.register": "$t(menu.register)", + "event.add_to_my_events.title": "Vous devez être connecté", + "event.add_to_my_events.text_html": "Pour ajouter <strong>{{eventName}}</strong> à vos carosters vous devez être connecté ou créer un compte.", + "travel.fields.meeting_point": "Lieu de rencontre", + "travel.fields.details": "Notes", + "travel.fields.phone": "Contact", + "travel.creation.date": "Date de départ", + "travel.creation.time": "Heure de départ", + "travel.creation.title": "Ajouter une voiture", + "travel.creation.car.title": "Voiture et contact", + "travel.creation.travel.title": "Trajet", + "travel.creation.name": "Nom de la voiture", + "travel.creation.seats": "Nombre de places disponibles", + "travel.creation.meeting": "Lieu de rencontre", + "travel.creation.phone": "Numéro de téléphone", + "travel.creation.phoneHelper.faq": "/fr/faq", + "travel.creation.phoneHelper.why": "Pourquoi le num. de tél. est-il demandé?", + "travel.creation.notes": "Infos complémentaires", + "travel.creation.created": "La voiture a été créée", + "travel.creation.submit": "Ajouter", + "travel.actions.remove_alert": "Voulez-vous vraiment supprimer cette voiture et ajouter les inscrits à la liste d'attente ?", + "travel.actions.removed": "La voiture a été supprimée et ses passagers déplacés dans la liste d'attente de l'événement.", + "travel.vehicle.add": "Ajouter un nouveau véhicule", + "travel.vehicle.title": "Mes véhicules", + "travel.vehicle.name": "Nom du véhicule", + "travel.vehicle.license_plate": "Plaque d'immatriculation", + "travel.vehicle.seats_number": "Nombre de places", + "travel.vehicle.empty": "Vous n'avez aucun véhicule assigné. Utilisez le bouton ci-dessous pour en créer un.", + "travel.passengers.empty": "Place disponible", + "travel.passengers.add": "Ajouter un passager", + "travel.passengers.add_to_travel": "Ajouter un passager", + "travel.passengers.add_to_waitingList": "Ajouter quelqu'un", + "travel.passengers.add_me": "S'ajouter", + "travel.passengers.add_to_car": "Ajouter à la voiture", + "travel.passengers.register_to_waiting_list": "Inscription à la liste d'attente", + "travel.passengers.add_someone": "Ajouter quelqu'un", + "travel.passengers.location": "Lieu de rencontre", + "travel.passengers.location_helper": "Indiquez votre lieu de départ de préférence", + "travel.passengers.location_placeholder": "Lieu de rencontre (optionnel)", + "travel.passengers.email": "Email", + "travel.passengers.email_placeholder": "Email", + "travel.passengers.email_placeholder_optional": "Email (optionnel)", + "travel.passengers.email_helpertext": "Email non valide", + "travel.passengers.name": "Nom", + "travel.passengers.name_placeholder": "Nom", + "travel.errors.cant_create": "Impossible de créer la voiture", + "travel.errors.cant_update": "Impossible de modifier la voiture", + "travel.errors.cant_remove": "Impossible de supprimer la voiture", + "travel.errors.cant_add_passenger": "Impossible d'ajouter un passager", + "travel.errors.cant_remove_passenger": "Impossible de supprimer le passager", + "travel.moved_to_waiting_list": "Le passager a été déplacé dans la liste d'attente de l'événement.", + "dashboard.title": "$t(menu.dashboard)", + "dashboard.actions.see_event": "Accéder", + "dashboard.actions.add_event": "Créer un caroster", + "dashboard.sections.future": "Caroster à venir", + "dashboard.sections.future_plural": "Carosters à venir", + "dashboard.sections.past": "Caroster passé", + "dashboard.sections.past_plural": "Carosters passés", + "dashboard.sections.noDate": "Caroster sans date", + "dashboard.sections.noDate_plural": "Carosters sans date", + "dashboard.noEvent.title": "Bienvenue sur Caroster", + "dashboard.noEvent.text_html": "Ici, vous y verrez <strong>les carosters auxquels vous participez</strong>, pour commencer créer un Caroster !", + "dashboard.noEvent.create_event": "$t(menu.new_event)", + "profile.title": "Profil", + "profile.firstName": "Prénom", + "profile.lastName": "Nom", + "profile.email": "Email", + "profile.current_password": "Mot de passe actuel", + "profile.new_password": "Nouveau mot de passe", + "profile.password_changed": "Mot de passe mis à jour", + "profile.updated": "Profil mis à jour", + "profile.not_defined": "Non précisé", + "profile.actions.save": "Enregistrer", + "profile.actions.edit": "Editer", + "profile.actions.change_password": "Changer son mot de passe", + "profile.actions.logout": "Se déconnecter", + "profile.actions.cancel": "Annuler", + "profile.actions.save_new_password": "Mettre à jour", + "profile.errors.password_nomatch": "Mot de passe erroné", + "passenger.title": "Liste d'attente", + "passenger.availability.seats": "{{count}} place disponible", + "passenger.availability.seats_plural": "{{count}} places disponibles", + "passenger.creation.seats": "Nombre de passagers: {{seats}}", + "passenger.creation.departure": "Depart: ", + "passenger.creation.assign": "Placer", + "passenger.creation.available_cars": "Voitures disponibles", + "passenger.creation.no_travel.title": "Pas de place disponible en ce moment...", + "passenger.creation.no_travel.desc": "{{name}} recevra un email lorsqu’une voiture sera ajoutée. En attendant, partagez l’événement", + "passenger.actions.remove_alert": "Voulez-vous vraiment supprimer <italic><bold>{{name}}</bold></italic> de la liste d'attente ?", + "passenger.actions.place": "Placer", + "passenger.errors.cant_add_passenger": "Impossible d'ajouter un passager", + "passenger.errors.cant_save_passengers": "Impossible de mettre à jour passagers", + "passenger.errors.cant_remove_passenger": "Impossible de retirer le passager", + "passenger.errors.cant_select_car": "Impossible de sélectionner la voiture", + "passenger.errors.car_full": "Voiture pleine. Impossible d'ajouter un passager supplémentaire", + "passenger.success.added_self_to_car": "Vous avez été ajouté à la voiture", + "passenger.success.added_to_car": "{{name}} a été ajouté à la voiture", + "passenger.success.added_self_to_waitlist": "Vous avez été ajouté à la liste d’attente. Vous serez notifié à l’ajout de nouvelles voitures", + "passenger.success.added_to_waitlist": "{{name}} ajouté à la liste d'attente", + "passenger.success.goToTravels": "Aller aux trajets", + "passenger.input.email": "Votre email", + "passenger.input.email_helper": "Optionnel - Soyez notifié si des voitures sont ajoutées", + "passenger.input.email_helper_car": "Optionnel", + "passenger.deleted": "Le passager a été supprimé de l'événement.", + "signup.title": "Inscription", + "signup.email": "Email", + "signup.firstName": "Prénom", + "signup.lastName": "Nom", + "signup.password": "Mot de passe", + "signup.submit": "Créer son compte", + "signup.login": "$t(menu.login)", + "signup.errors.email_taken": "Cet email est déjà associé à un compte", + "confirm.title": "Confirmez votre compte", + "confirm.text": "Vous avez reçu un email avec un lien. Merci de cliquer sur ce lien pour confirmer votre compte.", + "confirm.login": "Retour à l'écran de connexion", + "signin.title": "Connexion", + "signin.email": "Email", + "signin.password": "Mot de passe", + "signin.login": "$t(menu.login)", + "signin.register": "$t(menu.register)", + "signin.errors": "Vérifier votre email et mot de passe", + "signin.unconfirmed": "Votre compte n'a pas été confirmé. Merci de vérifier vos emails", + "signin.withGoogle": "Utiliser un compte Google", + "lost_password.title": "Récupération de mot de passe", + "lost_password.reset_title": "Définition d'un nouveau mot de passe", + "lost_password.message": "Perdu son mot de passe ?", + "lost_password.email": "Votre email", + "lost_password.password": "Nouveau mot de passe", + "lost_password.password_confirmation": "Confirmation du nouveau mot de passe", + "lost_password.sent": "Un email a été envoyé à {{email}}, avec un lien pour récupérer votre mot de passe", + "lost_password.error": "Cet email n'existe pas", + "lost_password.change_success": "Votre mot de passe a été modifié", + "lost_password.actions.send": "Envoyer un email de récupération", + "lost_password.actions.cancel": "Annuler", + "lost_password.actions.login": "Retour à l'écran de connexion", + "lost_password.actions.resend": "Envoyer à nouveau", + "lost_password.actions.register": "Créer un compte ?", + "lost_password.actions.save_new_password": "Mettre à jour" +}
M frontend/next.config.jsfrontend/next.config.js

@@ -1,3 +1,4 @@

+const {i18n} = require('./react-i18next.config'); const {NODE_ENV, STRAPI_URL = 'http://localhost:1337'} = process.env; const withPWA = require('next-pwa')({

@@ -16,6 +17,7 @@ },

env: { STRAPI_URL: process.env.STRAPI_URL, }, + i18n, async rewrites() { return [
M frontend/package.jsonfrontend/package.json

@@ -27,7 +27,7 @@ "next-pwa": "^5.6.0",

"react": "^18.2.0", "react-dom": "^18.2.0", "react-helmet": "^6.1.0", - "react-i18next": "^11.18.5", + "react-i18next": "^11.18.6", "react-joyride": "^2.5.2", "react-slick": "^0.29.0", "typescript": "^4.8.3",
M frontend/pages/_app.tsxfrontend/pages/_app.tsx

@@ -11,19 +11,15 @@ import {useApollo} from '../lib/apolloClient';

import Metas from '../containers/Metas'; import Toasts from '../components/Toasts'; import theme from '../theme'; -import {useTranslation} from 'react-i18next'; import useProfile from '../hooks/useProfile'; -import {changeLang} from '../lib/i18n'; +import useLocale from '../hooks/useLocale'; +import {I18nextProvider} from 'react-i18next'; +import i18n, {initI18Next} from '../lib/i18n'; const App = function (props: AppProps) { const {Component, pageProps} = props; const apolloClient = useApollo(pageProps); - const {profile} = useProfile(); - const {i18n} = useTranslation(); - - useEffect(() => { - if (profile?.lang) changeLang(profile.lang); - }, [profile]); + const {locale} = useLocale(); useEffect(() => { // Remove the server-side injected CSS.

@@ -33,21 +29,25 @@ jssStyles.parentElement!.removeChild(jssStyles);

} }, []); + initI18Next(locale); + return ( - <ApolloProvider client={apolloClient}> - <Metas metas={pageProps.metas} /> - <ThemeProvider theme={theme}> - <MuiPickersUtilsProvider - libInstance={moment} - utils={MomentUtils} - locale={i18n.language === 'fr' ? 'fr-ch' : 'en'} - > - <CssBaseline /> - <Component {...pageProps} /> - <Toasts /> - </MuiPickersUtilsProvider> - </ThemeProvider> - </ApolloProvider> + <I18nextProvider i18n={i18n}> + <ApolloProvider client={apolloClient}> + <Metas metas={pageProps.metas} /> + <ThemeProvider theme={theme}> + <MuiPickersUtilsProvider + libInstance={moment} + utils={MomentUtils} + locale={locale === 'fr' ? 'fr-ch' : 'en'} + > + <CssBaseline /> + <Component {...pageProps} /> + <Toasts /> + </MuiPickersUtilsProvider> + </ThemeProvider> + </ApolloProvider> + </I18nextProvider> ); };
M frontend/pages/auth/confirm.tsxfrontend/pages/auth/confirm.tsx

@@ -8,12 +8,13 @@ import Typography from '@material-ui/core/Typography';

import {useTranslation} from 'react-i18next'; import Layout from '../../layouts/Centered'; import Logo from '../../components/Logo'; +import Link from 'next/link'; const Confirm = () => { const {t} = useTranslation(); return ( - <Layout displayMenu={false}> + <Layout displayMenu={false}> <Card> <CardMedia component={Logo} /> <CardContent>

@@ -26,14 +27,15 @@ </Typography>

</CardContent> <CardActionArea> <CardActions> - <Button - color="primary" - variant="contained" - href={'/auth/login'} - id="SignUpSuccessLogin" - > - {t('confirm.login')} - </Button> + <Link href="/auth/login" passHref> + <Button + color="primary" + variant="contained" + id="SignUpSuccessLogin" + > + {t('confirm.login')} + </Button> + </Link> </CardActions> </CardActionArea> </Card>
A frontend/react-i18next.config.js

@@ -0,0 +1,8 @@

+module.exports = { + i18n: { + defaultLocale: 'fr', + locales: ['en', 'fr'], + + fallbackLng: 'fr' + }, +};
M frontend/yarn.lockfrontend/yarn.lock

@@ -5084,10 +5084,10 @@ prop-types "^15.7.2"

react-fast-compare "^3.1.1" react-side-effect "^2.1.0" -react-i18next@^11.18.5: - version "11.18.5" - resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-11.18.5.tgz#985e87bc66ed1316381b464a4ecfd35a2c951357" - integrity sha512-cKcyuuzIv0YUZ4l9WORflVNuhISPAqQShOAsxwFyYuJoCA7HlLmHm7XnvO6hfAGmGpDNRhJHoBX8hG49Cb2xZQ== +react-i18next@^11.18.6: + version "11.18.6" + resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-11.18.6.tgz#e159c2960c718c1314f1e8fcaa282d1c8b167887" + integrity sha512-yHb2F9BiT0lqoQDt8loZ5gWP331GwctHz9tYQ8A2EIEUu+CcEdjBLQWli1USG3RdWQt3W+jqQLg/d4rrQR96LA== dependencies: "@babel/runtime" "^7.14.5" html-parse-stringify "^3.0.1"