diff --git a/Gemfile b/Gemfile index 1875cec890..afe2b14402 100644 --- a/Gemfile +++ b/Gemfile @@ -197,3 +197,4 @@ gem "ruby-openai", "~> 7.0" gem "anycable-rails", "~> 1.5" gem "react_on_rails", "~> 14.0" gem "psych", "~> 5.2.3" +gem "i18n-tasks", "~> 1.0" diff --git a/app/javascript/components/AffiliatesDashboard/AffiliateSignupForm.tsx b/app/javascript/components/AffiliatesDashboard/AffiliateSignupForm.tsx index fbe41ffa12..3830182046 100644 --- a/app/javascript/components/AffiliatesDashboard/AffiliateSignupForm.tsx +++ b/app/javascript/components/AffiliatesDashboard/AffiliateSignupForm.tsx @@ -2,6 +2,7 @@ import cx from "classnames"; import * as React from "react"; import { Link, useLoaderData } from "react-router-dom"; import { cast } from "ts-safe-cast"; +import { useTranslation } from "react-i18next"; import { submitAffiliateSignupForm, @@ -41,6 +42,7 @@ const validateProduct = (product: SelfServeAffiliateProduct): InvalidProductAttr }; export const AffiliateSignupForm = () => { + const { t } = useTranslation('common'); const data = cast(useLoaderData()); const loggedInUser = useLoggedInUser(); const [isSaving, setIsSaving] = React.useState(false); @@ -57,17 +59,17 @@ export const AffiliateSignupForm = () => { const handleSaveChanges = asyncVoid(async () => { if (products.some((product) => validateProduct(product).size > 0)) { - showAlert("There are some errors on the page. Please fix them and try again.", "error"); + showAlert(t("errors.fix_errors_and_try_again"), "error"); return; } try { setIsSaving(true); await submitAffiliateSignupForm({ products, disable_global_affiliate: disableGlobalAffiliate }); - showAlert("Changes saved!", "success"); + showAlert(t("actions.changes_saved"), "success"); } catch (e) { assertResponseError(e); - showAlert(`An error occurred while saving changes${e.message ? ` - ${e.message}` : ""}`, "error"); + showAlert(t("errors.error_saving_changes", { message: e.message || "" }), "error"); } finally { setIsSaving(false); } diff --git a/app/javascript/components/Authentication/ForgotPasswordForm.tsx b/app/javascript/components/Authentication/ForgotPasswordForm.tsx index 4e5cb0c64f..afae90eae5 100644 --- a/app/javascript/components/Authentication/ForgotPasswordForm.tsx +++ b/app/javascript/components/Authentication/ForgotPasswordForm.tsx @@ -1,4 +1,5 @@ import * as React from "react"; +import { useTranslation } from "react-i18next"; import { renewPassword } from "$app/data/login"; import { assertResponseError } from "$app/utils/request"; @@ -10,6 +11,7 @@ import { showAlert } from "$app/components/server-components/Alert"; type SaveState = { type: "initial" | "submitting" } | { type: "error"; message: string }; export const ForgotPasswordForm = ({ onClose }: { onClose: () => void }) => { + const { t } = useTranslation('authentication'); const uid = React.useId(); const [email, setEmail] = React.useState(""); const [saveState, setSaveState] = React.useState({ type: "initial" }); @@ -19,7 +21,7 @@ export const ForgotPasswordForm = ({ onClose }: { onClose: () => void }) => { setSaveState({ type: "submitting" }); try { await renewPassword(email); - showAlert("Password reset sent! Please make sure to check your spam folder.", "success"); + showAlert(t("forgot_password.password_reset_sent"), "success"); setSaveState({ type: "initial" }); } catch (e) { assertResponseError(e); @@ -31,7 +33,7 @@ export const ForgotPasswordForm = ({ onClose }: { onClose: () => void }) => {
void handleSubmit(e)}>
- or + {t("forgot_password.or")}
{saveState.type === "error" ? ( @@ -41,14 +43,14 @@ export const ForgotPasswordForm = ({ onClose }: { onClose: () => void }) => { ) : null}
- + setEmail(e.target.value)} required />
- +
); diff --git a/app/javascript/components/BundleEdit/ContentTab/BundleContentUpdatedStatus.tsx b/app/javascript/components/BundleEdit/ContentTab/BundleContentUpdatedStatus.tsx index 8a2987e94c..a23394ce77 100644 --- a/app/javascript/components/BundleEdit/ContentTab/BundleContentUpdatedStatus.tsx +++ b/app/javascript/components/BundleEdit/ContentTab/BundleContentUpdatedStatus.tsx @@ -1,4 +1,5 @@ import * as React from "react"; +import { useTranslation } from "react-i18next"; import { updatePurchasesContent } from "$app/data/bundle"; import { assertResponseError } from "$app/utils/request"; @@ -8,6 +9,7 @@ import { Button } from "$app/components/Button"; import { showAlert } from "$app/components/server-components/Alert"; export const BundleContentUpdatedStatus = () => { + const { t } = useTranslation('common'); const { id } = useBundleEditContext(); const [isHidden, setIsHidden] = React.useState(false); const [isLoading, setIsLoading] = React.useState(false); @@ -16,7 +18,7 @@ export const BundleContentUpdatedStatus = () => { setIsLoading(true); try { await updatePurchasesContent(id); - showAlert("Queued an update to the content of all outdated purchases.", "success"); + showAlert(t("actions.queued_content_update"), "success"); setIsHidden(true); } catch (e) { assertResponseError(e); diff --git a/app/javascript/components/BundleEdit/Layout.tsx b/app/javascript/components/BundleEdit/Layout.tsx index 50be4067ed..fc0aec2d21 100644 --- a/app/javascript/components/BundleEdit/Layout.tsx +++ b/app/javascript/components/BundleEdit/Layout.tsx @@ -1,5 +1,6 @@ import * as React from "react"; import { Link, useMatches, useNavigate } from "react-router-dom"; +import { useTranslation } from "react-i18next"; import { saveBundle } from "$app/data/bundle"; import { setProductPublished } from "$app/data/publish_product"; @@ -42,12 +43,13 @@ export const Layout = ({ const [match] = useMatches(); const tab = match?.handle ?? "product"; + const { t } = useTranslation('common'); const [isSaving, setIsSaving] = React.useState(false); const handleSave = async () => { try { setIsSaving(true); await saveBundle(id, bundle); - showAlert("Changes saved!", "success"); + showAlert(t("actions.changes_saved"), "success"); } catch (e) { assertResponseError(e); showAlert(e.message, "error"); @@ -62,7 +64,7 @@ export const Layout = ({ await saveBundle(id, bundle); await setProductPublished(uniquePermalink, published); updateBundle({ is_published: published }); - showAlert(published ? "Published!" : "Unpublished!", "success"); + showAlert(published ? t("actions.published") : t("actions.unpublished"), "success"); if (tab === "share") navigate(`/bundles/${id}/content`); } catch (e) { assertResponseError(e); @@ -87,9 +89,9 @@ export const Layout = ({ const onTabClick = (e: React.MouseEvent, callback?: () => void) => { const message = isUploadingFiles - ? "Some files are still uploading, please wait..." + ? t("errors.files_still_uploading") : isUploadingFilesOrImages - ? "Some images are still uploading, please wait..." + ? t("errors.images_still_uploading") : undefined; if (message) { @@ -155,7 +157,7 @@ export const Layout = ({ if (!bundle.is_published) { evt.preventDefault(); showAlert( - "Not yet! You've got to publish your awesome product before you can share it with your audience and the world.", + t("errors.must_publish_before_share"), "warning", ); } diff --git a/app/javascript/components/Checkout/GiftForm.tsx b/app/javascript/components/Checkout/GiftForm.tsx index 230c7b4e4e..6bc19e8e05 100644 --- a/app/javascript/components/Checkout/GiftForm.tsx +++ b/app/javascript/components/Checkout/GiftForm.tsx @@ -1,5 +1,6 @@ import cx from "classnames"; import * as React from "react"; +import { useTranslation } from "react-i18next"; import { Button } from "$app/components/Button"; import { useState, getErrors } from "$app/components/Checkout/payment"; @@ -7,6 +8,7 @@ import { Icon } from "$app/components/Icons"; import { Modal } from "$app/components/Modal"; export const GiftForm = ({ isMembership }: { isMembership: boolean }) => { + const { t } = useTranslation('checkout'); const giftEmailUID = React.useId(); const giftNoteUID = React.useId(); const [cancellingPresetGift, setCancellingPresetGift] = React.useState(false); @@ -20,7 +22,7 @@ export const GiftForm = ({ isMembership }: { isMembership: boolean }) => {