From 5c9766a5356c5b5124d31de18210579aa903055e Mon Sep 17 00:00:00 2001 From: olivierdelcroix Date: Tue, 18 Nov 2025 16:18:47 +0100 Subject: [PATCH 01/12] use PasswordCreationDoubleInput in register --- frontend/knip.config.ts | 1 + frontend/locales/en.json | 1 + .../PasswordCreationDoubleInput.tsx | 24 +++-- frontend/src/external/mount.tsx | 45 +++++++++ .../external/register/PasswordDoubleInput.tsx | 61 ++++++++++++ frontend/src/gql/gql.ts | 12 +-- frontend/src/gql/graphql.ts | 96 +++++++++---------- frontend/src/routes/password.change.index.tsx | 6 ++ .../src/routes/password.recovery.index.tsx | 6 ++ templates/pages/register/password.html | 21 ++-- translations/en.json | 2 +- 11 files changed, 205 insertions(+), 70 deletions(-) create mode 100644 frontend/src/external/mount.tsx create mode 100644 frontend/src/external/register/PasswordDoubleInput.tsx diff --git a/frontend/knip.config.ts b/frontend/knip.config.ts index be6e73ebd..250a6604a 100644 --- a/frontend/knip.config.ts +++ b/frontend/knip.config.ts @@ -12,6 +12,7 @@ export default { "src/routeTree.gen.ts", ".storybook/locales.ts", "i18next.config.ts", + "src/external/**", ], ignoreDependencies: [ // This is used by the tailwind PostCSS plugin, but not detected by knip diff --git a/frontend/locales/en.json b/frontend/locales/en.json index 731eab2f0..054adcd60 100644 --- a/frontend/locales/en.json +++ b/frontend/locales/en.json @@ -29,6 +29,7 @@ "loading": "Loading…", "next": "Next", "password": "Password", + "password_confirm": "common.password_confirm", "previous": "Previous", "saved": "Saved", "saving": "Saving…" diff --git a/frontend/src/components/PasswordCreationDoubleInput.tsx b/frontend/src/components/PasswordCreationDoubleInput.tsx index 6724463c5..c7c14ea35 100644 --- a/frontend/src/components/PasswordCreationDoubleInput.tsx +++ b/frontend/src/components/PasswordCreationDoubleInput.tsx @@ -55,9 +55,17 @@ const usePasswordComplexity = (password: string): PasswordComplexity => { export default function PasswordCreationDoubleInput({ siteConfig, forceShowNewPasswordInvalid, + newPasswordFieldName, + newPasswordLabel, + newPasswordAgainFieldName, + newPasswordAgainLabel, }: { siteConfig: FragmentType; forceShowNewPasswordInvalid: boolean; + newPasswordFieldName: string; + newPasswordLabel: string; + newPasswordAgainFieldName: string; + newPasswordAgainLabel: string; }): React.ReactElement { const { t } = useTranslation(); const { minimumPasswordComplexity } = useFragment( @@ -81,10 +89,8 @@ export default function PasswordCreationDoubleInput({ return ( <> - - - {t("frontend.password_change.new_password_label")} - + + {newPasswordLabel} - + {/* TODO This field has validation defects, some caused by Radix-UI upstream bugs. https://github.com/matrix-org/matrix-authentication-service/issues/2855 */} - - {t("frontend.password_change.new_password_again_label")} - + {newPasswordAgainLabel} - v !== form.get("new_password")}> + v !== form.get(newPasswordFieldName)} + > {t("frontend.password_change.passwords_no_match")} diff --git a/frontend/src/external/mount.tsx b/frontend/src/external/mount.tsx new file mode 100644 index 000000000..15148e1c6 --- /dev/null +++ b/frontend/src/external/mount.tsx @@ -0,0 +1,45 @@ +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { TooltipProvider } from "@vector-im/compound-web"; +import { createElement, StrictMode, Suspense } from "react"; +import ReactDOM from "react-dom/client"; +import { I18nextProvider } from "react-i18next"; +import ErrorBoundary from "../components/ErrorBoundary"; +import i18n, { setupI18n } from "../i18n"; +import "../shared.css"; + +setupI18n(); + +export function mountWithProviders

>( + selector: string, + Component: React.ComponentType

, + defaultProps?: Partial

, +) { + try { + const el = document.querySelector(selector); + if (!el) throw new Error(`can not find ${selector} in DOM`); + const propsJSON = el.getAttribute("data-props") || "{}"; + const parsedProps = JSON.parse(propsJSON); + const props = { ...(defaultProps ?? {}), ...(parsedProps ?? {}) }; + const queryClient = new QueryClient(); + ReactDOM.createRoot(el).render( + + + + + {`Loading... ${selector}…`}}> + + {createElement( + Component as React.ComponentType, + props as P, + )} + + + + + + , + ); + } catch (err) { + console.error(`Cannot mount component on ${selector}:`, err); + } +} diff --git a/frontend/src/external/register/PasswordDoubleInput.tsx b/frontend/src/external/register/PasswordDoubleInput.tsx new file mode 100644 index 000000000..aa198e4e6 --- /dev/null +++ b/frontend/src/external/register/PasswordDoubleInput.tsx @@ -0,0 +1,61 @@ +import { queryOptions, useSuspenseQuery } from "@tanstack/react-query"; +import { Form } from "@vector-im/compound-web"; +import { graphql } from "../../gql"; +import { graphqlRequest } from "../../graphql"; +import { mountWithProviders } from "../mount"; +import "../../shared.css"; +import PasswordCreationDoubleInput from "../../components/PasswordCreationDoubleInput"; +import { useTranslation } from "react-i18next"; + +const HTML_ID = "#password-double-input"; + +const QUERY = graphql(/* GraphQL */ ` + query PasswordChange { + viewer { + __typename + ... on Node { + id + } + } + + siteConfig { + ...PasswordCreationDoubleInput_siteConfig + } + } +`); + +const query = queryOptions({ + queryKey: ["passwordChange"], + queryFn: ({ signal }) => graphqlRequest({ query: QUERY, signal }), +}); + +type PasswordDoubleInputProps = { forceShowNewPasswordInvalid: boolean }; + +function PasswordDoubleInput({ + forceShowNewPasswordInvalid, +}: PasswordDoubleInputProps) { + const { + data: { siteConfig }, + } = useSuspenseQuery(query); + const { t } = useTranslation(); + + return ( + // Form.Root is needed because Form.Field requires to be included into a Form + // asChild allows to replace Form.Root component by the child, the

used is in the password.html + +
+ +
+
+ ); +} + +// Allow mounting under either the new specific id or the legacy #view +mountWithProviders(HTML_ID, PasswordDoubleInput); diff --git a/frontend/src/gql/gql.ts b/frontend/src/gql/gql.ts index 635e117ca..af2ae93d6 100644 --- a/frontend/src/gql/gql.ts +++ b/frontend/src/gql/gql.ts @@ -49,6 +49,7 @@ type Documents = { "\n fragment UserEmailList_user on User {\n hasPassword\n }\n": typeof types.UserEmailList_UserFragmentDoc, "\n fragment UserEmailList_siteConfig on SiteConfig {\n emailChangeAllowed\n passwordLoginEnabled\n }\n": typeof types.UserEmailList_SiteConfigFragmentDoc, "\n fragment BrowserSessionsOverview_user on User {\n id\n\n browserSessions(first: 0, state: ACTIVE) {\n totalCount\n }\n }\n": typeof types.BrowserSessionsOverview_UserFragmentDoc, + "\n query PasswordChange {\n viewer {\n __typename\n ... on Node {\n id\n }\n }\n\n siteConfig {\n ...PasswordCreationDoubleInput_siteConfig\n }\n }\n": typeof types.PasswordChangeDocument, "\n query UserProfile {\n viewerSession {\n __typename\n ... on BrowserSession {\n id\n user {\n ...AddEmailForm_user\n ...UserEmailList_user\n ...AccountDeleteButton_user\n hasPassword\n emails(first: 0) {\n totalCount\n }\n }\n }\n }\n\n siteConfig {\n emailChangeAllowed\n passwordLoginEnabled\n accountDeactivationAllowed\n ...AddEmailForm_siteConfig\n ...UserEmailList_siteConfig\n ...PasswordChange_siteConfig\n ...AccountDeleteButton_siteConfig\n }\n }\n": typeof types.UserProfileDocument, "\n query PlanManagementTab {\n siteConfig {\n planManagementIframeUri\n }\n }\n": typeof types.PlanManagementTabDocument, "\n query BrowserSessionList(\n $first: Int\n $after: String\n $last: Int\n $before: String\n $lastActive: DateFilter\n ) {\n viewerSession {\n __typename\n ... on BrowserSession {\n id\n\n user {\n id\n\n browserSessions(\n first: $first\n after: $after\n last: $last\n before: $before\n lastActive: $lastActive\n state: ACTIVE\n ) {\n totalCount\n\n edges {\n cursor\n node {\n id\n ...BrowserSession_session\n }\n }\n\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n }\n }\n": typeof types.BrowserSessionListDocument, @@ -62,7 +63,6 @@ type Documents = { "\n mutation DoVerifyEmail($id: ID!, $code: String!) {\n completeEmailAuthentication(input: { id: $id, code: $code }) {\n status\n }\n }\n": typeof types.DoVerifyEmailDocument, "\n mutation ResendEmailAuthenticationCode($id: ID!, $language: String!) {\n resendEmailAuthenticationCode(input: { id: $id, language: $language }) {\n status\n }\n }\n": typeof types.ResendEmailAuthenticationCodeDocument, "\n mutation ChangePassword(\n $userId: ID!\n $oldPassword: String!\n $newPassword: String!\n ) {\n setPassword(\n input: {\n userId: $userId\n currentPassword: $oldPassword\n newPassword: $newPassword\n }\n ) {\n status\n }\n }\n": typeof types.ChangePasswordDocument, - "\n query PasswordChange {\n viewer {\n __typename\n ... on Node {\n id\n }\n }\n\n siteConfig {\n ...PasswordCreationDoubleInput_siteConfig\n }\n }\n": typeof types.PasswordChangeDocument, "\n mutation RecoverPassword($ticket: String!, $newPassword: String!) {\n setPasswordByRecovery(\n input: { ticket: $ticket, newPassword: $newPassword }\n ) {\n status\n }\n }\n": typeof types.RecoverPasswordDocument, "\n mutation ResendRecoveryEmail($ticket: String!) {\n resendRecoveryEmail(input: { ticket: $ticket }) {\n status\n progressUrl\n }\n }\n": typeof types.ResendRecoveryEmailDocument, "\n fragment RecoverPassword_userRecoveryTicket on UserRecoveryTicket {\n username\n email\n }\n": typeof types.RecoverPassword_UserRecoveryTicketFragmentDoc, @@ -106,6 +106,7 @@ const documents: Documents = { "\n fragment UserEmailList_user on User {\n hasPassword\n }\n": types.UserEmailList_UserFragmentDoc, "\n fragment UserEmailList_siteConfig on SiteConfig {\n emailChangeAllowed\n passwordLoginEnabled\n }\n": types.UserEmailList_SiteConfigFragmentDoc, "\n fragment BrowserSessionsOverview_user on User {\n id\n\n browserSessions(first: 0, state: ACTIVE) {\n totalCount\n }\n }\n": types.BrowserSessionsOverview_UserFragmentDoc, + "\n query PasswordChange {\n viewer {\n __typename\n ... on Node {\n id\n }\n }\n\n siteConfig {\n ...PasswordCreationDoubleInput_siteConfig\n }\n }\n": types.PasswordChangeDocument, "\n query UserProfile {\n viewerSession {\n __typename\n ... on BrowserSession {\n id\n user {\n ...AddEmailForm_user\n ...UserEmailList_user\n ...AccountDeleteButton_user\n hasPassword\n emails(first: 0) {\n totalCount\n }\n }\n }\n }\n\n siteConfig {\n emailChangeAllowed\n passwordLoginEnabled\n accountDeactivationAllowed\n ...AddEmailForm_siteConfig\n ...UserEmailList_siteConfig\n ...PasswordChange_siteConfig\n ...AccountDeleteButton_siteConfig\n }\n }\n": types.UserProfileDocument, "\n query PlanManagementTab {\n siteConfig {\n planManagementIframeUri\n }\n }\n": types.PlanManagementTabDocument, "\n query BrowserSessionList(\n $first: Int\n $after: String\n $last: Int\n $before: String\n $lastActive: DateFilter\n ) {\n viewerSession {\n __typename\n ... on BrowserSession {\n id\n\n user {\n id\n\n browserSessions(\n first: $first\n after: $after\n last: $last\n before: $before\n lastActive: $lastActive\n state: ACTIVE\n ) {\n totalCount\n\n edges {\n cursor\n node {\n id\n ...BrowserSession_session\n }\n }\n\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n }\n }\n": types.BrowserSessionListDocument, @@ -119,7 +120,6 @@ const documents: Documents = { "\n mutation DoVerifyEmail($id: ID!, $code: String!) {\n completeEmailAuthentication(input: { id: $id, code: $code }) {\n status\n }\n }\n": types.DoVerifyEmailDocument, "\n mutation ResendEmailAuthenticationCode($id: ID!, $language: String!) {\n resendEmailAuthenticationCode(input: { id: $id, language: $language }) {\n status\n }\n }\n": types.ResendEmailAuthenticationCodeDocument, "\n mutation ChangePassword(\n $userId: ID!\n $oldPassword: String!\n $newPassword: String!\n ) {\n setPassword(\n input: {\n userId: $userId\n currentPassword: $oldPassword\n newPassword: $newPassword\n }\n ) {\n status\n }\n }\n": types.ChangePasswordDocument, - "\n query PasswordChange {\n viewer {\n __typename\n ... on Node {\n id\n }\n }\n\n siteConfig {\n ...PasswordCreationDoubleInput_siteConfig\n }\n }\n": types.PasswordChangeDocument, "\n mutation RecoverPassword($ticket: String!, $newPassword: String!) {\n setPasswordByRecovery(\n input: { ticket: $ticket, newPassword: $newPassword }\n ) {\n status\n }\n }\n": types.RecoverPasswordDocument, "\n mutation ResendRecoveryEmail($ticket: String!) {\n resendRecoveryEmail(input: { ticket: $ticket }) {\n status\n progressUrl\n }\n }\n": types.ResendRecoveryEmailDocument, "\n fragment RecoverPassword_userRecoveryTicket on UserRecoveryTicket {\n username\n email\n }\n": types.RecoverPassword_UserRecoveryTicketFragmentDoc, @@ -265,6 +265,10 @@ export function graphql(source: "\n fragment UserEmailList_siteConfig on SiteCo * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function graphql(source: "\n fragment BrowserSessionsOverview_user on User {\n id\n\n browserSessions(first: 0, state: ACTIVE) {\n totalCount\n }\n }\n"): typeof import('./graphql').BrowserSessionsOverview_UserFragmentDoc; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n query PasswordChange {\n viewer {\n __typename\n ... on Node {\n id\n }\n }\n\n siteConfig {\n ...PasswordCreationDoubleInput_siteConfig\n }\n }\n"): typeof import('./graphql').PasswordChangeDocument; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ @@ -317,10 +321,6 @@ export function graphql(source: "\n mutation ResendEmailAuthenticationCode($id: * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function graphql(source: "\n mutation ChangePassword(\n $userId: ID!\n $oldPassword: String!\n $newPassword: String!\n ) {\n setPassword(\n input: {\n userId: $userId\n currentPassword: $oldPassword\n newPassword: $newPassword\n }\n ) {\n status\n }\n }\n"): typeof import('./graphql').ChangePasswordDocument; -/** - * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. - */ -export function graphql(source: "\n query PasswordChange {\n viewer {\n __typename\n ... on Node {\n id\n }\n }\n\n siteConfig {\n ...PasswordCreationDoubleInput_siteConfig\n }\n }\n"): typeof import('./graphql').PasswordChangeDocument; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ diff --git a/frontend/src/gql/graphql.ts b/frontend/src/gql/graphql.ts index 583ebfc5d..92a12c3c8 100644 --- a/frontend/src/gql/graphql.ts +++ b/frontend/src/gql/graphql.ts @@ -1852,6 +1852,17 @@ export type UserEmailList_SiteConfigFragment = { __typename?: 'SiteConfig', emai export type BrowserSessionsOverview_UserFragment = { __typename?: 'User', id: string, browserSessions: { __typename?: 'BrowserSessionConnection', totalCount: number } } & { ' $fragmentName'?: 'BrowserSessionsOverview_UserFragment' }; +export type PasswordChangeQueryVariables = Exact<{ [key: string]: never; }>; + + +export type PasswordChangeQuery = { __typename?: 'Query', viewer: + | { __typename: 'Anonymous', id: string } + | { __typename: 'User', id: string } + , siteConfig: ( + { __typename?: 'SiteConfig' } + & { ' $fragmentRefs'?: { 'PasswordCreationDoubleInput_SiteConfigFragment': PasswordCreationDoubleInput_SiteConfigFragment } } + ) }; + export type UserProfileQueryVariables = Exact<{ [key: string]: never; }>; @@ -1999,17 +2010,6 @@ export type ChangePasswordMutationVariables = Exact<{ export type ChangePasswordMutation = { __typename?: 'Mutation', setPassword: { __typename?: 'SetPasswordPayload', status: SetPasswordStatus } }; -export type PasswordChangeQueryVariables = Exact<{ [key: string]: never; }>; - - -export type PasswordChangeQuery = { __typename?: 'Query', viewer: - | { __typename: 'Anonymous', id: string } - | { __typename: 'User', id: string } - , siteConfig: ( - { __typename?: 'SiteConfig' } - & { ' $fragmentRefs'?: { 'PasswordCreationDoubleInput_SiteConfigFragment': PasswordCreationDoubleInput_SiteConfigFragment } } - ) }; - export type RecoverPasswordMutationVariables = Exact<{ ticket: Scalars['String']['input']; newPassword: Scalars['String']['input']; @@ -2580,6 +2580,22 @@ export const UserEmailListDocument = new TypedDocumentString(` id email }`) as unknown as TypedDocumentString; +export const PasswordChangeDocument = new TypedDocumentString(` + query PasswordChange { + viewer { + __typename + ... on Node { + id + } + } + siteConfig { + ...PasswordCreationDoubleInput_siteConfig + } +} + fragment PasswordCreationDoubleInput_siteConfig on SiteConfig { + id + minimumPasswordComplexity +}`) as unknown as TypedDocumentString; export const UserProfileDocument = new TypedDocumentString(` query UserProfile { viewerSession { @@ -2909,22 +2925,6 @@ export const ChangePasswordDocument = new TypedDocumentString(` } } `) as unknown as TypedDocumentString; -export const PasswordChangeDocument = new TypedDocumentString(` - query PasswordChange { - viewer { - __typename - ... on Node { - id - } - } - siteConfig { - ...PasswordCreationDoubleInput_siteConfig - } -} - fragment PasswordCreationDoubleInput_siteConfig on SiteConfig { - id - minimumPasswordComplexity -}`) as unknown as TypedDocumentString; export const RecoverPasswordDocument = new TypedDocumentString(` mutation RecoverPassword($ticket: String!, $newPassword: String!) { setPasswordByRecovery(input: {ticket: $ticket, newPassword: $newPassword}) { @@ -3326,6 +3326,27 @@ export const mockUserEmailListQuery = (resolver: GraphQLResponseResolver { + * return HttpResponse.json({ + * data: { viewer, siteConfig } + * }) + * }, + * requestOptions + * ) + */ +export const mockPasswordChangeQuery = (resolver: GraphQLResponseResolver, options?: RequestHandlerOptions) => + graphql.query( + 'PasswordChange', + resolver, + options + ) + /** * @param resolver A function that accepts [resolver arguments](https://mswjs.io/docs/api/graphql#resolver-argument) and must always return the instruction on what to do with the intercepted request. ([see more](https://mswjs.io/docs/concepts/response-resolver#resolver-instructions)) * @param options Options object to customize the behavior of the mock. ([see more](https://mswjs.io/docs/api/graphql#handler-options)) @@ -3607,27 +3628,6 @@ export const mockChangePasswordMutation = (resolver: GraphQLResponseResolver { - * return HttpResponse.json({ - * data: { viewer, siteConfig } - * }) - * }, - * requestOptions - * ) - */ -export const mockPasswordChangeQuery = (resolver: GraphQLResponseResolver, options?: RequestHandlerOptions) => - graphql.query( - 'PasswordChange', - resolver, - options - ) - /** * @param resolver A function that accepts [resolver arguments](https://mswjs.io/docs/api/graphql#resolver-argument) and must always return the instruction on what to do with the intercepted request. ([see more](https://mswjs.io/docs/concepts/response-resolver#resolver-instructions)) * @param options Options object to customize the behavior of the mock. ([see more](https://mswjs.io/docs/api/graphql#handler-options)) diff --git a/frontend/src/routes/password.change.index.tsx b/frontend/src/routes/password.change.index.tsx index 373d6456b..632384f93 100644 --- a/frontend/src/routes/password.change.index.tsx +++ b/frontend/src/routes/password.change.index.tsx @@ -189,6 +189,12 @@ function ChangePassword(): React.ReactNode { mutation.data.status === "INVALID_NEW_PASSWORD") || false } + newPasswordFieldName="new_password" + newPasswordLabel={t("frontend.password_change.new_password_label")} + newPasswordAgainFieldName="new_password_again" + newPasswordAgainLabel={t( + "frontend.password_change.new_password_again_label", + )} /> diff --git a/frontend/src/routes/password.recovery.index.tsx b/frontend/src/routes/password.recovery.index.tsx index 9a8beb920..3f34f25a8 100644 --- a/frontend/src/routes/password.recovery.index.tsx +++ b/frontend/src/routes/password.recovery.index.tsx @@ -291,6 +291,12 @@ const EmailRecovery: React.FC<{ forceShowNewPasswordInvalid={ mutation.data?.status === "INVALID_NEW_PASSWORD" || false } + newPasswordFieldName="new_password" + newPasswordLabel={t("frontend.password_change.new_password_label")} + newPasswordAgainFieldName="new_password_again" + newPasswordAgainLabel={t( + "frontend.password_change.new_password_again_label", + )} /> diff --git a/templates/pages/register/password.html b/templates/pages/register/password.html index f1a77efad..e695a97dd 100644 --- a/templates/pages/register/password.html +++ b/templates/pages/register/password.html @@ -9,6 +9,9 @@ {% extends "base.html" %} {% block content %} + + {{ include_asset('src/external/register/PasswordDoubleInput.tsx') | indent(4) | safe }} +
{{ icon.user_profile_solid() }} @@ -42,13 +45,19 @@

