@@ -57,6 +57,9 @@ import { useValue } from "@/lib/spock";
5757import { Spinner } from "@/components/ui/spinner" ;
5858import { PhoneFieldDefaultCountryProvider } from "@/components/formfield/phone-field" ;
5959import type { FormAgentGeo } from "@/grida-forms/formstate/core/geo" ;
60+ import resources from "@/i18n" ;
61+ import { select_lang } from "@/i18n/utils" ;
62+ import { supported_form_page_languages } from "@/k/supported_languages" ;
6063
6164const html_form_id = "form" ;
6265
@@ -94,25 +97,46 @@ export interface FormViewTranslation {
9497 } ;
9598}
9699
97- const default_form_view_translation_en : FormViewTranslation = {
98- next : "Next" ,
99- back : "Back" ,
100- submit : "Submit" ,
101- pay : "Pay" ,
102- email_challenge : {
103- verify : "Verify" ,
104- sending : "Sending" ,
105- verify_code : "Verify" ,
106- enter_verification_code : "Enter verification code" ,
107- code_sent : "A verification code has been sent to your inbox." ,
108- didnt_receive_code : "Didn't receive a code?" ,
109- resend : "Resend" ,
110- retry : "Retry" ,
111- code_expired : "Verification code has expired." ,
112- incorrect_code : "Incorrect verification code. Please try again." ,
113- error_occurred : "An error occurred. Please try again later." ,
114- } ,
115- } ;
100+ /**
101+ * Build a {@link FormViewTranslation} from shared i18n resources for the
102+ * given language code. Unknown / unsupported codes fall back to `"en"`.
103+ */
104+ function build_form_view_translation ( lang : string ) : FormViewTranslation {
105+ const lng = select_lang ( lang , supported_form_page_languages , "en" ) ;
106+ const t = resources [ lng ] . translation ;
107+ const ec = t . email_challenge ;
108+
109+ return {
110+ next : t . next ,
111+ back : t . back ,
112+ submit : t . submit ,
113+ pay : t . pay ,
114+ email_challenge : {
115+ verify : t . verify ,
116+ sending : t . sending ,
117+ verify_code : ec . verify_code ,
118+ enter_verification_code : ec . enter_verification_code ,
119+ code_sent : ec . code_sent ,
120+ didnt_receive_code : ec . didnt_receive_code ,
121+ resend : t . resend ,
122+ retry : t . retry ,
123+ code_expired : ec . code_expired ,
124+ incorrect_code : ec . incorrect_code ,
125+ error_occurred : ec . error_occurred ,
126+ } ,
127+ } ;
128+ }
129+
130+ /** Canonical English translation, derived from the shared i18n resources. */
131+ const default_form_view_translation_en = build_form_view_translation ( "en" ) ;
132+
133+ const FormViewTranslationContext = React . createContext < FormViewTranslation > (
134+ default_form_view_translation_en
135+ ) ;
136+
137+ function useFormViewTranslation ( ) {
138+ return React . useContext ( FormViewTranslationContext ) ;
139+ }
116140
117141type FormViewRootProps = {
118142 form_id : string ;
@@ -122,6 +146,21 @@ type FormViewRootProps = {
122146 blocks : ClientRenderBlock [ ] ;
123147 tree : FormBlockTree < ClientRenderBlock [ ] > ;
124148 defaultValues ?: { [ key : string ] : string } ;
149+ /**
150+ * Optional language code (e.g. `"ko"`, `"en"`).
151+ * Resolves a {@link FormViewTranslation} from the shared i18n resources and
152+ * provides it via context so that `FormView.Body`, `FormView.Prev`,
153+ * `FormView.Next`, and `FormView.Submit` are automatically localised.
154+ *
155+ * An explicit {@link translation} prop takes precedence over `lang`.
156+ */
157+ lang ?: string ;
158+ /**
159+ * Explicit {@link FormViewTranslation} object.
160+ * Takes precedence over {@link lang}. When both are omitted the context
161+ * defaults to English.
162+ */
163+ translation ?: FormViewTranslation ;
125164} ;
126165
127166export function GridaFormsFormView (
@@ -143,7 +182,6 @@ export function GridaFormsFormView(
143182 >
144183 < GridaFormBody { ...props } />
145184 < GridaFormFooter
146- translation = { props . translation }
147185 is_powered_by_branding_enabled = {
148186 props . config . is_powered_by_branding_enabled
149187 }
@@ -155,9 +193,24 @@ export function GridaFormsFormView(
155193
156194export function FormViewRoot ( {
157195 children,
196+ lang,
197+ translation : translationProp ,
158198 ...props
159199} : React . PropsWithChildren < FormViewRootProps > ) {
160- return < Providers { ...props } > { children } </ Providers > ;
200+ const translation = useMemo (
201+ ( ) =>
202+ translationProp ??
203+ ( lang
204+ ? build_form_view_translation ( lang )
205+ : default_form_view_translation_en ) ,
206+ [ translationProp , lang ]
207+ ) ;
208+
209+ return (
210+ < FormViewTranslationContext . Provider value = { translation } >
211+ < Providers { ...props } > { children } </ Providers >
212+ </ FormViewTranslationContext . Provider >
213+ ) ;
161214}
162215
163216function Providers ( {
@@ -236,11 +289,13 @@ export function FormBody({
236289 onSubmit,
237290 onAfterSubmit,
238291 className,
239- translation = default_form_view_translation_en ,
292+ translation : translationProp ,
240293 config,
241294 stylesheet,
242295 ...formattributes
243296} : FormBodyProps & HtmlFormElementProps & IOnSubmit ) {
297+ const contextTranslation = useFormViewTranslation ( ) ;
298+ const translation = translationProp ?? contextTranslation ;
244299 const [ state , dispatch ] = useFormAgentState ( ) ;
245300 const { tree, session_id, current_section_id, submit_hidden, onNext } =
246301 useFormAgent ( ) ;
@@ -322,16 +377,14 @@ export function FormBody({
322377
323378function GridaFormFooter ( {
324379 is_powered_by_branding_enabled,
325- translation = default_form_view_translation_en ,
326380} : {
327381 is_powered_by_branding_enabled : boolean ;
328- translation ?: FormViewTranslation ;
329382} ) {
330383 const { pay_hidden } = useFormAgent ( ) ;
331384
332385 return (
333386 < >
334- < Footer shouldHidePay = { pay_hidden } translation = { translation } />
387+ < Footer shouldHidePay = { pay_hidden } />
335388 { /* on desktop, branding attribute is below footer */ }
336389 { is_powered_by_branding_enabled && (
337390 < div className = "hidden md:block" >
@@ -342,13 +395,9 @@ function GridaFormFooter({
342395 ) ;
343396}
344397
345- function Footer ( {
346- translation,
347- shouldHidePay,
348- } : {
349- shouldHidePay : boolean ;
350- translation : FormViewTranslation ;
351- } ) {
398+ function Footer ( { shouldHidePay } : { shouldHidePay : boolean } ) {
399+ const translation = useFormViewTranslation ( ) ;
400+
352401 return (
353402 < footer
354403 className = { cn (
@@ -358,9 +407,9 @@ function Footer({
358407 "md:static md:justify-start md:bg-transparent md:dark:bg-transparent"
359408 ) }
360409 >
361- < FormPrev > { translation . back } </ FormPrev >
362- < FormNext className = "flex-1 md:w-auto" > { translation . next } </ FormNext >
363- < FormSubmit className = "flex-1 md:w-auto" > { translation . submit } </ FormSubmit >
410+ < FormPrev / >
411+ < FormNext className = "flex-1 md:w-auto" / >
412+ < FormSubmit className = "flex-1 md:w-auto" / >
364413 < TossPaymentsPayButton
365414 data-pay-hidden = { shouldHidePay }
366415 className = { cn (
@@ -382,6 +431,7 @@ function FormPrev({
382431 className ?: string ;
383432} > ) {
384433 const { has_previous, onPrevious } = useFormAgent ( ) ;
434+ const translation = useFormViewTranslation ( ) ;
385435
386436 return (
387437 < Button
@@ -390,7 +440,7 @@ function FormPrev({
390440 className = { cn ( "data-[next-hidden='true']:hidden" , className ) }
391441 onClick = { onPrevious }
392442 >
393- { children }
443+ { children ?? translation . back }
394444 </ Button >
395445 ) ;
396446}
@@ -402,6 +452,7 @@ function FormNext({
402452 className ?: string ;
403453} > ) {
404454 const { has_next } = useFormAgent ( ) ;
455+ const translation = useFormViewTranslation ( ) ;
405456
406457 return (
407458 < Button
@@ -411,7 +462,7 @@ function FormNext({
411462 type = "submit"
412463 className = { cn ( "data-[next-hidden='true']:hidden" , className ) }
413464 >
414- { children }
465+ { children ?? translation . next }
415466 </ Button >
416467 ) ;
417468}
@@ -423,6 +474,7 @@ function FormSubmit({
423474 className ?: string ;
424475} > ) {
425476 const { submit_hidden, is_submitting } = useFormAgent ( ) ;
477+ const translation = useFormViewTranslation ( ) ;
426478
427479 return (
428480 < Button
@@ -442,7 +494,7 @@ function FormSubmit({
442494 < Spinner className = "me-2" />
443495 </ div >
444496 ) }
445- { children }
497+ { children ?? translation . submit }
446498 </ Button >
447499 ) ;
448500}
0 commit comments