Skip to content

Feat/i18n en es#100

Merged
JoelVR17 merged 3 commits intoTrustless-Work:developfrom
AnoukRImola:feat/i18n-en-es
Mar 14, 2026
Merged

Feat/i18n en es#100
JoelVR17 merged 3 commits intoTrustless-Work:developfrom
AnoukRImola:feat/i18n-en-es

Conversation

@AnoukRImola
Copy link
Copy Markdown
Contributor

@AnoukRImola AnoukRImola commented Mar 14, 2026

feat: add i18n support (EN/ES) across backoffice, investor and shared packages

Summary

  • Add full internationalization (English/Spanish) using next-intl for both
    Next.js apps and a lightweight SharedTranslationProvider for tw-blocks-shared
  • Locale-prefixed routing (/es/campaigns, /en/campaigns) with browser language
    detection and Spanish as default
  • Migrate ~280 hardcoded strings across ~70 components to translation keys
  • Add language switcher in both app headers
  • Fix UTF-8 charset and latin-ext font subset for proper ñ/accent rendering
  • Install missing @radix-ui/react-dropdown-menu dependency from upstream

Changes

  • Config: next-intl plugin, i18n/routing.ts, i18n/request.ts,
    i18n/navigation.ts in both apps
  • Routing: All routes moved under [locale] dynamic segment
  • Middleware: Locale detection middleware (backoffice new, investor merged
    with existing CORS)
  • Messages: messages/en.json + messages/es.json for both apps, plus
    tw-blocks-shared/src/i18n/messages/
  • Shared package: TranslationProvider context with useSharedTranslation() hook
    — no next-intl dependency
  • Components: ~70 files migrated (layouts, campaigns, ROI, loans, tokens,
    vaults, escrows, wallet, invest dialog, claim ROI)
  • Error handling: contractErrorHandler.ts updated to use i18n keys

Test plan

  • Visit / — redirects to /es/ or /en/ based on browser language
  • /es/campaigns and /en/campaigns load correctly with translated content
  • Language switcher toggles all visible text
  • Wallet button shows "Conectar Billetera" (ES) / "Connect Wallet" (EN)
  • Create campaign wizard: labels, placeholders, validation errors in correct
    language
  • Special characters (ñ, á, é, í, ó, ú) render correctly
  • Toasts and contract error messages appear in the active language
  • /core-api/ proxy unaffected by locale prefix

Summary by CodeRabbit

  • New Features
    • Full Spanish and English localization added across apps (UI labels, placeholders, validation, toasts, and contract/error messages).
    • Global language switcher in the header to toggle locales; locale-aware navigation and layouts ensure consistent language across pages.
    • Numerous dialogs, forms, lists, and escrow/ROI flows now display localized strings and translated success/error feedback.

@vercel
Copy link
Copy Markdown

vercel bot commented Mar 14, 2026

@AnoukRImola is attempting to deploy a commit to the Trustless Work Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 14, 2026

📝 Walkthrough

Walkthrough

Adds comprehensive i18n: message catalogs (en/es), next-intl wiring (routing, request, navigation, middleware), locale-aware layouts/providers, SharedTranslationProvider, language switchers, and replaces hardcoded UI strings across apps and shared packages with translation lookups.

Changes