{{ _("mas.register.create_account.heading") }}

{% endcall %} {% endif %} - {% call(f) field.field(label=_("common.password"), name="password", form_state=form) %} - - {% endcall %} + +
- {% call(f) field.field(label=_("common.password_confirm"), name="password_confirm", form_state=form) %} - - {% endcall %} + {% if branding.tos_uri is not none %} {% call(f) field.field(label=_("mas.register.terms_of_service", tos_uri=branding.tos_uri), name="accept_terms", form_state=form, inline=true, class="my-4") %} diff --git a/translations/en.json b/translations/en.json index c8dbae97b..e43e8ec49 100644 --- a/translations/en.json +++ b/translations/en.json @@ -631,7 +631,7 @@ }, "heading": "Create an account", "@heading": { - "context": "pages/register/index.html:21:29-69, pages/register/password.html:18:27-67" + "context": "pages/register/index.html:21:29-69, pages/register/password.html:21:27-67" } }, "terms_of_service": "I agree to the Terms and Conditions", From c433421935ee9d195b24cecfa443ec63804f762b Mon Sep 17 00:00:00 2001 From: olivierdelcroix Date: Tue, 18 Nov 2025 16:32:09 +0100 Subject: [PATCH 02/12] fix translations for frontend --- frontend/locales/en.json | 2 +- frontend/locales/fr.json | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/locales/en.json b/frontend/locales/en.json index 054adcd60..849ff7624 100644 --- a/frontend/locales/en.json +++ b/frontend/locales/en.json @@ -29,7 +29,7 @@ "loading": "Loading…", "next": "Next", "password": "Password", - "password_confirm": "common.password_confirm", + "password_confirm": "Confirm password", "previous": "Previous", "saved": "Saved", "saving": "Saving…" diff --git a/frontend/locales/fr.json b/frontend/locales/fr.json index eb4f0b3f9..9cd441180 100644 --- a/frontend/locales/fr.json +++ b/frontend/locales/fr.json @@ -31,6 +31,7 @@ "loading": "Chargement…", "next": "Suivant", "password": "Mot de passe", + "password_confirm": "Confirmer le mot de passe", "previous": "Précédent", "saved": "Sauvegardé", "saving": "Enregistrement..." From d1498e7530bfb45e289845f426623d098b4269ca Mon Sep 17 00:00:00 2001 From: olivierdelcroix Date: Tue, 18 Nov 2025 16:42:32 +0100 Subject: [PATCH 03/12] fix lint --- frontend/src/external/register/PasswordDoubleInput.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/external/register/PasswordDoubleInput.tsx b/frontend/src/external/register/PasswordDoubleInput.tsx index aa198e4e6..b29e1badc 100644 --- a/frontend/src/external/register/PasswordDoubleInput.tsx +++ b/frontend/src/external/register/PasswordDoubleInput.tsx @@ -4,8 +4,8 @@ import { graphql } from "../../gql"; import { graphqlRequest } from "../../graphql"; import { mountWithProviders } from "../mount"; import "../../shared.css"; -import PasswordCreationDoubleInput from "../../components/PasswordCreationDoubleInput"; import { useTranslation } from "react-i18next"; +import PasswordCreationDoubleInput from "../../components/PasswordCreationDoubleInput"; const HTML_ID = "#password-double-input"; From 08b782d46923c97c324dd8a4d1c728edc7bf2932 Mon Sep 17 00:00:00 2001 From: olivierdelcroix Date: Wed, 19 Nov 2025 17:45:29 +0100 Subject: [PATCH 04/12] move variants inside components --- .../PasswordCreationDoubleInput.tsx | 36 ++++++++++++------- .../external/register/PasswordDoubleInput.tsx | 6 +--- frontend/src/routes/password.change.index.tsx | 6 ---- .../src/routes/password.recovery.index.tsx | 6 ---- 4 files changed, 24 insertions(+), 30 deletions(-) diff --git a/frontend/src/components/PasswordCreationDoubleInput.tsx b/frontend/src/components/PasswordCreationDoubleInput.tsx index c7c14ea35..33c87a371 100644 --- a/frontend/src/components/PasswordCreationDoubleInput.tsx +++ b/frontend/src/components/PasswordCreationDoubleInput.tsx @@ -52,26 +52,36 @@ const usePasswordComplexity = (password: string): PasswordComplexity => { return result; }; +type PasswordVariant = "register" | "change"; + export default function PasswordCreationDoubleInput({ siteConfig, forceShowNewPasswordInvalid, - newPasswordFieldName, - newPasswordLabel, - newPasswordAgainFieldName, - newPasswordAgainLabel, + variant="change", }: { siteConfig: FragmentType; forceShowNewPasswordInvalid: boolean; - newPasswordFieldName: string; - newPasswordLabel: string; - newPasswordAgainFieldName: string; - newPasswordAgainLabel: string; + variant?: PasswordVariant; }): React.ReactElement { const { t } = useTranslation(); const { minimumPasswordComplexity } = useFragment( CONFIG_FRAGMENT, siteConfig, ); + const variantFields = { + register: { + passwordFieldName: "password", + passwordLabel: t("common.password"), + passwordConfirmFieldName: "password_confirm", + passwordConfirmLabel: t("common.password_confirm"), + }, + change: { + passwordFieldName: "new_password", + passwordLabel: t("common.password"), + passwordConfirmFieldName: "new_password_again", + passwordConfirmLabel: t("common.password_confirm"), + } + }[variant]; const newPasswordRef = useRef(null); const newPasswordAgainRef = useRef(null); @@ -89,8 +99,8 @@ export default function PasswordCreationDoubleInput({ return ( <> - - {newPasswordLabel} + + {variantFields.passwordLabel} - + {/* TODO This field has validation defects, some caused by Radix-UI upstream bugs. https://github.com/matrix-org/matrix-authentication-service/issues/2855 */} - {newPasswordAgainLabel} + {variantFields.passwordConfirmLabel} v !== form.get(newPasswordFieldName)} + match={(v, form) => v !== form.get(variantFields.passwordFieldName)} > {t("frontend.password_change.passwords_no_match")} diff --git a/frontend/src/external/register/PasswordDoubleInput.tsx b/frontend/src/external/register/PasswordDoubleInput.tsx index b29e1badc..c057a5c34 100644 --- a/frontend/src/external/register/PasswordDoubleInput.tsx +++ b/frontend/src/external/register/PasswordDoubleInput.tsx @@ -37,7 +37,6 @@ function PasswordDoubleInput({ const { data: { siteConfig }, } = useSuspenseQuery(query); - const { t } = useTranslation(); return ( // Form.Root is needed because Form.Field requires to be included into a Form @@ -47,10 +46,7 @@ function PasswordDoubleInput({
diff --git a/frontend/src/routes/password.change.index.tsx b/frontend/src/routes/password.change.index.tsx index 632384f93..373d6456b 100644 --- a/frontend/src/routes/password.change.index.tsx +++ b/frontend/src/routes/password.change.index.tsx @@ -189,12 +189,6 @@ function ChangePassword(): React.ReactNode { mutation.data.status === "INVALID_NEW_PASSWORD") || false } - newPasswordFieldName="new_password" - newPasswordLabel={t("frontend.password_change.new_password_label")} - newPasswordAgainFieldName="new_password_again" - newPasswordAgainLabel={t( - "frontend.password_change.new_password_again_label", - )} /> diff --git a/frontend/src/routes/password.recovery.index.tsx b/frontend/src/routes/password.recovery.index.tsx index 3f34f25a8..9a8beb920 100644 --- a/frontend/src/routes/password.recovery.index.tsx +++ b/frontend/src/routes/password.recovery.index.tsx @@ -291,12 +291,6 @@ const EmailRecovery: React.FC<{ forceShowNewPasswordInvalid={ mutation.data?.status === "INVALID_NEW_PASSWORD" || false } - newPasswordFieldName="new_password" - newPasswordLabel={t("frontend.password_change.new_password_label")} - newPasswordAgainFieldName="new_password_again" - newPasswordAgainLabel={t( - "frontend.password_change.new_password_again_label", - )} /> From c44aecf3e45dd55a3578f16dc5c8d7358e90ba66 Mon Sep 17 00:00:00 2001 From: olivierdelcroix Date: Wed, 19 Nov 2025 17:50:41 +0100 Subject: [PATCH 05/12] remove unused prop forceShowNewPasswordInvalid --- frontend/src/external/register/PasswordDoubleInput.tsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/frontend/src/external/register/PasswordDoubleInput.tsx b/frontend/src/external/register/PasswordDoubleInput.tsx index c057a5c34..9e6b7b941 100644 --- a/frontend/src/external/register/PasswordDoubleInput.tsx +++ b/frontend/src/external/register/PasswordDoubleInput.tsx @@ -29,11 +29,7 @@ const query = queryOptions({ queryFn: ({ signal }) => graphqlRequest({ query: QUERY, signal }), }); -type PasswordDoubleInputProps = { forceShowNewPasswordInvalid: boolean }; - -function PasswordDoubleInput({ - forceShowNewPasswordInvalid, -}: PasswordDoubleInputProps) { +function PasswordDoubleInput() { const { data: { siteConfig }, } = useSuspenseQuery(query); @@ -45,7 +41,7 @@ function PasswordDoubleInput({
From c6ad90cfe321723ba5ba9f4af01520e01b69d6f0 Mon Sep 17 00:00:00 2001 From: olivierdelcroix Date: Wed, 19 Nov 2025 18:14:44 +0100 Subject: [PATCH 06/12] fix paths --- .../register/PasswordDoubleInput.tsx | 5 ++--- frontend/src/external/mount.tsx | 1 - templates/pages/register/password.html | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) rename frontend/src/{external => entrypoints}/register/PasswordDoubleInput.tsx (91%) diff --git a/frontend/src/external/register/PasswordDoubleInput.tsx b/frontend/src/entrypoints/register/PasswordDoubleInput.tsx similarity index 91% rename from frontend/src/external/register/PasswordDoubleInput.tsx rename to frontend/src/entrypoints/register/PasswordDoubleInput.tsx index 9e6b7b941..cf8dff25d 100644 --- a/frontend/src/external/register/PasswordDoubleInput.tsx +++ b/frontend/src/entrypoints/register/PasswordDoubleInput.tsx @@ -2,9 +2,8 @@ import { queryOptions, useSuspenseQuery } from "@tanstack/react-query"; import { Form } from "@vector-im/compound-web"; import { graphql } from "../../gql"; import { graphqlRequest } from "../../graphql"; -import { mountWithProviders } from "../mount"; -import "../../shared.css"; -import { useTranslation } from "react-i18next"; +import { mountWithProviders } from "../../external/mount"; +import "../shared.css"; import PasswordCreationDoubleInput from "../../components/PasswordCreationDoubleInput"; const HTML_ID = "#password-double-input"; diff --git a/frontend/src/external/mount.tsx b/frontend/src/external/mount.tsx index 15148e1c6..a7226acf1 100644 --- a/frontend/src/external/mount.tsx +++ b/frontend/src/external/mount.tsx @@ -5,7 +5,6 @@ import ReactDOM from "react-dom/client"; import { I18nextProvider } from "react-i18next"; import ErrorBoundary from "../components/ErrorBoundary"; import i18n, { setupI18n } from "../i18n"; -import "../shared.css"; setupI18n(); diff --git a/templates/pages/register/password.html b/templates/pages/register/password.html index e695a97dd..81c0939bf 100644 --- a/templates/pages/register/password.html +++ b/templates/pages/register/password.html @@ -10,7 +10,7 @@ {% block content %} - {{ include_asset('src/external/register/PasswordDoubleInput.tsx') | indent(4) | safe }} + {{ include_asset('src/entrypoints/register/PasswordDoubleInput.tsx') | indent(4) | safe }}
From 5d174ce4f1a7814845501772080dd23969dcb1e5 Mon Sep 17 00:00:00 2001 From: olivierdelcroix Date: Thu, 20 Nov 2025 12:09:53 +0100 Subject: [PATCH 07/12] merge mount function into component --- frontend/locales/en.json | 2 - .../PasswordCreationDoubleInput.tsx | 4 +- .../register/PasswordDoubleInput.tsx | 54 ++++++++++++++++--- frontend/src/external/mount.tsx | 44 --------------- 4 files changed, 48 insertions(+), 56 deletions(-) delete mode 100644 frontend/src/external/mount.tsx diff --git a/frontend/locales/en.json b/frontend/locales/en.json index 849ff7624..3ca7ccccc 100644 --- a/frontend/locales/en.json +++ b/frontend/locales/en.json @@ -156,8 +156,6 @@ }, "title": "Failed to update password" }, - "new_password_again_label": "Enter new password again", - "new_password_label": "New password", "passwords_match": "Passwords match!", "passwords_no_match": "Passwords don't match", "subtitle": "Choose a new password for your account.", diff --git a/frontend/src/components/PasswordCreationDoubleInput.tsx b/frontend/src/components/PasswordCreationDoubleInput.tsx index 33c87a371..adffbdb8d 100644 --- a/frontend/src/components/PasswordCreationDoubleInput.tsx +++ b/frontend/src/components/PasswordCreationDoubleInput.tsx @@ -57,7 +57,7 @@ type PasswordVariant = "register" | "change"; export default function PasswordCreationDoubleInput({ siteConfig, forceShowNewPasswordInvalid, - variant="change", + variant = "change", }: { siteConfig: FragmentType; forceShowNewPasswordInvalid: boolean; @@ -80,7 +80,7 @@ export default function PasswordCreationDoubleInput({ passwordLabel: t("common.password"), passwordConfirmFieldName: "new_password_again", passwordConfirmLabel: t("common.password_confirm"), - } + }, }[variant]; const newPasswordRef = useRef(null); diff --git a/frontend/src/entrypoints/register/PasswordDoubleInput.tsx b/frontend/src/entrypoints/register/PasswordDoubleInput.tsx index cf8dff25d..de2c79bf8 100644 --- a/frontend/src/entrypoints/register/PasswordDoubleInput.tsx +++ b/frontend/src/entrypoints/register/PasswordDoubleInput.tsx @@ -1,10 +1,21 @@ -import { queryOptions, useSuspenseQuery } from "@tanstack/react-query"; -import { Form } from "@vector-im/compound-web"; +import { + QueryClient, + QueryClientProvider, + queryOptions, + useSuspenseQuery, +} from "@tanstack/react-query"; +import { Form, TooltipProvider } from "@vector-im/compound-web"; +import { StrictMode, Suspense } from "react"; +import ReactDOM from "react-dom/client"; +import { I18nextProvider } from "react-i18next"; +import ErrorBoundary from "../../components/ErrorBoundary"; +import PasswordCreationDoubleInput from "../../components/PasswordCreationDoubleInput"; import { graphql } from "../../gql"; import { graphqlRequest } from "../../graphql"; -import { mountWithProviders } from "../../external/mount"; +import i18n, { setupI18n } from "../../i18n"; import "../shared.css"; -import PasswordCreationDoubleInput from "../../components/PasswordCreationDoubleInput"; + +setupI18n(); const HTML_ID = "#password-double-input"; @@ -34,8 +45,6 @@ function PasswordDoubleInput() { } = useSuspenseQuery(query); return ( - // Form.Root is needed because Form.Field requires to be included into a Form - // asChild allows to replace Form.Root component by the child, the used is in the password.html
+ + + + {`Loading... ${selector}…`}
}> + + + + + + + + , + ); + } catch (err) { + console.error( + `Cannot mount component PasswordCreationDoubleInput on ${selector}:`, + err, + ); + } +} + +mountComponentWithProviders(HTML_ID); diff --git a/frontend/src/external/mount.tsx b/frontend/src/external/mount.tsx deleted file mode 100644 index a7226acf1..000000000 --- a/frontend/src/external/mount.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { TooltipProvider } from "@vector-im/compound-web"; -import { createElement, StrictMode, Suspense } from "react"; -import ReactDOM from "react-dom/client"; -import { I18nextProvider } from "react-i18next"; -import ErrorBoundary from "../components/ErrorBoundary"; -import i18n, { setupI18n } from "../i18n"; - -setupI18n(); - -export function mountWithProviders

>( - selector: string, - Component: React.ComponentType

, - defaultProps?: Partial

, -) { - try { - const el = document.querySelector(selector); - if (!el) throw new Error(`can not find ${selector} in DOM`); - const propsJSON = el.getAttribute("data-props") || "{}"; - const parsedProps = JSON.parse(propsJSON); - const props = { ...(defaultProps ?? {}), ...(parsedProps ?? {}) }; - const queryClient = new QueryClient(); - ReactDOM.createRoot(el).render( - - - - - {`Loading... ${selector}…`}

}> - - {createElement( - Component as React.ComponentType, - props as P, - )} - - - - - - , - ); - } catch (err) { - console.error(`Cannot mount component on ${selector}:`, err); - } -} From 50106d73fdc4d93b78100879b5075001004ac74e Mon Sep 17 00:00:00 2001 From: olivierdelcroix Date: Thu, 20 Nov 2025 12:11:36 +0100 Subject: [PATCH 08/12] use id instead of css selector --- .../entrypoints/register/PasswordDoubleInput.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/frontend/src/entrypoints/register/PasswordDoubleInput.tsx b/frontend/src/entrypoints/register/PasswordDoubleInput.tsx index de2c79bf8..61b72646d 100644 --- a/frontend/src/entrypoints/register/PasswordDoubleInput.tsx +++ b/frontend/src/entrypoints/register/PasswordDoubleInput.tsx @@ -17,7 +17,7 @@ import "../shared.css"; setupI18n(); -const HTML_ID = "#password-double-input"; +const HTML_CONTAINER_ID = "password-double-input"; const QUERY = graphql(/* GraphQL */ ` query PasswordChange { @@ -57,10 +57,10 @@ function PasswordDoubleInput() { ); } -function mountComponentWithProviders(selector: string) { +function mountComponentWithProviders(containerId: string) { try { - const el = document.querySelector(selector); - if (!el) throw new Error(`can not find ${selector} in DOM`); + const el = document.getElementById(containerId); + if (!el) throw new Error(`can not find ${containerId} in DOM`); const queryClient = new QueryClient(); @@ -69,7 +69,7 @@ function mountComponentWithProviders(selector: string) { - {`Loading... ${selector}…`}}> + {`Loading... ${containerId}…`}}> @@ -81,10 +81,10 @@ function mountComponentWithProviders(selector: string) { ); } catch (err) { console.error( - `Cannot mount component PasswordCreationDoubleInput on ${selector}:`, + `Cannot mount component PasswordCreationDoubleInput on ${containerId}:`, err, ); } } -mountComponentWithProviders(HTML_ID); +mountComponentWithProviders(HTML_CONTAINER_ID); From 0b4fbf971c3bd24da48b0b1d1bbaf2184ae864f5 Mon Sep 17 00:00:00 2001 From: olivierdelcroix Date: Thu, 20 Nov 2025 12:23:25 +0100 Subject: [PATCH 09/12] fix translate --- translations/en.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/translations/en.json b/translations/en.json index e43e8ec49..8c59f163b 100644 --- a/translations/en.json +++ b/translations/en.json @@ -10,7 +10,7 @@ }, "continue": "Continue", "@continue": { - "context": "form_post.html:25:28-48, pages/consent.html:57:28-48, pages/device_consent.html:124:13-33, pages/device_link.html:40:26-46, pages/login.html:68:30-50, pages/reauth.html:32:28-48, pages/recovery/start.html:38:26-46, pages/register/password.html:77:26-46, pages/register/steps/display_name.html:43:28-48, pages/register/steps/registration_token.html:41:28-48, pages/register/steps/verify_email.html:51:26-46, pages/sso.html:37:28-48" + "context": "form_post.html:25:28-48, pages/consent.html:57:28-48, pages/device_consent.html:124:13-33, pages/device_link.html:40:26-46, pages/login.html:68:30-50, pages/reauth.html:32:28-48, pages/recovery/start.html:38:26-46, pages/register/password.html:86:26-46, pages/register/steps/display_name.html:43:28-48, pages/register/steps/registration_token.html:41:28-48, pages/register/steps/verify_email.html:51:26-46, pages/sso.html:37:28-48" }, "create_account": "Create Account", "@create_account": { @@ -79,7 +79,7 @@ }, "email_address": "Email address", "@email_address": { - "context": "pages/recovery/start.html:34:33-58, pages/register/password.html:40:35-60, pages/upstream_oauth2/do_register.html:115:37-62" + "context": "pages/recovery/start.html:34:33-58, pages/register/password.html:43:35-60, pages/upstream_oauth2/do_register.html:115:37-62" }, "loading": "Loading…", "@loading": { @@ -91,15 +91,15 @@ }, "password": "Password", "@password": { - "context": "pages/login.html:56:37-57, pages/reauth.html:28:35-55, pages/register/password.html:45:33-53" + "context": "pages/login.html:56:37-57, pages/reauth.html:28:35-55, pages/register/password.html:53:35-55" }, "password_confirm": "Confirm password", "@password_confirm": { - "context": "pages/register/password.html:49:33-61" + "context": "pages/register/password.html:57:35-63" }, "username": "Username", "@username": { - "context": "pages/login.html:50:37-57, pages/register/index.html:30:35-55, pages/register/password.html:34:33-53, pages/upstream_oauth2/do_register.html:101:35-55, pages/upstream_oauth2/do_register.html:107:39-59" + "context": "pages/login.html:50:37-57, pages/register/index.html:30:35-55, pages/register/password.html:37:33-53, pages/upstream_oauth2/do_register.html:101:35-55, pages/upstream_oauth2/do_register.html:107:39-59" } }, "error": { @@ -613,7 +613,7 @@ "register": { "call_to_login": "Already have an account?", "@call_to_login": { - "context": "pages/register/index.html:63:35-66, pages/register/password.html:80:33-64", + "context": "pages/register/index.html:63:35-66, pages/register/password.html:89:33-64", "description": "Displayed on the registration page to suggest to log in instead" }, "continue_with_email": "Continue with email address", @@ -636,7 +636,7 @@ }, "terms_of_service": "I agree to the Terms and Conditions", "@terms_of_service": { - "context": "pages/register/password.html:54:35-95, pages/upstream_oauth2/do_register.html:180:35-95" + "context": "pages/register/password.html:63:35-95, pages/upstream_oauth2/do_register.html:180:35-95" } }, "registration_token": { From cdf414c8e12a60497b2dcff5ea3834e649a22149 Mon Sep 17 00:00:00 2001 From: olivierdelcroix Date: Thu, 20 Nov 2025 12:25:24 +0100 Subject: [PATCH 10/12] fix knip --- frontend/knip.config.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frontend/knip.config.ts b/frontend/knip.config.ts index 250a6604a..5bdb749ee 100644 --- a/frontend/knip.config.ts +++ b/frontend/knip.config.ts @@ -6,13 +6,12 @@ import type { KnipConfig } from "knip"; export default { - entry: ["src/entrypoints/*", "src/routes/*"], + entry: ["src/entrypoints/**", "src/routes/*"], ignore: [ "src/gql/*", "src/routeTree.gen.ts", ".storybook/locales.ts", "i18next.config.ts", - "src/external/**", ], ignoreDependencies: [ // This is used by the tailwind PostCSS plugin, but not detected by knip From d8ce9f8007b737a17a0d8292c7fa2f81e380e78b Mon Sep 17 00:00:00 2001 From: olivierdelcroix Date: Thu, 20 Nov 2025 14:27:12 +0100 Subject: [PATCH 11/12] use new_password labels --- frontend/locales/en.json | 2 ++ frontend/src/components/PasswordCreationDoubleInput.tsx | 6 ++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/frontend/locales/en.json b/frontend/locales/en.json index 3ca7ccccc..366022cee 100644 --- a/frontend/locales/en.json +++ b/frontend/locales/en.json @@ -156,6 +156,8 @@ }, "title": "Failed to update password" }, + "new_password_again_label": "frontend.password_change.new_password_again_label", + "new_password_label": "frontend.password_change.new_password_label", "passwords_match": "Passwords match!", "passwords_no_match": "Passwords don't match", "subtitle": "Choose a new password for your account.", diff --git a/frontend/src/components/PasswordCreationDoubleInput.tsx b/frontend/src/components/PasswordCreationDoubleInput.tsx index adffbdb8d..066b09e25 100644 --- a/frontend/src/components/PasswordCreationDoubleInput.tsx +++ b/frontend/src/components/PasswordCreationDoubleInput.tsx @@ -77,9 +77,11 @@ export default function PasswordCreationDoubleInput({ }, change: { passwordFieldName: "new_password", - passwordLabel: t("common.password"), + passwordLabel: t("frontend.password_change.new_password_label"), passwordConfirmFieldName: "new_password_again", - passwordConfirmLabel: t("common.password_confirm"), + passwordConfirmLabel: t( + "frontend.password_change.new_password_again_label", + ), }, }[variant]; From 629bfd47e0df11046473407d510bd6bc08fd9950 Mon Sep 17 00:00:00 2001 From: olivierdelcroix Date: Thu, 20 Nov 2025 14:52:25 +0100 Subject: [PATCH 12/12] add translations in frontend --- frontend/locales/cs.json | 1 + frontend/locales/da.json | 1 + frontend/locales/de.json | 1 + frontend/locales/en.json | 4 ++-- frontend/locales/et.json | 1 + frontend/locales/fi.json | 1 + frontend/locales/hu.json | 1 + frontend/locales/nb-NO.json | 1 + frontend/locales/nl.json | 1 + frontend/locales/pl.json | 1 + frontend/locales/pt.json | 1 + frontend/locales/ru.json | 1 + frontend/locales/sv.json | 1 + frontend/locales/uk.json | 1 + frontend/locales/zh-Hans.json | 1 + 15 files changed, 16 insertions(+), 2 deletions(-) diff --git a/frontend/locales/cs.json b/frontend/locales/cs.json index 8613ad33e..ee3c49b9c 100644 --- a/frontend/locales/cs.json +++ b/frontend/locales/cs.json @@ -31,6 +31,7 @@ "loading": "Načítání…", "next": "Další", "password": "Heslo", + "password_confirm": "Potvrďte heslo", "previous": "Předchozí", "saved": "Uloženo", "saving": "Ukládání..." diff --git a/frontend/locales/da.json b/frontend/locales/da.json index 0529125ff..a6213bfbf 100644 --- a/frontend/locales/da.json +++ b/frontend/locales/da.json @@ -31,6 +31,7 @@ "loading": "Indlæser...", "next": "Næste", "password": "Adgangskode", + "password_confirm": "Bekræft adgangskode", "previous": "Forrige", "saved": "Gemt", "saving": "Gemmer..." diff --git a/frontend/locales/de.json b/frontend/locales/de.json index 75c8493ed..d8dff1a55 100644 --- a/frontend/locales/de.json +++ b/frontend/locales/de.json @@ -31,6 +31,7 @@ "loading": "Lade …", "next": "Weiter", "password": "Passwort", + "password_confirm": "Passwort wiederholen", "previous": "Zurück", "saved": "Gespeichert", "saving": "Speichern..." diff --git a/frontend/locales/en.json b/frontend/locales/en.json index 366022cee..849ff7624 100644 --- a/frontend/locales/en.json +++ b/frontend/locales/en.json @@ -156,8 +156,8 @@ }, "title": "Failed to update password" }, - "new_password_again_label": "frontend.password_change.new_password_again_label", - "new_password_label": "frontend.password_change.new_password_label", + "new_password_again_label": "Enter new password again", + "new_password_label": "New password", "passwords_match": "Passwords match!", "passwords_no_match": "Passwords don't match", "subtitle": "Choose a new password for your account.", diff --git a/frontend/locales/et.json b/frontend/locales/et.json index 271e268d8..bdc8b556b 100644 --- a/frontend/locales/et.json +++ b/frontend/locales/et.json @@ -31,6 +31,7 @@ "loading": "Laadime…", "next": "Edasi", "password": "Salasõna", + "password_confirm": "Korda salasõna", "previous": "Tagasi", "saved": "Salvestatud", "saving": "Salvestame…" diff --git a/frontend/locales/fi.json b/frontend/locales/fi.json index 8c11dfe85..9959e911b 100644 --- a/frontend/locales/fi.json +++ b/frontend/locales/fi.json @@ -31,6 +31,7 @@ "loading": "Ladataan…", "next": "Seuraava", "password": "Salasana", + "password_confirm": "Vahvista salasana", "previous": "Edellinen", "saved": "Tallennettu", "saving": "Tallennetaan…" diff --git a/frontend/locales/hu.json b/frontend/locales/hu.json index 239558da3..6b961f02d 100644 --- a/frontend/locales/hu.json +++ b/frontend/locales/hu.json @@ -31,6 +31,7 @@ "loading": "Betöltés…", "next": "Következő", "password": "Jelszó", + "password_confirm": "Jelszó megerősítése", "previous": "Előző", "saved": "Mentve", "saving": "Mentés…" diff --git a/frontend/locales/nb-NO.json b/frontend/locales/nb-NO.json index 85470c373..d3a124aed 100644 --- a/frontend/locales/nb-NO.json +++ b/frontend/locales/nb-NO.json @@ -31,6 +31,7 @@ "loading": "Laster inn...", "next": "Neste", "password": "Passord", + "password_confirm": "Bekreft passord", "previous": "Forrige", "saved": "Lagret", "saving": "Lagrer…" diff --git a/frontend/locales/nl.json b/frontend/locales/nl.json index e52e20624..3fa583af9 100644 --- a/frontend/locales/nl.json +++ b/frontend/locales/nl.json @@ -31,6 +31,7 @@ "loading": "Laden...", "next": "Volgende", "password": "Wachtwoord", + "password_confirm": "Bevestig wachtwoord", "previous": "Vorige", "saved": "Opgeslagen", "saving": "Opslaan..." diff --git a/frontend/locales/pl.json b/frontend/locales/pl.json index eac1eeffd..96da60850 100644 --- a/frontend/locales/pl.json +++ b/frontend/locales/pl.json @@ -31,6 +31,7 @@ "loading": "Wczytywanie…", "next": "Dalej", "password": "Hasło", + "password_confirm": "Potwierdź hasło", "previous": "Poprzedni", "saved": "Zapisano", "saving": "Zapisywanie…" diff --git a/frontend/locales/pt.json b/frontend/locales/pt.json index 5503dd642..42b928022 100644 --- a/frontend/locales/pt.json +++ b/frontend/locales/pt.json @@ -31,6 +31,7 @@ "loading": "A carregar...", "next": "Seguinte", "password": "Palavra-passe", + "password_confirm": "Confirmar palavra-passe", "previous": "Anterior", "saved": "Guardado", "saving": "A guardar…" diff --git a/frontend/locales/ru.json b/frontend/locales/ru.json index 743597672..0b7ef6345 100644 --- a/frontend/locales/ru.json +++ b/frontend/locales/ru.json @@ -31,6 +31,7 @@ "loading": "Загрузка…", "next": "Далее", "password": "Пароль", + "password_confirm": "Подтверждение пароля", "previous": "Предыдущий", "saved": "Сохранено", "saving": "Сохранение…" diff --git a/frontend/locales/sv.json b/frontend/locales/sv.json index f14d4e02d..3403914f5 100644 --- a/frontend/locales/sv.json +++ b/frontend/locales/sv.json @@ -31,6 +31,7 @@ "loading": "Laddar …", "next": "Nästa", "password": "Lösenord", + "password_confirm": "Bekräfta lösenordet", "previous": "Föregående", "saved": "Sparat", "saving": "Sparar..." diff --git a/frontend/locales/uk.json b/frontend/locales/uk.json index e15d27af9..6a78823f4 100644 --- a/frontend/locales/uk.json +++ b/frontend/locales/uk.json @@ -31,6 +31,7 @@ "loading": "Завантаження…", "next": "Далі", "password": "Пароль", + "password_confirm": "Підтвердити пароль", "previous": "Назад", "saved": "Збережено", "saving": "Збереження..." diff --git a/frontend/locales/zh-Hans.json b/frontend/locales/zh-Hans.json index ec5b8534f..bb1691d6a 100644 --- a/frontend/locales/zh-Hans.json +++ b/frontend/locales/zh-Hans.json @@ -31,6 +31,7 @@ "loading": "加载中...", "next": "下一页", "password": "密码", + "password_confirm": "确认密码", "previous": "上一页", "saved": "已保存", "saving": "正在保存..."