- {`${shortenName(user1.name)}'s preferred answers`}
+ {t('answers.modal.preferred_of_user', "{name}'s preferred answers", { name: shortenName(user1.name) })}
- {shortenName(user1.name)} marked this as{' '}
+ {t('answers.modal.user_marked', '{name} marked this as ', { name: shortenName(user1.name) })}
@@ -666,13 +673,14 @@ function CompatibilityDisplay(props: {
/>
- {`${
- isCurrentUser ? 'Your' : shortenName(user2.name) + `'s`
- } preferred answers`}
+ {isCurrentUser
+ ? t('answers.modal.your_preferred', 'Your preferred answers')
+ : t('answers.modal.preferred_of_user', "{name}'s preferred answers", { name: shortenName(user2.name) })}
- {isCurrentUser ? 'You' : shortenName(user2.name)} marked this
- as{' '}
+ {isCurrentUser
+ ? t('answers.modal.you_marked', 'You marked this as ')
+ : t('answers.modal.user_marked', '{name} marked this as ', { name: shortenName(user2.name) })}
diff --git a/web/components/answers/free-response-add-question.tsx b/web/components/answers/free-response-add-question.tsx
index 17b03e2..e5f5d2d 100644
--- a/web/components/answers/free-response-add-question.tsx
+++ b/web/components/answers/free-response-add-question.tsx
@@ -10,6 +10,7 @@ import {IndividualQuestionRow} from '../questions-form'
import {TbMessage} from 'react-icons/tb'
import {OtherProfileAnswers} from './other-profile-answers'
import {usePersistentInMemoryState} from 'web/hooks/use-persistent-in-memory-state'
+import {useT} from 'web/lib/locale'
export function AddQuestionButton(props: {
isFirstQuestion?: boolean
@@ -22,12 +23,13 @@ export function AddQuestionButton(props: {
false,
`add-question-${user.id}`
)
+ const t = useT()
return (
<>
@@ -91,7 +94,7 @@ function AddQuestionModal(props: {
) : selectedQuestion == null ? (
<>
- Choose a question to answer
+ {t('answers.free.choose_question', 'Choose a question to answer')}
{addableQuestions.map((question) => {
diff --git a/web/components/answers/free-response-display.tsx b/web/components/answers/free-response-display.tsx
index eb65df3..dc7920b 100644
--- a/web/components/answers/free-response-display.tsx
+++ b/web/components/answers/free-response-display.tsx
@@ -27,6 +27,7 @@ import { partition } from 'lodash'
import { shortenName } from 'web/components/widgets/user-link'
import { AddQuestionButton } from './free-response-add-question'
import { Profile } from 'common/profiles/profile'
+import {useT} from 'web/lib/locale'
export function FreeResponseDisplay(props: {
isCurrentUser: boolean
@@ -34,6 +35,7 @@ export function FreeResponseDisplay(props: {
fromProfilePage: Profile | undefined
}) {
const { isCurrentUser, user, fromProfilePage } = props
+ const t = useT()
const { refreshAnswers, answers: allAnswers } = useUserAnswers(user?.id)
@@ -59,9 +61,11 @@ export function FreeResponseDisplay(props: {
return (
- {`${
- isCurrentUser ? 'Your' : shortenName(user.name) + `'s`
- } Free Response`}
+
+ {isCurrentUser
+ ? t('answers.free.your_title', 'Your Free Response')
+ : t('answers.free.user_title', "{name}'s Free Response", { name: shortenName(user.name) })}
+
@@ -101,6 +105,7 @@ function AnswerBlock(props: {
const { answer, questions, isCurrentUser, user, refreshAnswers } = props
const question = questions.find((q) => q.id === answer.question_id)
const [edit, setEdit] = useState(false)
+ const t = useT()
const [otherAnswerModal, setOtherAnswerModal] = useState(false)
@@ -119,18 +124,18 @@ function AnswerBlock(props: {
,
onClick: () => setEdit(true),
},
{
- name: 'Delete',
+ name: t('answers.menu.delete', 'Delete'),
icon: ,
onClick: () =>
deleteAnswer(answer, user.id).then(() => refreshAnswers()),
},
{
- name: `See ${question.answer_count} other answers`,
+ name: t('answers.free.see_others', 'See {count} other answers', { count: String(question.answer_count) }),
icon: ,
onClick: () => setOtherAnswerModal(true),
},
diff --git a/web/components/answers/opinion-scale-display.tsx b/web/components/answers/opinion-scale-display.tsx
index ab091b5..2558161 100644
--- a/web/components/answers/opinion-scale-display.tsx
+++ b/web/components/answers/opinion-scale-display.tsx
@@ -10,6 +10,7 @@ import { Col } from 'web/components/layout/col'
import { Row } from 'web/components/layout/row'
import { Subtitle } from '../widgets/profile-subtitle'
import { BiTachometer } from 'react-icons/bi'
+import {useT} from 'web/lib/locale'
export function OpinionScale(props: {
multiChoiceAnswers: rowFor<'compatibility_answers_free'>[]
@@ -17,6 +18,7 @@ export function OpinionScale(props: {
isCurrentUser: boolean
}) {
const { multiChoiceAnswers, questions, isCurrentUser } = props
+ const t = useT()
const answeredMultiChoice = multiChoiceAnswers.filter(
(a) => a.multiple_choice != null && a.multiple_choice != -1
@@ -28,7 +30,7 @@ export function OpinionScale(props: {
)
@@ -39,7 +41,7 @@ export function OpinionScale(props: {
return (
- Opinion Scale
+ {t('answers.opinion.title', 'Opinion Scale')}
{isCurrentUser && (
)}
diff --git a/web/lib/locale.ts b/web/lib/locale.ts
index 2305d86..961212a 100644
--- a/web/lib/locale.ts
+++ b/web/lib/locale.ts
@@ -9,7 +9,8 @@ export type I18nContextType = {
export const I18nContext = createContext({
locale: defaultLocale,
- setLocale: () => {}
+ setLocale: () => {
+ }
})
export function useLocale() {
@@ -47,5 +48,17 @@ export function useT() {
.catch(() => setMessages({}))
}, [locale])
- return (key: string, fallback: string) => locale === defaultLocale ? fallback : messages[key] ?? fallback
+ return (key: string, fallback: string, formatter?: any) => {
+ const result = locale === defaultLocale ? fallback : messages[key] ?? fallback
+ if (!formatter) return result
+ if (typeof formatter === 'function') return formatter(result)
+ if (typeof formatter === 'object') {
+ let text = String(result)
+ for (const [k, v] of Object.entries(formatter)) {
+ text = text.replace(new RegExp(`\\{${k}\\}`, 'g'), String(v))
+ }
+ return text
+ }
+ return result
+ }
}
diff --git a/web/messages/fr.json b/web/messages/fr.json
index 504db5b..7fddf42 100644
--- a/web/messages/fr.json
+++ b/web/messages/fr.json
@@ -481,5 +481,27 @@
"block_user.toast.success": "Vous ne verrez plus le contenu de cet utilisateur",
"block_user.toast.error": "Erreur lors du blocage de l'utilisateur",
"block_user.unblock": "Débloquer",
- "block_user.block": "Bloquer"
+ "block_user.block": "Bloquer",
+ "answers.add.submit_own": "Proposez la vôtre !",
+ "answers.add.error_create": "Erreur lors de la création de la question de compatibilité. Réessayez ?",
+ "answers.answer.cta": "Répondre{core} aux questions",
+ "answers.answer.answer_yourself": "Répondez vous-même",
+ "answers.answer.view_list": "Voir la liste des questions",
+ "answers.answer.answer_skipped": "Répondre à {n} questions ignorées",
+ "answers.preferred.your_answer": "Votre réponse",
+ "answers.preferred.user_answer": "La réponse de {name}",
+ "answers.menu.edit": "Modifier",
+ "answers.menu.delete": "Supprimer",
+ "answers.menu.skip": "Ignorer",
+ "answers.compatible": "Compatible",
+ "answers.incompatible": "Incompatible",
+ "answers.modal.preferred_of_user": "Les réponses préférées de {name}",
+ "answers.modal.user_marked": "{name} a marqué ceci comme ",
+ "answers.modal.your_preferred": "Vos réponses préférées",
+ "answers.modal.you_marked": "Vous avez marqué ceci comme ",
+ "answers.opinion.fill": "Remplir l'échelle d'opinion",
+ "answers.opinion.edit": "Modifier",
+ "answers.free.add_free_response": "Ajouter une réponse libre",
+ "answers.free.choose_question": "Choisissez une question à laquelle répondre",
+ "answers.free.see_others": "Voir {count} autres réponses"
}
\ No newline at end of file