Cohort / File(s) Summary
i18n core & middleware
apps/backoffice-tokenization/src/i18n/routing.ts, apps/backoffice-tokenization/src/i18n/request.ts, apps/backoffice-tokenization/src/i18n/navigation.ts, apps/backoffice-tokenization/src/middleware.ts, apps/investor-tokenization/src/i18n/routing.ts, apps/investor-tokenization/src/i18n/request.ts, apps/investor-tokenization/src/i18n/navigation.ts, apps/investor-tokenization/src/middleware.ts
Adds next-intl routing, request-time message resolution, navigation helpers, and middleware that applies locale detection and CORS handling.
Next config & dependency
apps/backoffice-tokenization/next.config.ts, apps/investor-tokenization/next.config.ts, package.json
Wraps Next configs with createNextIntlPlugin and adds next-intl dependency.
Locale layouts & provider move
apps/backoffice-tokenization/src/app/layout.tsx, apps/backoffice-tokenization/src/app/[locale]/layout.tsx, apps/investor-tokenization/src/app/layout.tsx, apps/investor-tokenization/src/app/[locale]/layout.tsx
Moves provider stack (NextIntlClientProvider, SharedTranslationProvider, ReactQuery, Wallet/Escrow providers, Toaster) into locale-specific layouts and simplifies root layouts. Exports locale layout metadata and default async layout.
Message catalogs
apps/backoffice-tokenization/messages/en.json, apps/backoffice-tokenization/messages/es.json, apps/investor-tokenization/messages/en.json, apps/investor-tokenization/messages/es.json, packages/tw-blocks-shared/src/i18n/messages/en.json, packages/tw-blocks-shared/src/i18n/messages/es.json
Adds English and Spanish translation files for backoffice, investor app, and shared escrow UI (labels, placeholders, statuses, errors).
Shared translation provider
packages/tw-blocks-shared/src/i18n/TranslationProvider.tsx
Introduces SharedTranslationProvider and useSharedTranslation hook with nested-key resolution and simple interpolation for shared packages.
Language switcher & navigation
apps/backoffice-tokenization/src/components/shared/language-switcher.tsx, apps/investor-tokenization/src/components/shared/language-switcher.tsx, apps/backoffice-tokenization/src/i18n/navigation.ts, apps/investor-tokenization/src/i18n/navigation.ts
Adds LanguageSwitcher components and centralizes locale-aware Link/redirect/useRouter/usePathname helpers via createNavigation.
Header & Sidebar locale wiring
apps/backoffice-tokenization/src/components/layout/app-header.tsx, apps/backoffice-tokenization/src/components/layout/app-sidebar.tsx, apps/investor-tokenization/src/components/layout/app-header.tsx, apps/investor-tokenization/src/components/layout/app-sidebar.tsx
Adjusts header layout to surface LanguageSwitcher and replaces hardcoded nav labels with useTranslations-driven labels.
Campaign / ROI / Vault / Loans i18n
apps/backoffice-tokenization/src/features/campaigns/..., apps/backoffice-tokenization/src/features/roi/..., apps/backoffice-tokenization/src/features/vaults/..., apps/backoffice-tokenization/src/components/shared/campaign-card.tsx, apps/backoffice-tokenization/src/features/campaigns/hooks/use-create-campaign.ts, apps/investor-tokenization/src/features/roi/..., apps/investor-tokenization/src/features/investments/...
Replaces hardcoded UI strings, validation messages and status labels with translation lookups; converts static CAMPAIGN_STATUS_CONFIG to getCampaignStatusConfig(t) (backoffice) and adds i18n-aware status config function/types (investor).
Tokenization & Investment dialogs
apps/backoffice-tokenization/src/features/tokens/deploy/dialog/*, apps/investor-tokenization/src/features/tokens/components/InvestDialog.tsx
Localizes token deployment and Invest dialog strings, validation texts and button states; minor prop default adjustment for InvestDialog triggerLabel.
Contract error handling & toasts
apps/backoffice-tokenization/src/lib/contractErrorHandler.ts, apps/investor-tokenization/src/lib/contractErrorHandler.ts, apps/backoffice-tokenization/src/lib/toastWithTx.tsx
Extends extractContractError to accept optional translation function t and map contexts to namespaces; toastSuccessWithTx gains configurable viewTransactionLabel; error messages prefer translations when t is provided.
Home / Hero / Demo components
apps/backoffice-tokenization/src/features/home/BentoGrid.tsx, apps/backoffice-tokenization/src/features/home/HeroSection.tsx
Moves static card data into component scope and internationalizes titles/descriptions and skeleton variants (SkeletonFour/Five now accept t).
Shared escrow components localization
packages/tw-blocks-shared/src/escrows/** (many files: EscrowsCards, Filters, GeneralInformation, MilestoneCard, InitializeEscrow, UpdateEscrow, FundEscrow, Release/Approve/Dispute/Resolve/Withdraw dialogs and buttons...)
Systematic replacement of hardcoded escrow UI strings with useSharedTranslation lookups across ~20+ components.
Misc UI text updates
various small components across both apps (campaign search/list, filters, campaign-step components, ROI table rows, vault dialogs, wallet buttons, etc.)
Wires useTranslations/useSharedTranslation into many UI components to replace static text with localized keys; minor import/path adjustments to use i18n navigation Link.

Sequence Diagram(s)

sequenceDiagram
  participant Browser as Client (Browser)
  participant MW as Middleware
  participant Server as Next.js Server
  participant RequestCfg as i18n Request Config
  participant Messages as Message Bundles
  participant App as App (Locale Layout / Providers)

  Browser->>MW: Request /locale/path
  MW->>RequestCfg: resolve locale (routing/request)
  RequestCfg->>Messages: dynamic import ../../messages/{locale}.json
  Messages-->>RequestCfg: messages
  RequestCfg-->>Server: { locale, messages }
  Server->>App: render with NextIntlClientProvider(messages)
  App-->>Browser: localized HTML + client bundles
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

  • armandocodecr
  • zkCaleb-dev
  • JoelVR17

Poem

🐰 Hopping through keys and locale arrays,

I translate labels on bright spring days.
From es to en I nudge each string,
So apps can sing in every tongue and spring.
A little hop, a global cheer — i18n is here!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.76% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Feat/i18n en es' clearly and concisely describes the main change: implementing internationalization (i18n) support for English (en) and Spanish (es) across the applications.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 19

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (12)
packages/tw-blocks-shared/src/escrows/single-multi-release/approve-milestone/button/ApproveMilestone.tsx (1)

60-60: ⚠️ Potential issue | 🟡 Minor

Success message not translated.

This hardcoded toast message is inconsistent with the i18n changes in this PR. The button labels use t() but the success message remains in English.

🌍 Suggested fix
-      toast.success("Milestone approved flag updated successfully");
+      toast.success(t("escrow.approve.success"));

Ensure the corresponding translation key is added to both en.json and es.json message files.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/tw-blocks-shared/src/escrows/single-multi-release/approve-milestone/button/ApproveMilestone.tsx`
at line 60, Replace the hardcoded toast.success message in the ApproveMilestone
component with a translated string using the t() function (i.e., call t('...')
instead of the English literal) at the location where toast.success("Milestone
approved flag updated successfully") is called; add the new translation key (for
example "escrows.approveMilestone.success") to both en.json and es.json message
files with appropriate English and Spanish texts so the toast displays the
localized message.
apps/investor-tokenization/src/features/investments/components/InvestmentCard.tsx (1)

63-70: ⚠️ Potential issue | 🟠 Major

Use active locale for numeric/date formatting (not hardcoded "en-US")

Line 67, Line 83, and Line 118 force English formatting, so Spanish routes still show English-formatted numbers/dates.

💡 Proposed fix
-import { useTranslations } from "next-intl";
+import { useLocale, useTranslations } from "next-intl";
@@
 export const InvestmentCard = ({ investment }: InvestmentCardProps) => {
+  const locale = useLocale();
   const t = useTranslations("investments");
   const tCampaigns = useTranslations("campaigns");
@@
-              {usdcAmount.toLocaleString("en-US", {
+              {usdcAmount.toLocaleString(locale, {
                 minimumFractionDigits: 2,
                 maximumFractionDigits: 2,
               })}
@@
-              {tokenAmount.toLocaleString("en-US", {
+              {tokenAmount.toLocaleString(locale, {
                 minimumFractionDigits: 2,
                 maximumFractionDigits: 2,
               })}
@@
-              {createdAt.toLocaleDateString("en-US", {
+              {createdAt.toLocaleDateString(locale, {
                 year: "numeric",
                 month: "short",
                 day: "numeric",
               })}

This pattern also appears in InvestmentsView.tsx, InvestDialog.tsx, ProjectCard.tsx, and Carousel.tsx—consider applying the same fix across the codebase.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/investor-tokenization/src/features/investments/components/InvestmentCard.tsx`
around lines 63 - 70, The code currently forces "en-US" in numeric/date
formatting (e.g., usdcAmount.toLocaleString("en-US", ... ) in
InvestmentCard.tsx), causing wrong locale presentation; update these calls to
use the app/user active locale (obtain from i18n/locale provider or the existing
translation hook like t or a locale context) instead of the hardcoded "en-US",
and apply the same change for other instances (InvestmentsView.tsx,
InvestDialog.tsx, ProjectCard.tsx, Carousel.tsx) and for date formatting calls
so numbers and dates render with the current locale.
packages/tw-blocks-shared/src/escrows/single-multi-release/approve-milestone/dialog/ApproveMilestone.tsx (1)

59-76: ⚠️ Potential issue | 🟡 Minor

Incomplete i18n migration: some strings remain hardcoded.

Several user-facing strings in this form are not internationalized:

  • Line 60: "Milestone" label
  • Line 70: "Select milestone" placeholder
  • Line 75: Fallback text `Milestone ${idx + 1}`

For consistency with the comprehensive i18n effort in this PR, these should also use translation keys.

🌐 Proposed fix to complete i18n migration
                 <FormItem>
                   <FormLabel className="flex items-center">
-                    Milestone<span className="text-destructive ml-1">*</span>
+                    {t("escrow.approve.milestoneLabel")}<span className="text-destructive ml-1">*</span>
                   </FormLabel>
                   <FormControl>
                     <Select
                       value={field.value}
                       onValueChange={(e) => {
                         field.onChange(e);
                       }}
                     >
                       <SelectTrigger className="w-full">
-                        <SelectValue placeholder="Select milestone" />
+                        <SelectValue placeholder={t("escrow.approve.selectPlaceholder")} />
                       </SelectTrigger>
                       <SelectContent>
                         {(selectedEscrow?.milestones || []).map((m, idx) => (
                           <SelectItem key={`ms-${idx}`} value={String(idx)}>
-                            {m?.description || `Milestone ${idx + 1}`}
+                            {m?.description || t("escrow.approve.milestoneFallback", { index: idx + 1 })}
                           </SelectItem>
                         ))}
                       </SelectContent>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/tw-blocks-shared/src/escrows/single-multi-release/approve-milestone/dialog/ApproveMilestone.tsx`
around lines 59 - 76, The FormLabel "Milestone", the SelectValue placeholder
"Select milestone", and the SelectItem fallback `Milestone ${idx + 1}` are still
hardcoded; update these to use the project's i18n helper (e.g., the t function
from useTranslation) so they pull from translation keys instead. In the
component where FormLabel, SelectValue and the mapping over
selectedEscrow?.milestones are defined, import/use the translation hook (e.g.,
useTranslation or t) and replace "Milestone", "Select milestone", and the
template fallback with calls like t('escrow.milestoneLabel'),
t('escrow.selectMilestonePlaceholder'), and t('escrow.milestoneFallback', {
number: idx + 1 }) respectively so the UI strings are localized.
packages/tw-blocks-shared/src/escrows/multi-release/withdraw-remaining-funds/dialog/WithdrawRemainingFunds.tsx (1)

61-154: ⚠️ Potential issue | 🟡 Minor

Incomplete translation coverage.

Several UI strings remain hardcoded while others are translated, creating an inconsistent user experience:

  • Line 61: "Distributions"
  • Line 74: "Address"
  • Line 78: "Receiver address"
  • Line 95: "Amount"
  • Line 131: "Add Item"
  • Line 137: "Total Amount: "
  • Line 142: "Difference: "
  • Line 149: "Total Balance: "

Consider adding these strings to the translation files for complete i18n coverage.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/tw-blocks-shared/src/escrows/multi-release/withdraw-remaining-funds/dialog/WithdrawRemainingFunds.tsx`
around lines 61 - 154, The UI contains hardcoded strings inside the
WithdrawRemainingFunds component (e.g., the FormLabel/placeholder/button/labels
in the distributions list and totals) — replace each literal ("Distributions",
"Address", "Receiver address", "Amount", "Add Item", "Total Amount: ",
"Difference: ", "Total Balance: ") with translation lookups (using the project's
i18n hook/function used elsewhere in this file, e.g., t(...) or
useTranslation()) and add corresponding keys to the locale JSON files; ensure
keys are descriptive (e.g., withdrawRemaining.distributions,
withdrawRemaining.address, withdrawRemaining.receiverPlaceholder,
withdrawRemaining.amount, withdrawRemaining.addItem,
withdrawRemaining.totalAmount, withdrawRemaining.difference,
withdrawRemaining.totalBalance) and update any tests/stories that assert visible
text if present.
apps/backoffice-tokenization/src/features/vaults/deploy/dialog/VaultDeploySuccessDialog.tsx (1)

64-70: ⚠️ Potential issue | 🟡 Minor

Use a contract/explorer label here.

This href opens a contract page (/contract/${address}), so viewTransaction is misleading. A viewContract or viewOnExplorer key would match the actual destination.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/backoffice-tokenization/src/features/vaults/deploy/dialog/VaultDeploySuccessDialog.tsx`
around lines 64 - 70, The translation key used in VaultDeploySuccessDialog's
Link is misleading—tCommon("viewTransaction") points to a contract explorer URL;
update the component to use a more accurate key such as tCommon("viewContract")
or tCommon("viewOnExplorer") and replace the string in the Link render, then add
the corresponding new key(s) and translations in the i18n resource files so the
label matches the destination; locate this in VaultDeploySuccessDialog.tsx
around the Link component and update both the key usage and the translation
entries.
packages/tw-blocks-shared/src/escrows/multi-release/resolve-dispute/dialog/ResolveDispute.tsx (1)

84-121: ⚠️ Potential issue | 🟠 Major

This dialog still contains multiple hardcoded English strings.

The selector, distributions section, field labels/placeholders, and totals copy remain unlocalized, so Spanish mode will be partially untranslated.

Also applies to: 133-160, 182-214

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/tw-blocks-shared/src/escrows/multi-release/resolve-dispute/dialog/ResolveDispute.tsx`
around lines 84 - 121, The component ResolveDispute.tsx contains multiple
hardcoded English strings (e.g., the "Milestone" FormLabel, SelectValue
placeholder "Select milestone", SelectItem fallback label `Milestone ${idx +
1}`, the "Distributions" label and other text around lines referenced) — replace
these with calls to the app's localization utilities (e.g., useTranslation()/t
or the existing i18n helper) so all labels, placeholders, button text and totals
use translated keys; update usages in the Select (SelectValue placeholder),
SelectItem text, FormLabel and any totals/distribution strings referenced in the
other ranges (133–160, 182–214) to use translation keys (e.g.,
t('escrow.milestone'), t('escrow.select_milestone_placeholder'),
t('escrow.milestone_index', { index: idx + 1 }), t('escrow.distributions'),
etc.) and ensure keys are added to the locale files.
packages/tw-blocks-shared/src/escrows/single-multi-release/change-milestone-status/dialog/ChangeMilestoneStatus.tsx (1)

74-112: ⚠️ Potential issue | 🟠 Major

Several visible strings are still hardcoded, causing mixed-language UI.

The milestone selector and evidence field still render English literals, so locale switching is incomplete in this dialog.

🌐 Suggested i18n completion
-<FormLabel className="flex items-center">
-  Milestone
+<FormLabel className="flex items-center">
+  {t("escrow.changeStatus.milestoneLabel")}
   <span className="text-destructive ml-1">*</span>
 </FormLabel>

-<SelectValue placeholder="Select milestone" />
+<SelectValue placeholder={t("escrow.changeStatus.selectMilestone")} />

-{m?.description || `Milestone ${idx + 1}`}
+{m?.description || `${t("escrow.changeStatus.milestoneFallback")} ${idx + 1}`}

-<FormLabel>Evidence</FormLabel>
+<FormLabel>{t("escrow.changeStatus.evidenceLabel")}</FormLabel>

-<Textarea placeholder="Enter evidence (optional)" {...field} />
+<Textarea placeholder={t("escrow.changeStatus.evidencePlaceholder")} {...field} />

Also applies to: 131-141

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/tw-blocks-shared/src/escrows/single-multi-release/change-milestone-status/dialog/ChangeMilestoneStatus.tsx`
around lines 74 - 112, The Milestone selector uses hardcoded English strings —
replace the literals in ChangeMilestoneStatus.tsx (references:
showSelectMilestone, FormLabel label text "Milestone", the destructive asterisk
label, SelectValue placeholder "Select milestone", and the SelectItem fallback
`{m?.description || `Milestone ${idx + 1}`}`) with calls to your i18n function
(e.g., t('...') or useLocale()/translate) and add corresponding locale keys;
also update the evidence field strings referenced around the same component
(lines ~131-141) to use the same translation keys so all displayed UI text in
this dialog is localized. Ensure keys are descriptive (e.g.,
escrow.milestone.label, escrow.milestone.selectPlaceholder,
escrow.milestone.fallback) and wired into the locales.
apps/investor-tokenization/src/lib/contractErrorHandler.ts (1)

113-129: ⚠️ Potential issue | 🟡 Minor

Use translated title in coded-error fallback when t is provided.

When t exists but i18n path is not used, Line 121 and Line 127 still return "Contract Error", producing mixed-language output.

🛠️ Proposed change
     if (humanMessage) {
       return {
-        message: "Contract Error",
+        message: t ? t("title") : "Contract Error",
         details: humanMessage,
       };
     }

     return {
-      message: "Contract Error",
+      message: t ? t("title") : "Contract Error",
       details: `Contract error code ${errorCode}`,
     };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/investor-tokenization/src/lib/contractErrorHandler.ts` around lines 113
- 129, The fallback returns a hardcoded title "Contract Error" even when the
translation function t is provided; update the returns in the
contractErrorHandler (the branch using errorMap/humanMessage and the final
fallback) to use t(...) for the title when t is defined (e.g., title:
t('contractError.title') or the existing i18n key used elsewhere), keeping
details as-is (humanMessage or `Contract error code ${errorCode}`) so the output
language is consistent whenever the t parameter is passed into the function.
packages/tw-blocks-shared/src/escrows/multi-release/update-escrow/dialog/UpdateEscrow.tsx (1)

72-91: ⚠️ Potential issue | 🟡 Minor

Complete i18n coverage for remaining lock/help texts.

A few user-facing strings in this block are still hardcoded English (Update escrow details and milestones, Escrow is locked, and the explanatory paragraph), so they won’t localize.

🌐 Proposed change
-<p className="text-muted-foreground mt-1">
-  Update escrow details and milestones
-</p>
+<p className="text-muted-foreground mt-1">
+  {t("escrow.update.detailsAndMilestones")}
+</p>

-<span className="text-yellow-600 dark:text-yellow-500 font-medium">
-  Escrow is locked
-</span>
+<span className="text-yellow-600 dark:text-yellow-500 font-medium">
+  {t("escrow.update.lockedTitle")}
+</span>

-<p className="text-muted-foreground font-medium">
-  When the escrow has balance, it cannot be updated in all
-  fields, just adding new milestones is allowed.
-</p>
+<p className="text-muted-foreground font-medium">
+  {t("escrow.update.lockedDescription")}
+</p>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/tw-blocks-shared/src/escrows/multi-release/update-escrow/dialog/UpdateEscrow.tsx`
around lines 72 - 91, Replace the three hardcoded English strings in
UpdateEscrow.tsx with i18n lookups using the existing translation function t;
specifically change the subtitle "Update escrow details and milestones" to a
t(...) call (e.g., t("escrow.update.multiReleaseSubtitle")), the visible lock
label "Escrow is locked" to t("escrow.locked.title"), and the explanatory
paragraph to t("escrow.locked.description"); update any missing keys in your
translation files to match these identifiers and keep the conditional block
using isEscrowLocked and the Lock icon unchanged.
apps/investor-tokenization/src/features/tokens/components/InvestDialog.tsx (1)

292-297: ⚠️ Potential issue | 🟡 Minor

Use locale-aware number formatting instead of fixed en-US.

Displayed amounts are still formatted with "en-US", which breaks localized numeric conventions on Spanish routes.

Also applies to: 327-327, 335-335, 341-341

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/investor-tokenization/src/features/tokens/components/InvestDialog.tsx`
around lines 292 - 297, Replace the hardcoded "en-US" locale used for formatting
amounts in the InvestDialog component with the runtime locale so numbers follow
the user's language/region; e.g., obtain the current locale from your i18n
instance (e.g., i18n.language) or Next/router (router.locale) and pass that
variable into toLocaleString or Intl.NumberFormat when formatting totalAmount
and other monetary displays (references: InvestDialog, totalAmount, currency —
occurrences around the shown block and the other three spots reported at lines
327, 335, 341).
apps/backoffice-tokenization/src/features/campaigns/components/loans/manage-loans-view.tsx (2)

84-89: ⚠️ Potential issue | 🟠 Major

Remove any type cast and use proper type narrowing.

The code disables TypeScript type safety with as any at the point where remote data is consumed. Use Array.isArray() to narrow the type before accessing array elements:

Suggested fix
-        // eslint-disable-next-line `@typescript-eslint/no-explicit-any`
-        const data = (await getEscrowByContractIds({
+        const data = await getEscrowByContractIds({
           contractIds: [escrowId],
           validateOnChain: true,
-        })) as any;
-        if (!data || !data[0]) throw new Error(t("escrowNotFound"));
+        });
+        if (!Array.isArray(data) || data.length === 0) {
+          throw new Error(t("escrowNotFound"));
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/backoffice-tokenization/src/features/campaigns/components/loans/manage-loans-view.tsx`
around lines 84 - 89, Remove the unsafe "as any" cast when consuming
getEscrowByContractIds; instead let the call return its declared type (or add a
proper return type annotation) and use Array.isArray(data) and length checks to
narrow the type before accessing elements. Specifically, replace the cast on the
result of getEscrowByContractIds with a typed value (or no cast), then do: if
(!Array.isArray(data) || data.length === 0) throw new
Error(t("escrowNotFound")); and only use data[0] after that check so TypeScript
can infer the correct element type for further use of escrowId and the retrieved
escrow.

79-99: ⚠️ Potential issue | 🟠 Major

Address type safety violation and add missing callback dependencies.

The callback uses t(...) and needs it in the dependency list to ensure fresh translations. Additionally, replace the as any cast with unknown and type narrowing to comply with TypeScript guidelines.

Suggested fix
  const fetchEscrow = useCallback(
    async (escrowId: string) => {
      setLoading(true);
      setError(null);
      try {
-       // eslint-disable-next-line `@typescript-eslint/no-explicit-any`
-       const data = (await getEscrowByContractIds({
+       const data = await getEscrowByContractIds({
          contractIds: [escrowId],
          validateOnChain: true,
-       })) as any;
+       });
+       if (typeof data !== 'object' || data === null || !Array.isArray(data)) {
+         throw new Error(t("escrowNotFound"));
+       }
-       if (!data || !data[0]) throw new Error(t("escrowNotFound"));
-       setSelectedEscrow(data[0]);
+       const escrow = data[0];
+       if (!escrow) throw new Error(t("escrowNotFound"));
+       setSelectedEscrow(escrow);
      } catch (err) {
        setError(handleError(err as ErrorResponse).message);
      } finally {
        setLoading(false);
      }
    },
-    // eslint-disable-next-line react-hooks/exhaustive-deps
-    [],
+    [getEscrowByContractIds, setSelectedEscrow, t],
  );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/backoffice-tokenization/src/features/campaigns/components/loans/manage-loans-view.tsx`
around lines 79 - 99, The fetchEscrow callback currently unsafely casts the
response to any and omits dependencies; change the cast from as any to as
unknown and perform runtime type narrowing (e.g., check Array.isArray(data) and
typeof data[0] or a shape check) before assigning setSelectedEscrow(data[0]);
also update the dependency array to include t and any external functions/vars
used inside the callback (e.g., getEscrowByContractIds, handleError,
setSelectedEscrow, setLoading, setError) so React re-creates the callback when
those change.
🧹 Nitpick comments (19)
apps/backoffice-tokenization/src/features/home/BentoGrid.tsx (1)

224-224: Consider using next-intl's type utilities for stricter key validation.

The current (key: string) => string type works but doesn't catch typos in translation keys at compile time. You could leverage next-intl's type system for stronger guarantees if desired.

♻️ Optional: Extract a shared type for translation props
// In a shared types file or at the top of this file:
type TranslationFunction = ReturnType<typeof useTranslations<'home'>>;

// Then use it:
const SkeletonFour = ({ t }: { t: TranslationFunction }) => {

This provides autocomplete for translation keys and compile-time errors for invalid keys.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/backoffice-tokenization/src/features/home/BentoGrid.tsx` at line 224,
The prop type for SkeletonFour uses a broad (key: string) => string which
doesn't enforce valid translation keys; change it to use next-intl's typed
translation function by deriving a shared type (e.g. TranslationFunction =
ReturnType<typeof useTranslations<'home'>>) and update the prop to ({ t }: { t:
TranslationFunction }) so SkeletonFour receives a strongly-typed t that provides
autocomplete and compile-time key validation.
packages/tw-blocks-shared/src/escrows/single-multi-release/approve-milestone/button/ApproveMilestone.tsx (1)

16-16: Inconsistent import style with other internal package imports.

Other imports in this file use the @tokenization/tw-blocks-shared/src/... alias pattern (lines 3, 4, 13, 14), but this import uses a relative path. For consistency and maintainability, consider using the same pattern.

♻️ Suggested fix
-import { useSharedTranslation } from "../../../../i18n/TranslationProvider";
+import { useSharedTranslation } from "@tokenization/tw-blocks-shared/src/i18n/TranslationProvider";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/tw-blocks-shared/src/escrows/single-multi-release/approve-milestone/button/ApproveMilestone.tsx`
at line 16, In ApproveMilestone.tsx replace the relative import of
useSharedTranslation with the internal package alias to match other imports;
change the import source for the useSharedTranslation symbol from
"../../../../i18n/TranslationProvider" to the same alias pattern used elsewhere
(e.g. "@tokenization/tw-blocks-shared/src/i18n/TranslationProvider") so imports
in this file are consistent and maintainable.
apps/investor-tokenization/src/features/claim-roi/VaultCard.tsx (1)

5-6: Use the repository import alias instead of a relative path.

Please switch useVaultInfo to @/* alias import style in this file.

🔧 Suggested change
-import { useVaultInfo } from "./hooks/useVaultInfo";
+import { useVaultInfo } from "@/features/claim-roi/hooks/useVaultInfo";

As per coding guidelines: "Use path alias @/* mapping to ./src/* for imports".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/investor-tokenization/src/features/claim-roi/VaultCard.tsx` around lines
5 - 6, The import of the hook useVaultInfo in VaultCard.tsx uses a relative
path; update it to use the repository path alias by replacing the relative
import with the `@/`* alias (e.g., import { useVaultInfo } from
"@/features/claim-roi/hooks/useVaultInfo") so it resolves via the ./src/*
mapping and conforms to project import guidelines; ensure the import specifier
matches the existing hook file and that any related imports in this file remain
unchanged.
apps/backoffice-tokenization/src/components/layout/app-sidebar.tsx (1)

28-35: Footer items remain hardcoded in English.

The footerItems array still contains hardcoded English strings ("Documentation", "View documentation") while nav items are now translated. Consider moving footerItems inside the component and adding corresponding translation keys for consistency.

Example approach
 export function AppSidebar() {
   const t = useTranslations("nav");
+  const tCommon = useTranslations("common");

+  const footerItems: AppSidebarFooterItem[] = [
+    {
+      label: tCommon("documentation"),
+      icon: BookOpen,
+      href: "https://interactuar.gitbook.io/interactuar-x-trustless/",
+      tooltip: tCommon("viewDocumentation"),
+    },
+  ];

   const navItems: AppSidebarNavItem[] = [

Then add the keys to en.json and es.json.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/backoffice-tokenization/src/components/layout/app-sidebar.tsx` around
lines 28 - 35, Move the hardcoded footerItems array into the AppSidebar
component and replace literal strings with translation keys using the existing
i18n hook (e.g., useTranslation or t) so footer labels and tooltips are
localized; update the footerItems declaration (AppSidebarFooterItem[], BookOpen
icon stays) to call t('sidebar.footer.documentation') and
t('sidebar.footer.viewDocumentation') and add those keys to en.json and es.json
with appropriate translations.
apps/backoffice-tokenization/src/components/shared/language-switcher.tsx (1)

25-36: Consider adding accessibility attributes.

The buttons lack aria-label or aria-pressed attributes that would help screen reader users understand the current state.

Accessibility improvement
         <button
           key={code}
           onClick={() => switchLocale(code)}
+          aria-pressed={code === locale}
+          aria-label={`Switch to ${code === "es" ? "Spanish" : "English"}`}
           className={cn(
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/backoffice-tokenization/src/components/shared/language-switcher.tsx`
around lines 25 - 36, The language switcher buttons are missing accessibility
attributes; update the JSX button (the element that calls switchLocale(code)) to
include an appropriate aria-label (e.g., the language label or a localized
string like `Switch to ${label}`) and an aria-pressed attribute that reflects
the current state (set aria-pressed={code === locale} so the active language is
true). Ensure the change is applied to the button rendered in
language-switcher.tsx so screen readers can announce the control and its
selected state.
apps/investor-tokenization/src/features/investments/InvestmentsView.tsx (1)

119-124: Hardcoded locale in number formatting.

The "en-US" locale is hardcoded for currency formatting, which is inconsistent with the i18n approach. Consider using the current locale for consistent number formatting based on user language preference.

♻️ Proposed fix
+import { useLocale } from "next-intl";

Then in the component:

+  const currentLocale = useLocale();
   ...
-  {totalInvested.toLocaleString("en-US", {
+  {totalInvested.toLocaleString(currentLocale, {
     minimumFractionDigits: 2,
     maximumFractionDigits: 2,
   })}{" "}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/investor-tokenization/src/features/investments/InvestmentsView.tsx`
around lines 119 - 124, The number formatting uses a hardcoded "en-US" locale
when rendering totalInvested in InvestmentsView.tsx; replace that literal with
the app's current locale (e.g., obtain from your i18n hook/provider or Next.js
router.locale) and pass it to toLocaleString/Intl.NumberFormat, ensuring you
provide a sensible fallback (like 'en-US') if the locale is missing; update the
toLocaleString call on totalInvested to use that locale variable so formatting
follows the user's language preference.
packages/tw-blocks-shared/src/i18n/TranslationProvider.tsx (1)

43-60: Memoize the context value before passing it to the provider.

value={{ t, locale }} creates a new object on every parent render, so all useSharedTranslation() consumers re-render even when messages and locale are unchanged.

♻️ Proposed fix
 import {
   createContext,
   useContext,
   useCallback,
+  useMemo,
   type ReactNode,
 } from "react";
@@
 export function SharedTranslationProvider({
   messages,
   locale,
   children,
 }: SharedTranslationProviderProps) {
   const t: TranslationFn = useCallback(
     (key, params) => {
       const value = getNestedValue(messages, key);
       if (value === undefined) return key;
       return params ? interpolate(value, params) : value;
     },
     [messages],
   );
+  const value = useMemo(() => ({ t, locale }), [t, locale]);
 
   return (
-    <SharedTranslationContext.Provider value={{ t, locale }}>
+    <SharedTranslationContext.Provider value={value}>
       {children}
     </SharedTranslationContext.Provider>
   );
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/tw-blocks-shared/src/i18n/TranslationProvider.tsx` around lines 43 -
60, The provider is creating a fresh object for value on every render causing
unnecessary consumer re-renders; inside SharedTranslationProvider memoize the
context value (the object containing t and locale) with useMemo and pass that
memoized value to SharedTranslationContext.Provider instead of inline value={{
t, locale }}; ensure the dependency array includes t and locale (or
messages/locale if you prefer) so the value only changes when translation
function or locale actually change.
packages/tw-blocks-shared/src/escrows/escrows-by-signer/details/MilestoneCard.tsx (1)

23-23: Use the project import alias for TranslationProvider.

Please replace the deep relative import with the configured alias to keep imports stable across refactors.

♻️ Proposed change
-import { useSharedTranslation } from "../../../i18n/TranslationProvider";
+import { useSharedTranslation } from "@/i18n/TranslationProvider";

As per coding guidelines, **/*.{ts,tsx} must use path alias @/* mapping to ./src/* for imports.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/tw-blocks-shared/src/escrows/escrows-by-signer/details/MilestoneCard.tsx`
at line 23, Replace the deep relative import of TranslationProvider with the
project path alias: change the import that brings in useSharedTranslation from
"../../../i18n/TranslationProvider" to use the configured alias import starting
with "@/...", updating the import that provides useSharedTranslation (from the
TranslationProvider module) so the file MilestoneCard.tsx uses the `@/`* path
mapping instead of deep relative paths.
packages/tw-blocks-shared/src/escrows/escrows-by-signer/cards/EscrowsCards.tsx (1)

31-31: Prefer alias import for useSharedTranslation.

Replace the relative path with the configured alias for consistency.

As per coding guidelines, **/*.{ts,tsx} must use path alias @/* mapping to ./src/* for imports.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/tw-blocks-shared/src/escrows/escrows-by-signer/cards/EscrowsCards.tsx`
at line 31, Replace the relative import for useSharedTranslation with the
project path alias: change the import line that references useSharedTranslation
from "../../../i18n/TranslationProvider" to use the configured alias (e.g.
"@/i18n/TranslationProvider") so the component EscrowsCards.tsx uses the `@/`* ->
./src/* mapping consistently.
packages/tw-blocks-shared/src/escrows/multi-release/initialize-escrow/dialog/InitializeEscrow.tsx (1)

33-33: Import path should use @/* alias.

Please switch this relative import to the project alias to align with repo conventions.

As per coding guidelines, **/*.{ts,tsx} must use path alias @/* mapping to ./src/* for imports.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/tw-blocks-shared/src/escrows/multi-release/initialize-escrow/dialog/InitializeEscrow.tsx`
at line 33, The import in InitializeEscrow.tsx uses a relative path for
useSharedTranslation; replace the relative import "import { useSharedTranslation
} from '../../../../i18n/TranslationProvider';" with the project path-alias form
"import { useSharedTranslation } from '@/i18n/TranslationProvider';" so all
TypeScript/TSX imports follow the `@/*` -> `./src/*` convention and satisfy the
linting rule; update any other similar imports in this file referencing
TranslationProvider to the alias as well.
packages/tw-blocks-shared/src/escrows/single-multi-release/change-milestone-status/dialog/ChangeMilestoneStatus.tsx (1)

30-30: Switch this import to the @/* alias.

Use the project alias instead of a multi-level relative path for consistency and safer refactors.

As per coding guidelines, **/*.{ts,tsx} must use path alias @/* mapping to ./src/* for imports.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/tw-blocks-shared/src/escrows/single-multi-release/change-milestone-status/dialog/ChangeMilestoneStatus.tsx`
at line 30, Replace the multi-level relative import of useSharedTranslation with
the project alias form: locate the import statement in ChangeMilestoneStatus
(the line importing useSharedTranslation from
"../../../../i18n/TranslationProvider") and update it to use the `@/`* path alias
that maps to ./src/* (e.g., import useSharedTranslation from
"@/i18n/TranslationProvider"); keep the imported symbol name unchanged.
packages/tw-blocks-shared/src/escrows/escrows-by-signer/cards/Filters.tsx (1)

33-33: Use @/* alias for this import.

Please replace the relative path with the configured alias for consistency with project rules.

As per coding guidelines, **/*.{ts,tsx} must use path alias @/* mapping to ./src/* for imports.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/tw-blocks-shared/src/escrows/escrows-by-signer/cards/Filters.tsx` at
line 33, Replace the relative import in Filters.tsx with the project path alias:
change the import of useSharedTranslation from
"../../../i18n/TranslationProvider" to use the "@/..." alias (e.g. import from
"@/i18n/TranslationProvider") so it follows the required `@/*` → `./src/*`
mapping and keeps imports consistent with the project's alias rules.
packages/tw-blocks-shared/src/escrows/multi-release/resolve-dispute/dialog/ResolveDispute.tsx (1)

30-30: Use the @/* alias instead of a deep relative path.

This keeps module boundaries cleaner and avoids fragile import chains.

As per coding guidelines, **/*.{ts,tsx} must use path alias @/* mapping to ./src/* for imports.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/tw-blocks-shared/src/escrows/multi-release/resolve-dispute/dialog/ResolveDispute.tsx`
at line 30, The import in ResolveDispute.tsx currently uses a deep relative path
to TranslationProvider; change the import to use the project path alias instead
(e.g. import { useSharedTranslation } from "@/i18n/TranslationProvider") so it
follows the /*.{ts,tsx} alias rule; update the import statement in the
ResolveDispute component file (where useSharedTranslation is referenced) to use
the `@/`* mapping.
packages/tw-blocks-shared/src/escrows/multi-release/update-escrow/dialog/UpdateEscrow.tsx (1)

32-32: Use alias import instead of deep relative traversal.

Line 32 uses ../../../../..., which is brittle and conflicts with the import convention.

♻️ Proposed change
-import { useSharedTranslation } from "../../../../i18n/TranslationProvider";
+import { useSharedTranslation } from "@/i18n/TranslationProvider";

As per coding guidelines, "Use path alias @/* mapping to ./src/* for imports".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/tw-blocks-shared/src/escrows/multi-release/update-escrow/dialog/UpdateEscrow.tsx`
at line 32, Replace the brittle deep-relative import in UpdateEscrow.tsx that
currently imports useSharedTranslation via
"../../../../i18n/TranslationProvider" with the project path-alias form; update
the import for the useSharedTranslation symbol to use the alias path starting
with "@/i18n/TranslationProvider" to follow the codebase convention and avoid
deep traversal.
apps/investor-tokenization/src/features/roi/components/campaign-card.tsx (1)

19-19: Switch to @/* alias for local module import.

Line 19 uses a relative import where alias import is preferred.

♻️ Proposed change
-import { getCampaignStatusConfig } from "../constants/campaign-status";
+import { getCampaignStatusConfig } from "@/features/roi/constants/campaign-status";

As per coding guidelines, "Use path alias @/* mapping to ./src/* for imports".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/investor-tokenization/src/features/roi/components/campaign-card.tsx` at
line 19, Replace the relative import of getCampaignStatusConfig in
campaign-card.tsx with the project path-alias form (use the '@/...' mapping to
./src/*) so the module is imported via the aliased campaign-status module
instead of a relative path; update the import that references
getCampaignStatusConfig from the campaign-status module to use the `@/`* alias.
apps/backoffice-tokenization/src/i18n/request.ts (1)

2-2: Use project alias import for routing in i18n request config.

Line 2 uses a relative internal import; this should use the @/* alias for consistency with project import rules.

♻️ Suggested fix
-import { routing } from "./routing";
+import { routing } from "@/i18n/routing";

As per coding guidelines: "Use path alias @/* mapping to ./src/* for imports".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/backoffice-tokenization/src/i18n/request.ts` at line 2, Replace the
relative import of the routing module with the project path-alias form: change
the import that currently references "./routing" in
apps/backoffice-tokenization/src/i18n/request.ts to use "@/i18n/routing" so the
module referenced by the routing symbol is imported via the `@/`* alias (update
the import statement that imports routing accordingly).
apps/backoffice-tokenization/src/features/campaigns/hooks/use-create-campaign.ts (1)

308-313: Add tCommon to the runDeployAndCreate dependencies.

Line 309 closes over tCommon, but Line 313 doesn’t track it. That leaves the Step 3 error handler vulnerable to stale translations after a locale change.

♻️ Minimal fix
-  }, [walletAddress, router, queryClient]);
+  }, [walletAddress, router, queryClient, tCommon]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/backoffice-tokenization/src/features/campaigns/hooks/use-create-campaign.ts`
around lines 308 - 313, The callback runDeployAndCreate closes over the
translation function tCommon but its dependency array only contains
[walletAddress, router, queryClient], causing stale translations after locale
changes; update the useCallback dependency array for runDeployAndCreate to
include tCommon (the translation function referenced when building the error
message), so the function is recreated when tCommon changes and the
setPhaseStatus / setDeployFailedAt error handler uses the current locale.
apps/investor-tokenization/src/app/[locale]/layout.tsx (1)

17-18: Prefer @/... imports for app-local modules.

These relative imports are fragile during refactors and don’t follow the TS/TSX import-path guideline.

As per coding guidelines: "Use path alias @/* mapping to ./src/* for imports".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/investor-tokenization/src/app/`[locale]/layout.tsx around lines 17 - 18,
In layout.tsx replace the fragile relative imports of appEn and appEs with the
path-alias imports mandated by our guideline: import the JSONs via the `@/`* alias
(e.g., "@/messages/en.json" and "@/messages/es.json") and ensure the import
symbols appEn and appEs are used unchanged; verify the tsconfig/next.config
path-mapping for @ -> ./src is present so the new imports resolve correctly.
apps/backoffice-tokenization/src/app/[locale]/layout.tsx (1)

17-18: Prefer @/... imports for app-local modules.

These deep relative imports are brittle and break the repository import convention for TS/TSX files.

As per coding guidelines: "Use path alias @/* mapping to ./src/* for imports".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/backoffice-tokenization/src/app/`[locale]/layout.tsx around lines 17 -
18, Replace the brittle deep-relative imports of localized JSON in layout.tsx by
using the project path alias; change the imports that define appEn and appEs
(currently from "../../../messages/en.json" and "../../../messages/es.json") to
use the alias (e.g. "@/messages/en.json" and "@/messages/es.json") so they
follow the `@/*` -> `./src/*` convention and build tooling; ensure the
tsconfig/next config already has the alias mapping if missing.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 266b29b1-db85-4bfa-9ae6-f80329224403

📥 Commits

Reviewing files that changed from the base of the PR and between 349c516 and 2743cbc.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (99)
  • apps/backoffice-tokenization/messages/en.json
  • apps/backoffice-tokenization/messages/es.json
  • apps/backoffice-tokenization/next.config.ts
  • apps/backoffice-tokenization/src/app/[locale]/(dashboard)/campaigns/loans/[id]/page.tsx
  • apps/backoffice-tokenization/src/app/[locale]/(dashboard)/campaigns/new/page.tsx
  • apps/backoffice-tokenization/src/app/[locale]/(dashboard)/campaigns/page.tsx
  • apps/backoffice-tokenization/src/app/[locale]/(dashboard)/layout.tsx
  • apps/backoffice-tokenization/src/app/[locale]/(dashboard)/roi/page.tsx
  • apps/backoffice-tokenization/src/app/[locale]/layout.tsx
  • apps/backoffice-tokenization/src/app/[locale]/page.tsx
  • apps/backoffice-tokenization/src/app/layout.tsx
  • apps/backoffice-tokenization/src/components/layout/app-header.tsx
  • apps/backoffice-tokenization/src/components/layout/app-sidebar.tsx
  • apps/backoffice-tokenization/src/components/shared/campaign-card.tsx
  • apps/backoffice-tokenization/src/components/shared/language-switcher.tsx
  • apps/backoffice-tokenization/src/features/campaigns/components/campaign-filter.tsx
  • apps/backoffice-tokenization/src/features/campaigns/components/campaign-list.tsx
  • apps/backoffice-tokenization/src/features/campaigns/components/campaign-search.tsx
  • apps/backoffice-tokenization/src/features/campaigns/components/campaigns-view.tsx
  • apps/backoffice-tokenization/src/features/campaigns/components/create/create-campaign-stepper.tsx
  • apps/backoffice-tokenization/src/features/campaigns/components/create/step-campaign-basics.tsx
  • apps/backoffice-tokenization/src/features/campaigns/components/create/step-create-token.tsx
  • apps/backoffice-tokenization/src/features/campaigns/components/create/step-escrow-config.tsx
  • apps/backoffice-tokenization/src/features/campaigns/components/loans/manage-loans-view.tsx
  • apps/backoffice-tokenization/src/features/campaigns/components/roi/FundRoiDialog.tsx
  • apps/backoffice-tokenization/src/features/campaigns/components/roi/ToggleVaultButton.tsx
  • apps/backoffice-tokenization/src/features/campaigns/components/roi/create-roi-dialog.tsx
  • apps/backoffice-tokenization/src/features/campaigns/components/roi/roi-table-row.tsx
  • apps/backoffice-tokenization/src/features/campaigns/components/roi/roi-table.tsx
  • apps/backoffice-tokenization/src/features/campaigns/components/roi/roi-view.tsx
  • apps/backoffice-tokenization/src/features/campaigns/constants/campaign-status.ts
  • apps/backoffice-tokenization/src/features/campaigns/hooks/use-create-campaign.ts
  • apps/backoffice-tokenization/src/features/home/BentoGrid.tsx
  • apps/backoffice-tokenization/src/features/home/HeroSection.tsx
  • apps/backoffice-tokenization/src/features/tokens/deploy/dialog/TokenizeEscrow.tsx
  • apps/backoffice-tokenization/src/features/tokens/deploy/dialog/TokenizeEscrowSuccessDialog.tsx
  • apps/backoffice-tokenization/src/features/vaults/deploy/dialog/CreateVault.tsx
  • apps/backoffice-tokenization/src/features/vaults/deploy/dialog/EnableVault.tsx
  • apps/backoffice-tokenization/src/features/vaults/deploy/dialog/VaultDeploySuccessDialog.tsx
  • apps/backoffice-tokenization/src/i18n/navigation.ts
  • apps/backoffice-tokenization/src/i18n/request.ts
  • apps/backoffice-tokenization/src/i18n/routing.ts
  • apps/backoffice-tokenization/src/lib/contractErrorHandler.ts
  • apps/backoffice-tokenization/src/lib/toastWithTx.tsx
  • apps/backoffice-tokenization/src/middleware.ts
  • apps/investor-tokenization/messages/en.json
  • apps/investor-tokenization/messages/es.json
  • apps/investor-tokenization/next.config.ts
  • apps/investor-tokenization/src/app/[locale]/campaigns/layout.tsx
  • apps/investor-tokenization/src/app/[locale]/campaigns/page.tsx
  • apps/investor-tokenization/src/app/[locale]/claim-roi/page.tsx
  • apps/investor-tokenization/src/app/[locale]/investments/page.tsx
  • apps/investor-tokenization/src/app/[locale]/layout.tsx
  • apps/investor-tokenization/src/app/[locale]/my-investments/layout.tsx
  • apps/investor-tokenization/src/app/[locale]/my-investments/page.tsx
  • apps/investor-tokenization/src/app/[locale]/page.tsx
  • apps/investor-tokenization/src/app/layout.tsx
  • apps/investor-tokenization/src/components/layout/app-header.tsx
  • apps/investor-tokenization/src/components/layout/app-sidebar.tsx
  • apps/investor-tokenization/src/components/shared/language-switcher.tsx
  • apps/investor-tokenization/src/features/claim-roi/ClaimROIView.tsx
  • apps/investor-tokenization/src/features/claim-roi/VaultCard.tsx
  • apps/investor-tokenization/src/features/investments/InvestmentsView.tsx
  • apps/investor-tokenization/src/features/investments/components/InvestmentCard.tsx
  • apps/investor-tokenization/src/features/roi/components/campaign-card.tsx
  • apps/investor-tokenization/src/features/roi/components/campaign-filter.tsx
  • apps/investor-tokenization/src/features/roi/components/campaign-list.tsx
  • apps/investor-tokenization/src/features/roi/components/campaign-search.tsx
  • apps/investor-tokenization/src/features/roi/constants/campaign-status.ts
  • apps/investor-tokenization/src/features/tokens/components/InvestDialog.tsx
  • apps/investor-tokenization/src/features/transparency/ProjectCard.tsx
  • apps/investor-tokenization/src/features/transparency/ProjectList.tsx
  • apps/investor-tokenization/src/i18n/navigation.ts
  • apps/investor-tokenization/src/i18n/request.ts
  • apps/investor-tokenization/src/i18n/routing.ts
  • apps/investor-tokenization/src/lib/contractErrorHandler.ts
  • apps/investor-tokenization/src/middleware.ts
  • package.json
  • packages/tw-blocks-shared/src/escrows/escrows-by-signer/cards/EscrowsCards.tsx
  • packages/tw-blocks-shared/src/escrows/escrows-by-signer/cards/Filters.tsx
  • packages/tw-blocks-shared/src/escrows/escrows-by-signer/details/Entities.tsx
  • packages/tw-blocks-shared/src/escrows/escrows-by-signer/details/GeneralInformation.tsx
  • packages/tw-blocks-shared/src/escrows/escrows-by-signer/details/MilestoneCard.tsx
  • packages/tw-blocks-shared/src/escrows/escrows-by-signer/details/SuccessReleaseDialog.tsx
  • packages/tw-blocks-shared/src/escrows/multi-release/dispute-milestone/button/DisputeMilestone.tsx
  • packages/tw-blocks-shared/src/escrows/multi-release/initialize-escrow/dialog/InitializeEscrow.tsx
  • packages/tw-blocks-shared/src/escrows/multi-release/release-milestone/button/ReleaseMilestone.tsx
  • packages/tw-blocks-shared/src/escrows/multi-release/resolve-dispute/dialog/ResolveDispute.tsx
  • packages/tw-blocks-shared/src/escrows/multi-release/update-escrow/dialog/UpdateEscrow.tsx
  • packages/tw-blocks-shared/src/escrows/multi-release/withdraw-remaining-funds/dialog/WithdrawRemainingFunds.tsx
  • packages/tw-blocks-shared/src/escrows/single-multi-release/approve-milestone/button/ApproveMilestone.tsx
  • packages/tw-blocks-shared/src/escrows/single-multi-release/approve-milestone/dialog/ApproveMilestone.tsx
  • packages/tw-blocks-shared/src/escrows/single-multi-release/approve-milestone/form/ApproveMilestone.tsx
  • packages/tw-blocks-shared/src/escrows/single-multi-release/change-milestone-status/dialog/ChangeMilestoneStatus.tsx
  • packages/tw-blocks-shared/src/escrows/single-multi-release/fund-escrow/dialog/FundEscrow.tsx
  • packages/tw-blocks-shared/src/i18n/TranslationProvider.tsx
  • packages/tw-blocks-shared/src/i18n/messages/en.json
  • packages/tw-blocks-shared/src/i18n/messages/es.json
  • packages/tw-blocks-shared/src/wallet-kit/WalletButtons.tsx

Comment on lines +41 to +51
const { locale } = await params;
const messages = appMessages[locale] ?? appMessages.es;

return (
<html lang={locale}>
<head>
<meta charSet="utf-8" />
</head>
<body className={cn(inter.variable, "antialiased font-sans")}>
<NextIntlClientProvider messages={messages} locale={locale} formats={{}} now={new Date()} timeZone={Intl.DateTimeFormat().resolvedOptions().timeZone}>
<SharedTranslationProvider messages={sharedMessages[locale] ?? sharedMessages.es} locale={locale}>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Normalize unsupported locales before wiring providers.

Line 42 falls back messages to Spanish, but Lines 45/50/51 still use raw locale. If an unsupported locale slips through, text locale and provider/html locale diverge.

💡 Proposed fix
-  const { locale } = await params;
-  const messages = appMessages[locale] ?? appMessages.es;
+  const { locale: rawLocale } = await params;
+  const locale = rawLocale === "en" || rawLocale === "es" ? rawLocale : "es";
+  const messages = appMessages[locale];
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const { locale } = await params;
const messages = appMessages[locale] ?? appMessages.es;
return (
<html lang={locale}>
<head>
<meta charSet="utf-8" />
</head>
<body className={cn(inter.variable, "antialiased font-sans")}>
<NextIntlClientProvider messages={messages} locale={locale} formats={{}} now={new Date()} timeZone={Intl.DateTimeFormat().resolvedOptions().timeZone}>
<SharedTranslationProvider messages={sharedMessages[locale] ?? sharedMessages.es} locale={locale}>
const { locale: rawLocale } = await params;
const locale = rawLocale === "en" || rawLocale === "es" ? rawLocale : "es";
const messages = appMessages[locale];
return (
<html lang={locale}>
<head>
<meta charSet="utf-8" />
</head>
<body className={cn(inter.variable, "antialiased font-sans")}>
<NextIntlClientProvider messages={messages} locale={locale} formats={{}} now={new Date()} timeZone={Intl.DateTimeFormat().resolvedOptions().timeZone}>
<SharedTranslationProvider messages={sharedMessages[locale] ?? sharedMessages.es} locale={locale}>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/backoffice-tokenization/src/app/`[locale]/layout.tsx around lines 41 -
51, Normalize the locale once and use the normalized value everywhere: compute
an effectiveLocale (e.g., const effectiveLocale = appMessages[locale] ? locale :
'es') before creating messages/sharedMessages and before rendering <html
lang={...}>, then use appMessages[effectiveLocale],
sharedMessages[effectiveLocale], and pass effectiveLocale into
NextIntlClientProvider and SharedTranslationProvider (instead of the raw
params.locale) so html lang and both providers always agree.

<div className="flex flex-col gap-1">
<span className="text-xs font-bold text-foreground">
<span className="font-bold">Pool Size:</span> USDC {formatCurrency(escrowData?.balance ?? 0)} / USDC {formatCurrency(campaign.poolSize)}
<span className="font-bold">{t("poolSize")}:</span> USDC {formatCurrency(escrowData?.balance ?? 0)} / USDC {formatCurrency(campaign.poolSize)}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n -C3 'fromStroops|10_000_000|escrowData|getEscrowByContractIds|useGetEscrowFromIndexerByContractIds'

Repository: Trustless-Work/tokenization-private-credit

Length of output: 37972


Normalize escrowData.balance to display units before formatting.

Line 89 formats escrowData?.balance directly, but the indexer returns escrow balances in stroops (7 decimal places). The same escrow object's milestones array is explicitly normalized via fromStroops throughout the codebase (see ProjectCard.tsx:81, Carousel.tsx:36), and roi-table-row.tsx:60 normalizes the balance by dividing by 10_000_000. Without conversion, the pool size will display 10 million times larger than the actual value.

Apply fromStroops() to escrowData.balance before passing to formatCurrency:

Suggested fix
// Line 89: Convert balance from stroops to display units
<span className="font-bold">{t("poolSize")}:</span> USDC {formatCurrency(fromStroops(escrowData?.balance ?? 0))} / USDC {formatCurrency(campaign.poolSize)}

Import fromStroops from @/lib/utils if not already imported.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/backoffice-tokenization/src/components/shared/campaign-card.tsx` at line
89, escrowData.balance is in stroops and is being passed raw to formatCurrency
in the CampaignCard component (span that renders USDC
{formatCurrency(escrowData?.balance ?? 0)}), causing a 10Mx inflation; convert
the balance using fromStroops() before formatting (i.e., call
formatCurrency(fromStroops(escrowData?.balance ?? 0))) and add an import for
fromStroops from "@/lib/utils"; update the span that references
escrowData.balance and ensure no other references in campaign-card.tsx use the
raw stroop value.

Comment on lines 133 to 137
<div className="rounded-lg border bg-muted/50 p-4">
<p className="text-sm text-muted-foreground">
<strong>Note:</strong> This token represents Escrow{" "}
<span className="font-mono text-xs">
{form.watch("escrowId") || "[Escrow ID]"}
</span>{" "}
and can only be minted by its Token Sale contract.
<strong>Note:</strong>{" "}
{t("tokenNote", { escrowId: form.watch("escrowId") || "[Escrow ID]" })}
</p>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

This note block is still partially hardcoded in English.

Lines 135-136 bypass the tokens messages with Note: and "[Escrow ID]", so the localized dialog still leaks English in /es/.... Move both pieces into translations, or fold the whole sentence into a single message.

🌐 One possible fix
-                  <strong>Note:</strong>{" "}
-                  {t("tokenNote", { escrowId: form.watch("escrowId") || "[Escrow ID]" })}
+                  <strong>{t("noteLabel")}</strong>{" "}
+                  {t("tokenNote", {
+                    escrowId: form.watch("escrowId") || t("escrowIdFallback"),
+                  })}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/backoffice-tokenization/src/features/tokens/deploy/dialog/TokenizeEscrow.tsx`
around lines 133 - 137, The note text in TokenizeEscrow.tsx currently hardcodes
"Note:" and the fallback string "[Escrow ID]" around t("tokenNote"), causing
untranslated English to leak; change the UI to pass the entire sentence as a
single translation key (e.g., replace the split JSX with t("tokenNote", {
escrowId: form.watch("escrowId") })) and move the "Note:" label and the fallback
placeholder into the localization resource (use a placeholder like {{escrowId}}
in the tokenNote message so the translation contains the full sentence including
the fallback text instead of the hardcoded "[Escrow ID]").

onClick={() => copyToClipboard(response.tokenFactoryAddress)}
>
{copiedKeyId ? "Copied!" : "Copy"}
{copiedKeyId ? t("copied") : t("copy")}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Scope “copied” state per field to avoid false positive labels.

Line 59 and Line 92 only check truthiness of copiedKeyId, so copying one address can make both buttons render the copied label.

🐛 Suggested fix
-                  {copiedKeyId ? t("copied") : t("copy")}
+                  {copiedKeyId === response.tokenFactoryAddress ? t("copied") : t("copy")}
...
-                  {copiedKeyId ? t("copied") : t("copy")}
+                  {copiedKeyId === response.tokenSaleAddress ? t("copied") : t("copy")}

Also applies to: 92-92

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/backoffice-tokenization/src/features/tokens/deploy/dialog/TokenizeEscrowSuccessDialog.tsx`
at line 59, The "copied" label is rendered based only on the truthiness of
copiedKeyId so copying one address makes all buttons show "copied"; change the
rendering logic to scope the copied state to the specific field by storing (or
comparing) the id of the copied item and using an equality check per button
(e.g., use copiedKeyId === keyId or maintain a Set/map of copied ids) when
rendering the label and when setting the state in the copy handlers; update any
places that read copiedKeyId (the value used in the button label and the copy
handlers in TokenizeEscrowSuccessDialog) to use this per-field comparison so
only the copied button shows "copied".

Comment on lines +7 to +10
export function toastSuccessWithTx(
message: string,
txHash?: string,
viewTransactionLabel: string = "View Transaction",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove the English default from this helper.

The fallback on Line 10 lets callers skip localization silently, so this toast can regress to English whenever a call site forgets the CTA label. Making the label explicit here turns that into a compile-time miss instead of a runtime i18n leak.

💡 Minimal fix
 export function toastSuccessWithTx(
   message: string,
-  txHash?: string,
-  viewTransactionLabel: string = "View Transaction",
+  txHash: string | undefined,
+  viewTransactionLabel: string,
 ) {

Also applies to: 20-20

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/backoffice-tokenization/src/lib/toastWithTx.tsx` around lines 7 - 10,
The helper toastSuccessWithTx currently provides an English default for
viewTransactionLabel which lets callers omit a localized label; remove the
default value so the signature toastSuccessWithTx(message: string, txHash?:
string, viewTransactionLabel: string) requires the caller to pass the localized
CTA. Apply the same change to the corresponding toastFailureWithTx function (the
other helper around line 20) so neither function supplies a hardcoded fallback
and both force callers to provide a localized label.

"minimalistic-crypto-utils": "^1.0.1",
"motion": "^10.16.2",
"next": "16.0.10",
"next-intl": "^4.8.3",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

next-intl Next.js 16 compatibility

💡 Result:

next-intl is compatible with Next.js 16 starting from next-intl v4.4.0 (2025-10-22), which bumps the Next.js 16 peer dependency and updates the docs/examples for Next.js 16, including the new proxy.ts setup. (github.com)

If you upgrade to Next.js 16 while on an older next-intl, locale detection/routing can break because Next.js 16 expects proxy.ts instead of middleware.ts—upgrading to next-intl ≥ 4.4.0 and following the updated routing docs fixes this. (github.com)

Citations:


Update routing setup from middleware.ts to proxy.ts for Next.js 16 compatibility.

The next-intl version ^4.8.3 is compatible with Next.js 16.0.10 (supported from v4.4.0). However, Next.js 16 requires locale routing through proxy.ts instead of the previous middleware.ts approach. Without this setup change, locale detection and routing may break. Update the routing configuration and follow the updated next-intl documentation for Next.js 16.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@package.json` at line 49, Update the Next.js routing setup to use proxy.ts
for locale routing required by Next.js 16 and next-intl (v4.8.3) by replacing
the existing middleware-based approach: locate the current middleware.ts usage
(and any middleware-based locale handling) and create a proxy.ts implementing
the next-intl recommended proxy routing pattern, move locale detection/rewrites
and any exports used by next-intl into proxy.ts, and remove or disable
middleware.ts so that next-intl uses the new proxy-based routing as described in
the next-intl Next.js 16 docs.

Comment on lines 264 to 268
variant={isActive ? "default" : "destructive"}
className="shrink-0"
>
{isActive ? "Active" : "Inactive"}
{isActive ? t("escrow.filters.active") : t("escrow.filters.inactive")}
</Badge>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Active badge is derived from filter state, not the escrow card data.

This makes every card show the same active/inactive label based on current filter toggles instead of each escrow’s real status.

🐛 Proposed fix
-<Badge
-  variant={isActive ? "default" : "destructive"}
-  className="shrink-0"
->
-  {isActive ? t("escrow.filters.active") : t("escrow.filters.inactive")}
-</Badge>
+<Badge
+  variant={escrow.isActive ? "default" : "destructive"}
+  className="shrink-0"
+>
+  {escrow.isActive ? t("escrow.filters.active") : t("escrow.filters.inactive")}
+</Badge>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/tw-blocks-shared/src/escrows/escrows-by-signer/cards/EscrowsCards.tsx`
around lines 264 - 268, The Badge in EscrowsCards.tsx currently uses the
filter-derived isActive variable so every card shows the same label; change the
logic to derive active/inactive from the escrow item data (e.g., escrow.isActive
or escrow.status === "active") instead of the filter state, update the
conditional where Badge's variant and text are set (the existing isActive usage)
to reference the escrow record's active flag/property, and ensure any prop
passed into the Escrow card component supplies that escrow data if needed
(adjust prop names like escrow or item accordingly).

} from "@trustless-work/escrow/types";
import { EntityCard } from "./EntityCard";
import { Separator } from "@tokenization/ui/separator";
import { useSharedTranslation } from "../../../i18n/TranslationProvider";
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Use the @/* alias instead of a deep relative import.

Line 9 uses a fragile relative path (../../../i18n/TranslationProvider). Please switch it to the configured @/* alias.

Suggested change
-import { useSharedTranslation } from "../../../i18n/TranslationProvider";
+import { useSharedTranslation } from "@/i18n/TranslationProvider";

As per coding guidelines, "Use path alias @/* mapping to ./src/* for imports".

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { useSharedTranslation } from "../../../i18n/TranslationProvider";
import { useSharedTranslation } from "@/i18n/TranslationProvider";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/tw-blocks-shared/src/escrows/escrows-by-signer/details/Entities.tsx`
at line 9, Replace the fragile deep relative import of the translation provider
with the project path alias: change the import of useSharedTranslation from
"../../../i18n/TranslationProvider" to use the `@/`* alias (e.g.
"@/i18n/TranslationProvider") in the Entities.tsx file so the symbol
useSharedTranslation is imported via the aliased module instead of a relative
path.

Comment on lines +126 to +129
{t("escrow.successRelease.title")}
</DialogTitle>
<DialogDescription>
Funds were distributed successfully to the corresponding parties.
{t("escrow.successRelease.description")}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

This dialog is still partly hard-coded in English.

The header is localized now, but the cards model below still builds visible labels like Platform, Trustless Work, Receiver, and Private as raw English strings. /es will therefore show a mixed-language success dialog unless those display labels are routed through shared messages too.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/tw-blocks-shared/src/escrows/escrows-by-signer/details/SuccessReleaseDialog.tsx`
around lines 126 - 129, The card display labels in SuccessReleaseDialog (the
`cards` model used to render Platform, Trustless Work, Receiver, Private, etc.)
are hard-coded in English; update the `cards` construction inside the
SuccessReleaseDialog component to use the translation function (e.g., t("...")
with appropriate shared message keys) for every user-facing label and tooltip
instead of raw strings, ensuring you reference the shared message keys used
elsewhere in the app so the dialog renders fully localized for other locales.

Comment on lines +72 to +75
<span className="ml-2">{t("escrow.approve.approving")}</span>
</div>
) : (
"Approve"
t("escrow.approve.approve")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Only the submit button was localized.

Milestone, Select milestone, and the fallback Milestone ${idx + 1} remain hard-coded above, so this form will still render partly in English for Spanish users. Please add keys for those labels/placeholders/fallbacks alongside the button copy.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/tw-blocks-shared/src/escrows/single-multi-release/approve-milestone/form/ApproveMilestone.tsx`
around lines 72 - 75, The form still has hard-coded strings for the milestone
label, the select placeholder, and the fallback "Milestone ${idx + 1}"; update
ApproveMilestone.tsx to replace those literals with i18n keys using the existing
t(...) function (e.g., add keys like "escrow.approve.milestone",
"escrow.approve.selectMilestone" and a parametrized fallback key such as
"escrow.approve.milestoneFallback" and call it with { number: idx + 1 }) so the
label, placeholder, and fallback are localized consistently with the submit
button.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
apps/backoffice-tokenization/src/features/campaigns/components/loans/manage-loans-view.tsx (2)

79-99: ⚠️ Potential issue | 🟡 Minor

Add missing dependencies to fetchEscrow callback

fetchEscrow captures t, getEscrowByContractIds, and setSelectedEscrow but is memoized with an empty dependency array. Since fetchEscrow is used as a dependency in the useEffect below it and in other handlers, these missing dependencies can cause stale closures—particularly t which changes when the locale switches, leading to stale error messages and potentially stale API responses.

Suggested fix
  const fetchEscrow = useCallback(
    async (escrowId: string) => {
      setLoading(true);
      setError(null);
      try {
        // eslint-disable-next-line `@typescript-eslint/no-explicit-any`
        const data = (await getEscrowByContractIds({
          contractIds: [escrowId],
          validateOnChain: true,
        })) as any;
        if (!data || !data[0]) throw new Error(t("escrowNotFound"));
        setSelectedEscrow(data[0]);
      } catch (err) {
        setError(handleError(err as ErrorResponse).message);
      } finally {
        setLoading(false);
      }
    },
-    // eslint-disable-next-line react-hooks/exhaustive-deps
-    [],
+    [getEscrowByContractIds, setSelectedEscrow, t],
  );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/backoffice-tokenization/src/features/campaigns/components/loans/manage-loans-view.tsx`
around lines 79 - 99, The fetchEscrow useCallback currently has an empty
dependency array causing stale closures; update its dependencies to include all
referenced values: t, getEscrowByContractIds, setSelectedEscrow, setLoading,
setError, and handleError (and ErrorResponse if it's a variable/function) so the
callback is recreated when any of those change; retain the async logic inside
fetchEscrow and only remove the eslint-disable comment after adding these
dependencies to the useCallback call.

84-89: ⚠️ Potential issue | 🟠 Major

Remove the as any cast and use proper type narrowing for escrow fetch response.

The as any cast on line 88 violates the TypeScript guideline. The codebase pattern (seen in apple-cards-carousel.tsx and ProjectList.tsx) is to cast to unknown, then type-narrow with an array check:

Suggested fix
-        // eslint-disable-next-line `@typescript-eslint/no-explicit-any`
-        const data = (await getEscrowByContractIds({
+        const data = (await getEscrowByContractIds({
           contractIds: [escrowId],
           validateOnChain: true,
-        })) as any;
+        })) as unknown;
         if (!data || !data[0]) throw new Error(t("escrowNotFound"));
         setSelectedEscrow(data[0]);

Alternatively, cast to the proper type from the external package:

-        const data = (await getEscrowByContractIds({
+        const data = (await getEscrowByContractIds({
           contractIds: [escrowId],
           validateOnChain: true,
-        })) as any;
-        if (!data || !data[0]) throw new Error(t("escrowNotFound"));
+        })) as unknown;
+        if (!Array.isArray(data) || !data[0]) throw new Error(t("escrowNotFound"));
         setSelectedEscrow(data[0]);

Per coding guidelines: **/*.{ts,tsx}: Never use any in TypeScript; use unknown with type narrowing instead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/backoffice-tokenization/src/features/campaigns/components/loans/manage-loans-view.tsx`
around lines 84 - 89, Remove the `as any` cast on the getEscrowByContractIds
result: call getEscrowByContractIds({ contractIds: [escrowId], validateOnChain:
true }) and assign it to an unknown-typed variable, then narrow it by checking
Array.isArray(data) && data.length > 0 before accessing data[0]; after the array
check, you can safely treat data as the proper escrow array (or cast to the
external package type, e.g., Escrow[] from the SDK) and keep the existing throw
new Error(t("escrowNotFound")) if the check fails. Target the
getEscrowByContractIds call and the local data usage in manage-loans-view.tsx
and remove the `as any` cast.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In
`@apps/backoffice-tokenization/src/features/campaigns/components/loans/manage-loans-view.tsx`:
- Around line 79-99: The fetchEscrow useCallback currently has an empty
dependency array causing stale closures; update its dependencies to include all
referenced values: t, getEscrowByContractIds, setSelectedEscrow, setLoading,
setError, and handleError (and ErrorResponse if it's a variable/function) so the
callback is recreated when any of those change; retain the async logic inside
fetchEscrow and only remove the eslint-disable comment after adding these
dependencies to the useCallback call.
- Around line 84-89: Remove the `as any` cast on the getEscrowByContractIds
result: call getEscrowByContractIds({ contractIds: [escrowId], validateOnChain:
true }) and assign it to an unknown-typed variable, then narrow it by checking
Array.isArray(data) && data.length > 0 before accessing data[0]; after the array
check, you can safely treat data as the proper escrow array (or cast to the
external package type, e.g., Escrow[] from the SDK) and keep the existing throw
new Error(t("escrowNotFound")) if the check fails. Target the
getEscrowByContractIds call and the local data usage in manage-loans-view.tsx
and remove the `as any` cast.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ea66605d-e8aa-4b79-9076-2c445461e6a6

📥 Commits

Reviewing files that changed from the base of the PR and between 2743cbc and de35b6a.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (4)
  • apps/backoffice-tokenization/src/components/shared/campaign-card.tsx
  • apps/backoffice-tokenization/src/features/campaigns/components/loans/manage-loans-view.tsx
  • apps/backoffice-tokenization/src/features/campaigns/components/roi/roi-table-row.tsx
  • apps/investor-tokenization/src/features/roi/components/campaign-card.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • apps/backoffice-tokenization/src/components/shared/campaign-card.tsx
  • apps/investor-tokenization/src/features/roi/components/campaign-card.tsx
  • apps/backoffice-tokenization/src/features/campaigns/components/roi/roi-table-row.tsx

@vercel
Copy link
Copy Markdown

vercel bot commented Mar 14, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
backoffice-tokenization Error Error Mar 14, 2026 2:23am
investor Ready Ready Preview, Comment Mar 14, 2026 2:23am

Request Review

@JoelVR17 JoelVR17 merged commit cd6567d into Trustless-Work:develop Mar 14, 2026
3 of 4 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Mar 14, 2026
@coderabbitai coderabbitai bot mentioned this pull request Mar 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants