diff --git a/apps/backoffice-tokenization/messages/en.json b/apps/backoffice-tokenization/messages/en.json new file mode 100644 index 0000000..f181ce4 --- /dev/null +++ b/apps/backoffice-tokenization/messages/en.json @@ -0,0 +1,266 @@ +{ + "common": { + "loading": "Loading...", + "error": "Error", + "cancel": "Cancel", + "retry": "Retry", + "continue": "Continue", + "next": "Next →", + "previous": "Previous", + "save": "Save", + "create": "Create", + "update": "Update", + "delete": "Delete", + "close": "Close", + "viewTransaction": "View Transaction", + "unknownError": "Unknown error", + "connectWallet": "Connect your wallet to create a campaign" + }, + "metadata": { + "title": "Backoffice Tokenization", + "description": "Backoffice Tokenization" + }, + "nav": { + "campaigns": "Campaigns", + "roi": "Return on Investment" + }, + "home": { + "heroTitle": "Entrepreneur Support", + "heroDescription": "We accompany entrepreneurs and micro-business owners with solutions that drive the growth of their businesses.", + "heroTagline": "Growth with impact.", + "openApp": "Open backoffice", + "aboutUs": "About us", + "cardFinancing": "Financing", + "cardFinancingDesc": "Credit solutions designed to boost the growth of entrepreneurs and micro-businesses.", + "cardSupport": "Support", + "cardSupportDesc": "Guidance and support to strengthen capabilities, make decisions, and move forward with confidence.", + "cardStrengthening": "Business Strengthening", + "cardStrengtheningDesc": "Tools and opportunities to build sustainable businesses with impact.", + "cardOpportunities": "Opportunities", + "cardOpportunitiesDesc": "We connect entrepreneurs and micro-businesses with solutions that pave the way for growth and business consolidation.", + "heroHighlight": "Oversight", + "bentoDeployTitle": "Deploy Core Contracts", + "bentoDeployDesc": "Set up the Escrow, Token Sale, and Participation Token that power the project's funding flow.", + "bentoApprovalsTitle": "Milestone Approvals", + "bentoApprovalsDesc": "Review and approve project milestones to authorize on-chain fund releases.", + "bentoReleasesTitle": "Escrow Releases", + "bentoReleasesDesc": "Trigger controlled USDC releases as each milestone is completed and validated.", + "bentoDisputeTitle": "Dispute Management", + "bentoDisputeDesc": "Pause releases, review evidence, and resolve issues when project conditions are not met.", + "bentoMonitorTitle": "Project & Contract Monitoring", + "bentoMonitorDesc": "View contract addresses, escrow balances, sale status, and real-time project activity.", + "bentoSetup": "Setup", + "bentoApproval": "Approval", + "bentoRelease": "Release", + "bentoDeployCards": "Deploy core contracts for each project", + "bentoApproveCards": "Review and approve project milestones", + "bentoReleaseCards": "Release escrow funds as milestones are completed", + "bentoManageCards": "Manage deployments, configure milestones, and run on-chain releases.", + "bentoOperations": "Operations" + }, + "campaigns": { + "title": "Campaigns", + "description": "Browse and manage active investment campaigns.", + "newCampaign": "New Campaign", + "loading": "Loading campaigns...", + "loadError": "Failed to load campaigns.", + "empty": "No campaigns available.", + "manageLoans": "Manage Loans", + "poolSize": "Pool Size", + "loansCompleted": "Loans Completed", + "loans": "Loans", + "noLoansAvailable": "No loans available.", + "loan": "Loan {index}", + "filterAll": "All", + "filterFundraising": "Fundraising", + "filterActive": "Active", + "filterClosed": "Closed", + "searchPlaceholder": "Search campaigns...", + "status": { + "DRAFT": "Draft", + "FUNDRAISING": "Fundraising", + "ACTIVE": "Active", + "REPAYMENT": "Repayment", + "CLAIMABLE": "Claimable", + "CLOSED": "Closed", + "PAUSED": "Paused" + } + }, + "createCampaign": { + "title": "New Campaign", + "description": "Complete the steps to set up your micro-loan program.", + "step1": "Campaign Basics", + "step2": "Initialize Escrow", + "step3": "Deploy & Create", + "nameLabel": "Campaign Name", + "namePlaceholder": "e.g. Micro-Loans for Women Entrepreneurs", + "descriptionLabel": "Description", + "descriptionPlaceholder": "Briefly describe the purpose of this fund...", + "tokenNameLabel": "Token Name", + "tokenNamePlaceholder": "e.g. AgriGrowth Bond", + "poolSizeLabel": "Pool Size (USD)", + "loanSizeLabel": "Loan Size (USD)", + "loanDurationLabel": "Loan Duration (months)", + "expectedReturnLabel": "Expected Return (%)", + "initializingEscrow": "Initializing escrow...", + "escrowInitError": "Error initializing escrow", + "escrowInitSuccess": "Escrow initialized successfully", + "deployingTitle": "Deploying contracts and creating campaign", + "deployPhase1": "Creating participation token and tokenizing", + "deployPhase2": "Final steps...", + "validation": { + "nameRequired": "Name is required", + "descriptionMin": "Description must be at least 10 characters", + "mustBePositive": "Must be greater than 0", + "tokenNameRequired": "Token name is required" + } + }, + "loans": { + "title": "Manage Loans", + "description": "Manage milestones and loans for the campaign.", + "beneficiaries": "Beneficiaries", + "noMilestones": "No milestones registered.", + "status": "Status", + "disbursed": "Disbursed", + "disburse": "Disburse", + "approve": "Approve", + "insufficientFunds": "Insufficient funds in escrow", + "changeLoanStatus": "Change loan status", + "statusLabel": "Status", + "statusPlaceholder": "e.g. completed", + "evidenceLabel": "Evidence", + "evidencePlaceholder": "Evidence (optional)", + "addNewBeneficiary": "Add New Beneficiary", + "loanDescription": "Loan Description", + "loanDescPlaceholder": "Describe the purpose of this milestone", + "ngoAddress": "NGO Address", + "noWalletConnected": "No wallet connected", + "amountUsdc": "Amount (USDC)", + "createNewMilestone": "Create New Milestone", + "escrowNotFound": "Escrow not found", + "loanApproved": "Loan {index} approved", + "fundsReleased": "Loan {index} funds released", + "loanAddedSuccess": "Loan added successfully", + "validation": { + "descriptionRequired": "Description is required", + "amountPositive": "Must be greater than 0" + } + }, + "roi": { + "title": "Returns", + "description": "Manage and monitor your active ROI programs in real time.", + "activeCampaigns": "Active ROI Campaigns", + "projectName": "Project Name", + "invested": "Invested", + "statusHeader": "Status", + "actions": "Actions", + "manageLoans": "Manage Loans", + "uploadFunds": "Upload Funds", + "updateRoi": "Update ROI", + "loadMore": "Load More", + "fundRoi": { + "title": "Fund ROI — {name}", + "description": "Transfer USDC to the vault so investors can claim their returns.", + "amountLabel": "Amount (USDC)", + "amountPlaceholder": "e.g. 1000", + "vaultLabel": "Vault", + "funding": "Funding Vault...", + "submit": "Fund Vault", + "success": "Vault funded successfully" + }, + "createRoi": { + "title": "Create ROI", + "description": "Set the multiplier for tracking the performance of your assets.", + "priceLabel": "Price (%)", + "howItWorks": "How it works", + "howItWorksDesc": "The multiplier adjusts real-time tracking based on the price percentage set to optimize the performance of your assets in the Vault.", + "readMore": "Read more →", + "createVault": "Create Vault", + "validation": { + "priceRequired": "Price is required", + "minPrice": "Must be greater than or equal to 0", + "maxPrice": "Cannot exceed 100%" + } + }, + "toggleVault": { + "enable": "Enable", + "disable": "Disable", + "enabled": "Vault enabled", + "disabled": "Vault disabled" + } + }, + "tokens": { + "tokenizeEscrow": "Tokenize Escrow", + "title": "Tokenize Escrow", + "escrowIdLabel": "Escrow ID", + "escrowIdPlaceholder": "Enter escrow contract ID", + "tokenNameLabel": "Token Name", + "tokenNamePlaceholder": "e.g., Trustless Work Token", + "tokenSymbolLabel": "Token Symbol/Ticker", + "tokenSymbolPlaceholder": "e.g., TRUST", + "tokenSymbolDesc": "Maximum 12 characters. Uppercase letters and numbers only.", + "tokenNote": "This token represents Escrow {escrowId} and can only be minted by its Token Sale contract.", + "deploying": "Deploying...", + "deployToken": "Deploy Token", + "deploySuccess": "Token Deployment Successful", + "tokenFactoryAddress": "Token Factory Address", + "tokenSaleAddress": "Token Sale Address", + "copied": "Copied!", + "copy": "Copy", + "validation": { + "escrowIdRequired": "Escrow ID is required", + "tokenNameRequired": "Token name is required", + "tokenSymbolRequired": "Token symbol is required", + "symbolMaxLength": "Symbol must be 12 characters or less", + "symbolPattern": "Symbol must contain only uppercase letters and numbers" + } + }, + "vaults": { + "createVault": "Create Vault", + "title": "Create Vault", + "priceLabel": "Price", + "priceDesc": "The percentage you enter becomes a multiplier on the base price. 6% → 1.06, 20% → 1.20. That multiplier is the final price per token.", + "pricePlaceholder": "Enter price", + "multiplier": "Multiplier", + "pricePerToken": "Price per token (base 1)", + "factoryAddressLabel": "Factory Address", + "factoryAddressDesc": "The factory address of the token you want to deploy the vault for.", + "factoryAddressPlaceholder": "Enter factory address", + "creating": "Creating...", + "enableVault": "Enable Vault", + "vaultContractAddressLabel": "Vault Contract Address", + "vaultContractAddressPlaceholder": "Enter vault contract ID (C...)", + "enabling": "Enabling...", + "enable": "Enable", + "deploySuccess": "Vault Deployment Successful", + "vaultContractAddress": "Vault Contract Address", + "copied": "Copied!", + "copy": "Copy", + "validation": { + "priceRequired": "Price is required", + "maxPrice": "Cannot exceed 100%", + "factoryRequired": "Factory address is required", + "vaultAddressRequired": "Vault contract address is required" + } + }, + "contractErrors": { + "title": "Contract Error", + "unknownCode": "Contract error code {code}", + "vault": { + "1": "Admin not found", + "2": "Only admin can change availability", + "3": "Exchange is currently disabled", + "4": "Beneficiary has no tokens to claim", + "5": "Vault does not have enough USDC" + }, + "tokenSale": { + "1": "Escrow contract not found", + "2": "Participation token not found", + "3": "Admin not found", + "4": "Only admin can set token", + "5": "Hard cap exceeded – the campaign is fully funded", + "6": "Investor cap exceeded – you have reached the maximum investment", + "7": "Amount must be positive" + } + } +} diff --git a/apps/backoffice-tokenization/messages/es.json b/apps/backoffice-tokenization/messages/es.json new file mode 100644 index 0000000..f190cff --- /dev/null +++ b/apps/backoffice-tokenization/messages/es.json @@ -0,0 +1,266 @@ +{ + "common": { + "loading": "Cargando...", + "error": "Error", + "cancel": "Cancelar", + "retry": "Reintentar", + "continue": "Continuar", + "next": "Siguiente →", + "previous": "Anterior", + "save": "Guardar", + "create": "Crear", + "update": "Actualizar", + "delete": "Eliminar", + "close": "Cerrar", + "viewTransaction": "Ver Transacción", + "unknownError": "Error desconocido", + "connectWallet": "Conecta tu wallet para crear una campaña" + }, + "metadata": { + "title": "Backoffice Tokenización", + "description": "Backoffice Tokenización" + }, + "nav": { + "campaigns": "Campañas", + "roi": "Retorno de Inversión" + }, + "home": { + "heroTitle": "Apoyo al emprendedor", + "heroDescription": "Acompañamos a emprendedores y microempresarios con soluciones que impulsan el crecimiento de sus negocios.", + "heroTagline": "Crecimiento con impacto.", + "openApp": "Abrir backoffice", + "aboutUs": "Sobre nosotros", + "cardFinancing": "Financiación", + "cardFinancingDesc": "Soluciones de crédito pensadas para impulsar el crecimiento de emprendedores y microempresas.", + "cardSupport": "Acompañamiento", + "cardSupportDesc": "Orientación y apoyo para fortalecer capacidades, tomar decisiones y avanzar con confianza.", + "cardStrengthening": "Fortalecimiento Empresarial", + "cardStrengtheningDesc": "Herramientas y oportunidades para consolidar negocios sostenibles y con impacto.", + "cardOpportunities": "Oportunidades", + "cardOpportunitiesDesc": "Conectamos a emprendedores y microempresas con soluciones que abren camino al crecimiento y la consolidación de sus negocios.", + "heroHighlight": "Oversight", + "bentoDeployTitle": "Desplegar Contratos Base", + "bentoDeployDesc": "Configura el Escrow, Token Sale y Participation Token que impulsan el flujo de financiamiento del proyecto.", + "bentoApprovalsTitle": "Aprobación de Hitos", + "bentoApprovalsDesc": "Revisa y aprueba los hitos del proyecto para autorizar la liberación de fondos on-chain.", + "bentoReleasesTitle": "Liberaciones de Escrow", + "bentoReleasesDesc": "Ejecuta liberaciones controladas de USDC a medida que cada hito se completa y valida.", + "bentoDisputeTitle": "Gestión de Disputas", + "bentoDisputeDesc": "Pausa liberaciones, revisa evidencia y resuelve problemas cuando las condiciones del proyecto no se cumplen.", + "bentoMonitorTitle": "Monitoreo de Proyectos y Contratos", + "bentoMonitorDesc": "Visualiza direcciones de contratos, balances de escrow, estado de venta y actividad del proyecto en tiempo real.", + "bentoSetup": "Configuración", + "bentoApproval": "Aprobación", + "bentoRelease": "Liberación", + "bentoDeployCards": "Despliega los contratos base para cada proyecto", + "bentoApproveCards": "Revisa y aprueba los hitos del proyecto", + "bentoReleaseCards": "Libera fondos de escrow a medida que se completan los hitos", + "bentoManageCards": "Gestiona despliegues, configura hitos y ejecuta liberaciones on-chain.", + "bentoOperations": "Operaciones" + }, + "campaigns": { + "title": "Campañas", + "description": "Consulta y gestiona las campañas de inversión activas.", + "newCampaign": "Nueva Campaña", + "loading": "Cargando campañas...", + "loadError": "No se pudieron cargar las campañas.", + "empty": "No hay campañas disponibles.", + "manageLoans": "Manejar Préstamos", + "poolSize": "Tamaño del Fondo", + "loansCompleted": "Préstamos Completados", + "loans": "Préstamos", + "noLoansAvailable": "No hay préstamos disponibles.", + "loan": "Préstamo {index}", + "filterAll": "Todas", + "filterFundraising": "Recaudando", + "filterActive": "Activa", + "filterClosed": "Cerrada", + "searchPlaceholder": "Buscar campañas...", + "status": { + "DRAFT": "Borrador", + "FUNDRAISING": "Recaudando", + "ACTIVE": "Activa", + "REPAYMENT": "En Pago", + "CLAIMABLE": "Reclamable", + "CLOSED": "Cerrada", + "PAUSED": "Pausada" + } + }, + "createCampaign": { + "title": "Nueva Campaña", + "description": "Completa los pasos para configurar tu programa de micro-préstamos.", + "step1": "Campaña Básica", + "step2": "Inicializar Escrow", + "step3": "Desplegar y Crear", + "nameLabel": "Nombre de la Campaña", + "namePlaceholder": "ej. Micro-Préstamos para Mujeres Emprendedoras", + "descriptionLabel": "Descripción", + "descriptionPlaceholder": "Describe brevemente el propósito de este fondo...", + "tokenNameLabel": "Nombre del Token", + "tokenNamePlaceholder": "ej. AgriGrowth Bond", + "poolSizeLabel": "Tamaño del Fondo (USD)", + "loanSizeLabel": "Tamaño del Préstamo (USD)", + "loanDurationLabel": "Duración del Préstamo (meses)", + "expectedReturnLabel": "Retorno Esperado (%)", + "initializingEscrow": "Inicializando escrow...", + "escrowInitError": "Error al inicializar escrow", + "escrowInitSuccess": "Escrow inicializado exitosamente", + "deployingTitle": "Desplegando contratos y creando campaña", + "deployPhase1": "Creando token de participación y tokenizando", + "deployPhase2": "Últimos pasos...", + "validation": { + "nameRequired": "El nombre es requerido", + "descriptionMin": "La descripción debe tener al menos 10 caracteres", + "mustBePositive": "Debe ser mayor a 0", + "tokenNameRequired": "El nombre del token es requerido" + } + }, + "loans": { + "title": "Manejar Préstamos", + "description": "Administra los hitos y préstamos de la campaña.", + "beneficiaries": "Beneficiarios", + "noMilestones": "No hay hitos registrados.", + "status": "Estado", + "disbursed": "Desembolsado", + "disburse": "Desembolsar", + "approve": "Aprobar", + "insufficientFunds": "Fondos insuficientes en el escrow", + "changeLoanStatus": "Cambiar estado del préstamo", + "statusLabel": "Estado", + "statusPlaceholder": "Ej: completed", + "evidenceLabel": "Evidencia", + "evidencePlaceholder": "Evidencia (opcional)", + "addNewBeneficiary": "Agregar Nuevo Beneficiario", + "loanDescription": "Descripción del Préstamo", + "loanDescPlaceholder": "Describe el propósito de este hito", + "ngoAddress": "Dirección ONG", + "noWalletConnected": "Sin wallet conectada", + "amountUsdc": "Monto (USDC)", + "createNewMilestone": "Crear Nuevo Hito", + "escrowNotFound": "Escrow no encontrado", + "loanApproved": "Préstamo {index} aprobado", + "fundsReleased": "Fondos del préstamo {index} liberados", + "loanAddedSuccess": "Préstamo agregado exitosamente", + "validation": { + "descriptionRequired": "La descripción es obligatoria", + "amountPositive": "Debe ser mayor a 0" + } + }, + "roi": { + "title": "Retornos", + "description": "Gestione y monitoree sus programas de ROI activos en tiempo real.", + "activeCampaigns": "Campaña de ROI Activas", + "projectName": "Nombre del Proyecto", + "invested": "Invertido", + "statusHeader": "Estado", + "actions": "Acciones", + "manageLoans": "Gestionar Préstamos", + "uploadFunds": "Subir Fondos", + "updateRoi": "Actualizar ROI", + "loadMore": "Cargar Más", + "fundRoi": { + "title": "Fund ROI — {name}", + "description": "Transfiere USDC al vault para que los inversores puedan reclamar sus retornos.", + "amountLabel": "Monto (USDC)", + "amountPlaceholder": "ej. 1000", + "vaultLabel": "Vault", + "funding": "Fondeando Vault...", + "submit": "Fondear Vault", + "success": "Vault fondeado exitosamente" + }, + "createRoi": { + "title": "Crear ROI", + "description": "Establezca el multiplicador para el seguimiento del rendimiento de sus activos.", + "priceLabel": "Precio (%)", + "howItWorks": "Cómo funciona", + "howItWorksDesc": "El multiplicador ajusta el seguimiento en tiempo real basándose en el porcentaje de precio establecido para optimizar el rendimiento de sus activos en el Vault.", + "readMore": "Leer más →", + "createVault": "Crear Vault", + "validation": { + "priceRequired": "El precio es obligatorio", + "minPrice": "Debe ser mayor o igual a 0", + "maxPrice": "No puede ser mayor a 100%" + } + }, + "toggleVault": { + "enable": "Habilitar", + "disable": "Deshabilitar", + "enabled": "Vault habilitado", + "disabled": "Vault deshabilitado" + } + }, + "tokens": { + "tokenizeEscrow": "Tokenizar Escrow", + "title": "Tokenizar Escrow", + "escrowIdLabel": "Escrow ID", + "escrowIdPlaceholder": "Ingresa el ID del contrato escrow", + "tokenNameLabel": "Nombre del Token", + "tokenNamePlaceholder": "ej. Trustless Work Token", + "tokenSymbolLabel": "Símbolo/Ticker del Token", + "tokenSymbolPlaceholder": "ej. TRUST", + "tokenSymbolDesc": "Máximo 12 caracteres. Solo letras mayúsculas y números.", + "tokenNote": "Este token representa el Escrow {escrowId} y solo puede ser acuñado por su contrato Token Sale.", + "deploying": "Desplegando...", + "deployToken": "Desplegar Token", + "deploySuccess": "Token desplegado exitosamente", + "tokenFactoryAddress": "Dirección del Token Factory", + "tokenSaleAddress": "Dirección del Token Sale", + "copied": "¡Copiado!", + "copy": "Copiar", + "validation": { + "escrowIdRequired": "El Escrow ID es requerido", + "tokenNameRequired": "El nombre del token es requerido", + "tokenSymbolRequired": "El símbolo del token es requerido", + "symbolMaxLength": "El símbolo debe tener 12 caracteres o menos", + "symbolPattern": "El símbolo solo debe contener letras mayúsculas y números" + } + }, + "vaults": { + "createVault": "Crear Vault", + "title": "Crear Vault", + "priceLabel": "Precio", + "priceDesc": "El porcentaje que ingreses se convierte en un multiplicador sobre el precio base. 6% → 1.06, 20% → 1.20. Ese multiplicador es el precio final por token.", + "pricePlaceholder": "Ingresa el precio", + "multiplier": "Multiplicador", + "pricePerToken": "Precio por token (base 1)", + "factoryAddressLabel": "Dirección de Fábrica", + "factoryAddressDesc": "La dirección de fábrica del token para el cual deseas desplegar el vault.", + "factoryAddressPlaceholder": "Ingresa la dirección de fábrica", + "creating": "Creando...", + "enableVault": "Habilitar Vault", + "vaultContractAddressLabel": "Dirección del Contrato Vault", + "vaultContractAddressPlaceholder": "Ingresa el ID del contrato vault (C...)", + "enabling": "Habilitando...", + "enable": "Habilitar", + "deploySuccess": "Vault desplegado exitosamente", + "vaultContractAddress": "Dirección del Contrato Vault", + "copied": "¡Copiado!", + "copy": "Copiar", + "validation": { + "priceRequired": "El precio es requerido", + "maxPrice": "No puede exceder 100%", + "factoryRequired": "La dirección de fábrica es requerida", + "vaultAddressRequired": "La dirección del contrato vault es requerida" + } + }, + "contractErrors": { + "title": "Error de Contrato", + "unknownCode": "Código de error de contrato {code}", + "vault": { + "1": "Administrador no encontrado", + "2": "Solo el administrador puede cambiar la disponibilidad", + "3": "El intercambio está actualmente deshabilitado", + "4": "El beneficiario no tiene tokens para reclamar", + "5": "El vault no tiene suficiente USDC" + }, + "tokenSale": { + "1": "Contrato escrow no encontrado", + "2": "Token de participación no encontrado", + "3": "Administrador no encontrado", + "4": "Solo el administrador puede establecer el token", + "5": "Tope máximo excedido — la campaña está completamente financiada", + "6": "Tope de inversor excedido — has alcanzado la inversión máxima", + "7": "El monto debe ser positivo" + } + } +} diff --git a/apps/backoffice-tokenization/next.config.ts b/apps/backoffice-tokenization/next.config.ts index 763a4eb..e3fcf88 100644 --- a/apps/backoffice-tokenization/next.config.ts +++ b/apps/backoffice-tokenization/next.config.ts @@ -1,4 +1,7 @@ import type { NextConfig } from "next"; +import createNextIntlPlugin from "next-intl/plugin"; + +const withNextIntl = createNextIntlPlugin("./src/i18n/request.ts"); const nextConfig: NextConfig = { reactCompiler: true, @@ -18,4 +21,4 @@ const nextConfig: NextConfig = { }, }; -export default nextConfig; +export default withNextIntl(nextConfig); diff --git a/apps/backoffice-tokenization/src/app/(dashboard)/campaigns/loans/[id]/page.tsx b/apps/backoffice-tokenization/src/app/[locale]/(dashboard)/campaigns/loans/[id]/page.tsx similarity index 100% rename from apps/backoffice-tokenization/src/app/(dashboard)/campaigns/loans/[id]/page.tsx rename to apps/backoffice-tokenization/src/app/[locale]/(dashboard)/campaigns/loans/[id]/page.tsx diff --git a/apps/backoffice-tokenization/src/app/(dashboard)/campaigns/new/page.tsx b/apps/backoffice-tokenization/src/app/[locale]/(dashboard)/campaigns/new/page.tsx similarity index 66% rename from apps/backoffice-tokenization/src/app/(dashboard)/campaigns/new/page.tsx rename to apps/backoffice-tokenization/src/app/[locale]/(dashboard)/campaigns/new/page.tsx index 218475a..fd5f50f 100644 --- a/apps/backoffice-tokenization/src/app/(dashboard)/campaigns/new/page.tsx +++ b/apps/backoffice-tokenization/src/app/[locale]/(dashboard)/campaigns/new/page.tsx @@ -1,12 +1,17 @@ +"use client"; + import { SectionTitle } from "@/components/shared/section-title"; import { CreateCampaignStepper } from "@/features/campaigns/components/create/create-campaign-stepper"; +import { useTranslations } from "next-intl"; export default function NewCampaignPage() { + const t = useTranslations("createCampaign"); + return (
diff --git a/apps/backoffice-tokenization/src/app/(dashboard)/campaigns/page.tsx b/apps/backoffice-tokenization/src/app/[locale]/(dashboard)/campaigns/page.tsx similarity index 73% rename from apps/backoffice-tokenization/src/app/(dashboard)/campaigns/page.tsx rename to apps/backoffice-tokenization/src/app/[locale]/(dashboard)/campaigns/page.tsx index 3f5ea67..9bc8973 100644 --- a/apps/backoffice-tokenization/src/app/(dashboard)/campaigns/page.tsx +++ b/apps/backoffice-tokenization/src/app/[locale]/(dashboard)/campaigns/page.tsx @@ -1,23 +1,28 @@ -import Link from "next/link"; +"use client"; + +import { Link } from "@/i18n/navigation"; import { SectionTitle } from "@/components/shared/section-title"; import { CampaignsView } from "@/features/campaigns/components/campaigns-view"; import { Button } from "@tokenization/ui/button"; import { Plus } from "lucide-react"; +import { useTranslations } from "next-intl"; export default function CampaignsPage() { + const t = useTranslations("campaigns"); + return (
diff --git a/apps/backoffice-tokenization/src/app/(dashboard)/layout.tsx b/apps/backoffice-tokenization/src/app/[locale]/(dashboard)/layout.tsx similarity index 100% rename from apps/backoffice-tokenization/src/app/(dashboard)/layout.tsx rename to apps/backoffice-tokenization/src/app/[locale]/(dashboard)/layout.tsx diff --git a/apps/backoffice-tokenization/src/app/(dashboard)/roi/page.tsx b/apps/backoffice-tokenization/src/app/[locale]/(dashboard)/roi/page.tsx similarity index 64% rename from apps/backoffice-tokenization/src/app/(dashboard)/roi/page.tsx rename to apps/backoffice-tokenization/src/app/[locale]/(dashboard)/roi/page.tsx index eaa771e..da12dcf 100644 --- a/apps/backoffice-tokenization/src/app/(dashboard)/roi/page.tsx +++ b/apps/backoffice-tokenization/src/app/[locale]/(dashboard)/roi/page.tsx @@ -1,12 +1,17 @@ +"use client"; + import { SectionTitle } from "@/components/shared/section-title"; import { RoiView } from "@/features/campaigns/components/roi/roi-view"; +import { useTranslations } from "next-intl"; export default function RoiPage() { + const t = useTranslations("roi"); + return (
diff --git a/apps/backoffice-tokenization/src/app/[locale]/layout.tsx b/apps/backoffice-tokenization/src/app/[locale]/layout.tsx new file mode 100644 index 0000000..ee01c67 --- /dev/null +++ b/apps/backoffice-tokenization/src/app/[locale]/layout.tsx @@ -0,0 +1,71 @@ +import type { Metadata } from "next"; +import "../globals.css"; +import { ReactQueryClientProvider } from "@tokenization/tw-blocks-shared/src/providers/ReactQueryClientProvider"; +import { TrustlessWorkProvider } from "@tokenization/tw-blocks-shared/src/providers/TrustlessWork"; +import { EscrowProvider } from "@tokenization/tw-blocks-shared/src/providers/EscrowProvider"; +import { EscrowDialogsProvider } from "@tokenization/tw-blocks-shared/src/providers/EscrowDialogsProvider"; +import { EscrowAmountProvider } from "@tokenization/tw-blocks-shared/src/providers/EscrowAmountProvider"; +import { Toaster } from "@tokenization/ui/sonner"; +import { WalletProvider } from "@tokenization/tw-blocks-shared/src/wallet-kit/WalletProvider"; +import type { ReactNode } from "react"; +import { Inter } from "next/font/google"; +import { cn } from "@/lib/utils"; +import { NextIntlClientProvider } from "next-intl"; +import { SharedTranslationProvider } from "@tokenization/tw-blocks-shared/src/i18n/TranslationProvider"; +import sharedEn from "@tokenization/tw-blocks-shared/src/i18n/messages/en.json"; +import sharedEs from "@tokenization/tw-blocks-shared/src/i18n/messages/es.json"; +import appEn from "../../../messages/en.json"; +import appEs from "../../../messages/es.json"; + +const sharedMessages: Record> = { en: sharedEn, es: sharedEs }; +const appMessages: Record> = { en: appEn, es: appEs }; + +const inter = Inter({ + subsets: ["latin", "latin-ext"], + display: "swap", + variable: "--font-inter", +}); + +export const metadata: Metadata = { + title: "Backoffice Tokenization", + description: "Backoffice Tokenization", +}; + +export default async function LocaleLayout({ + children, + params, +}: { + children: ReactNode; + params: Promise<{ locale: string }>; +}) { + const { locale } = await params; + const messages = appMessages[locale] ?? appMessages.es; + + return ( + + + + + + + + + + + + + + {children} + + + + + + + + + + + + ); +} diff --git a/apps/backoffice-tokenization/src/app/page.tsx b/apps/backoffice-tokenization/src/app/[locale]/page.tsx similarity index 100% rename from apps/backoffice-tokenization/src/app/page.tsx rename to apps/backoffice-tokenization/src/app/[locale]/page.tsx diff --git a/apps/backoffice-tokenization/src/app/layout.tsx b/apps/backoffice-tokenization/src/app/layout.tsx index 59f9736..f0ad499 100644 --- a/apps/backoffice-tokenization/src/app/layout.tsx +++ b/apps/backoffice-tokenization/src/app/layout.tsx @@ -1,50 +1,5 @@ -import type { Metadata } from "next"; -import "./globals.css"; -import { ReactQueryClientProvider } from "@tokenization/tw-blocks-shared/src/providers/ReactQueryClientProvider"; -import { TrustlessWorkProvider } from "@tokenization/tw-blocks-shared/src/providers/TrustlessWork"; -import { EscrowProvider } from "@tokenization/tw-blocks-shared/src/providers/EscrowProvider"; -import { EscrowDialogsProvider } from "@tokenization/tw-blocks-shared/src/providers/EscrowDialogsProvider"; -import { EscrowAmountProvider } from "@tokenization/tw-blocks-shared/src/providers/EscrowAmountProvider"; -import { Toaster } from "@tokenization/ui/sonner"; -import { WalletProvider } from "@tokenization/tw-blocks-shared/src/wallet-kit/WalletProvider"; import type { ReactNode } from "react"; -import { Inter } from "next/font/google"; -import { cn } from "@/lib/utils"; -const inter = Inter({ - subsets: ["latin"], - display: "swap", - variable: "--font-inter", -}); - -export const metadata: Metadata = { - title: "Backoffice Tokenization", - description: "Backoffice Tokenization", -}; - -export default function RootLayout({ - children, -}: Readonly<{ - children: ReactNode; -}>) { - return ( - - - - - - - - - {children} - - - - - - - - - - ); +export default function RootLayout({ children }: { children: ReactNode }) { + return children; } diff --git a/apps/backoffice-tokenization/src/components/layout/app-header.tsx b/apps/backoffice-tokenization/src/components/layout/app-header.tsx index e744662..b7ae838 100644 --- a/apps/backoffice-tokenization/src/components/layout/app-header.tsx +++ b/apps/backoffice-tokenization/src/components/layout/app-header.tsx @@ -1,11 +1,15 @@ "use client"; import { SidebarTrigger } from "@tokenization/ui/sidebar"; +import { LanguageSwitcher } from "@/components/shared/language-switcher"; export function AppHeader() { return ( -
- +
+ +
+ +
); } diff --git a/apps/backoffice-tokenization/src/components/layout/app-sidebar.tsx b/apps/backoffice-tokenization/src/components/layout/app-sidebar.tsx index b63732d..2bd22ac 100644 --- a/apps/backoffice-tokenization/src/components/layout/app-sidebar.tsx +++ b/apps/backoffice-tokenization/src/components/layout/app-sidebar.tsx @@ -9,6 +9,7 @@ import { type AppSidebarLogoConfig, } from "@tokenization/ui/app-sidebar"; import { SidebarWalletButton } from "@tokenization/ui/sidebar-wallet-button"; +import { useTranslations } from "next-intl"; const logo: AppSidebarLogoConfig = { element: ( @@ -24,19 +25,6 @@ const logo: AppSidebarLogoConfig = { href: "/", }; -const navItems: AppSidebarNavItem[] = [ - { - label: "Campañas", - href: "/campaigns", - icon: Wallet, - }, - { - label: "Retorno de Inversión", - href: "/roi", - icon: TrendingUp, - }, -]; - const footerItems: AppSidebarFooterItem[] = [ { label: "Documentation", @@ -47,6 +35,21 @@ const footerItems: AppSidebarFooterItem[] = [ ]; export function AppSidebar() { + const t = useTranslations("nav"); + + const navItems: AppSidebarNavItem[] = [ + { + label: t("campaigns"), + href: "/campaigns", + icon: Wallet, + }, + { + label: t("roi"), + href: "/roi", + icon: TrendingUp, + }, + ]; + return ( - Manejar Préstamos + {t("manageLoans")} ) : undefined @@ -84,16 +86,16 @@ export function CampaignCard({ campaign }: CampaignCardProps) { footer={
- Pool Size: {formatCurrency(Number(escrowData?.balance ?? 0), "USDC")} / {formatCurrency(Number(campaign.poolSize), "USDC")} + {t("poolSize")}: USDC {formatCurrency(escrowData?.balance ?? 0)} / USDC {formatCurrency(campaign.poolSize)}
} - stat={{ label: "Loans", value: totalLoans }} + stat={{ label: t("loans"), value: totalLoans }} > {visibleMilestones.length > 0 ? ( <>

- Loans + {t("loans")}

    {visibleMilestones.map((m, i) => ( @@ -105,14 +107,14 @@ export function CampaignCard({ campaign }: CampaignCardProps) { ) : ( )} - {m.description || `Loan ${i + 1}`} + {m.description || t("loan", { index: i + 1 })} {m.amount} USDC ))}
) : ( -

No loans available.

+

{t("noLoansAvailable")}

)} ); diff --git a/apps/backoffice-tokenization/src/components/shared/language-switcher.tsx b/apps/backoffice-tokenization/src/components/shared/language-switcher.tsx new file mode 100644 index 0000000..9264917 --- /dev/null +++ b/apps/backoffice-tokenization/src/components/shared/language-switcher.tsx @@ -0,0 +1,40 @@ +"use client"; + +import { useLocale } from "next-intl"; +import { useRouter, usePathname } from "@/i18n/navigation"; +import { cn } from "@/lib/utils"; + +const locales = [ + { code: "es" as const, label: "ES" }, + { code: "en" as const, label: "EN" }, +]; + +export function LanguageSwitcher() { + const locale = useLocale(); + const router = useRouter(); + const pathname = usePathname(); + + function switchLocale(next: "es" | "en") { + if (next === locale) return; + router.replace(pathname, { locale: next }); + } + + return ( +
+ {locales.map(({ code, label }) => ( + + ))} +
+ ); +} diff --git a/apps/backoffice-tokenization/src/features/campaigns/components/campaign-filter.tsx b/apps/backoffice-tokenization/src/features/campaigns/components/campaign-filter.tsx index fc5bead..75afd97 100644 --- a/apps/backoffice-tokenization/src/features/campaigns/components/campaign-filter.tsx +++ b/apps/backoffice-tokenization/src/features/campaigns/components/campaign-filter.tsx @@ -1,20 +1,23 @@ "use client"; +import { useTranslations } from "next-intl"; import type { CampaignStatus } from "@/features/campaigns/types/campaign.types"; -const STATUS_OPTIONS: { value: CampaignStatus | "all"; label: string }[] = [ - { value: "all", label: "Todas" }, - { value: "FUNDRAISING", label: "Recaudando" }, - { value: "ACTIVE", label: "Activa" }, - { value: "CLOSED", label: "Cerrada" }, -]; - interface CampaignFilterProps { value: CampaignStatus | "all"; onChange: (value: CampaignStatus | "all") => void; } export function CampaignFilter({ value, onChange }: CampaignFilterProps) { + const t = useTranslations("campaigns"); + + const STATUS_OPTIONS: { value: CampaignStatus | "all"; label: string }[] = [ + { value: "all", label: t("filterAll") }, + { value: "FUNDRAISING", label: t("filterFundraising") }, + { value: "ACTIVE", label: t("filterActive") }, + { value: "CLOSED", label: t("filterClosed") }, + ]; + return (
{STATUS_OPTIONS.map((option) => ( diff --git a/apps/backoffice-tokenization/src/features/campaigns/components/campaign-list.tsx b/apps/backoffice-tokenization/src/features/campaigns/components/campaign-list.tsx index f7ff460..6368519 100644 --- a/apps/backoffice-tokenization/src/features/campaigns/components/campaign-list.tsx +++ b/apps/backoffice-tokenization/src/features/campaigns/components/campaign-list.tsx @@ -1,3 +1,6 @@ +"use client"; + +import { useTranslations } from "next-intl"; import type { Campaign } from "@/features/campaigns/types/campaign.types"; import { CampaignCard } from "@/components/shared/campaign-card"; @@ -6,10 +9,12 @@ interface CampaignListProps { } export function CampaignList({ campaigns }: CampaignListProps) { + const t = useTranslations("campaigns"); + if (campaigns.length === 0) { return (
-

No hay campañas disponibles.

+

{t("empty")}

); } diff --git a/apps/backoffice-tokenization/src/features/campaigns/components/campaign-search.tsx b/apps/backoffice-tokenization/src/features/campaigns/components/campaign-search.tsx index b52ceb2..667ba1d 100644 --- a/apps/backoffice-tokenization/src/features/campaigns/components/campaign-search.tsx +++ b/apps/backoffice-tokenization/src/features/campaigns/components/campaign-search.tsx @@ -1,5 +1,6 @@ "use client"; +import { useTranslations } from "next-intl"; import { Search } from "lucide-react"; interface CampaignSearchProps { @@ -8,12 +9,14 @@ interface CampaignSearchProps { } export function CampaignSearch({ value, onChange }: CampaignSearchProps) { + const t = useTranslations("campaigns"); + return (
onChange(e.target.value)} className="h-10 w-full rounded-xl border border-border bg-card pl-9 pr-4 text-sm text-foreground placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-ring" diff --git a/apps/backoffice-tokenization/src/features/campaigns/components/campaigns-view.tsx b/apps/backoffice-tokenization/src/features/campaigns/components/campaigns-view.tsx index ce77378..e9455e4 100644 --- a/apps/backoffice-tokenization/src/features/campaigns/components/campaigns-view.tsx +++ b/apps/backoffice-tokenization/src/features/campaigns/components/campaigns-view.tsx @@ -1,12 +1,14 @@ "use client"; import { useState, useMemo } from "react"; +import { useTranslations } from "next-intl"; import { CampaignToolbar } from "./campaign-toolbar"; import { CampaignList } from "./campaign-list"; import { useCampaigns } from "@/features/campaigns/hooks/use-campaigns"; import type { CampaignStatus } from "@/features/campaigns/types/campaign.types"; export function CampaignsView() { + const t = useTranslations("campaigns"); const [search, setSearch] = useState(""); const [filter, setFilter] = useState("all"); const { data: campaigns = [], isLoading, isError } = useCampaigns(); @@ -25,7 +27,7 @@ export function CampaignsView() { if (isLoading) { return (
- Cargando campañas... + {t("loading")}
); } @@ -33,7 +35,7 @@ export function CampaignsView() { if (isError) { return (
- No se pudieron cargar las campañas. + {t("loadError")}
); } diff --git a/apps/backoffice-tokenization/src/features/campaigns/components/create/create-campaign-stepper.tsx b/apps/backoffice-tokenization/src/features/campaigns/components/create/create-campaign-stepper.tsx index 7c43789..4245b29 100644 --- a/apps/backoffice-tokenization/src/features/campaigns/components/create/create-campaign-stepper.tsx +++ b/apps/backoffice-tokenization/src/features/campaigns/components/create/create-campaign-stepper.tsx @@ -1,6 +1,7 @@ "use client"; -import { useRouter } from "next/navigation"; +import { useTranslations } from "next-intl"; +import { useRouter } from "@/i18n/navigation"; import { cn } from "@tokenization/shared/lib/utils"; import { Button } from "@tokenization/ui/button"; import { useCreateCampaign } from "@/features/campaigns/hooks/use-create-campaign"; @@ -8,13 +9,9 @@ import { StepCampaignBasics } from "./step-campaign-basics"; import { StepEscrowConfig } from "./step-escrow-config"; import { StepCreateToken } from "./step-create-token"; -const STEPS = [ - { number: 1, label: "Campaña Básica" }, - { number: 2, label: "Inicializar Escrow" }, - { number: 3, label: "Desplegar y Crear" }, -]; - export function CreateCampaignStepper() { + const t = useTranslations("createCampaign"); + const tCommon = useTranslations("common"); const router = useRouter(); const { form, @@ -36,12 +33,18 @@ export function CreateCampaignStepper() { retryDeploy, } = useCreateCampaign(); + const STEPS = [ + { number: 1, label: t("step1") }, + { number: 2, label: t("step2") }, + { number: 3, label: t("step3") }, + ]; + if (!walletAddress) { return (

- Conecta tu wallet para crear una campaña + {tCommon("connectWallet")}

@@ -122,10 +125,10 @@ export function CreateCampaignStepper() { variant="outline" onClick={() => router.push("/campaigns")} > - Cancelar + {tCommon("cancel")}
)} diff --git a/apps/backoffice-tokenization/src/features/campaigns/components/create/step-campaign-basics.tsx b/apps/backoffice-tokenization/src/features/campaigns/components/create/step-campaign-basics.tsx index 0621760..93e258c 100644 --- a/apps/backoffice-tokenization/src/features/campaigns/components/create/step-campaign-basics.tsx +++ b/apps/backoffice-tokenization/src/features/campaigns/components/create/step-campaign-basics.tsx @@ -1,5 +1,6 @@ "use client"; +import { useTranslations } from "next-intl"; import type { UseFormReturn } from "react-hook-form"; import { Form, @@ -18,6 +19,8 @@ interface Props { } export function StepCampaignBasics({ form }: Props) { + const t = useTranslations("createCampaign"); + return (
@@ -27,12 +30,12 @@ export function StepCampaignBasics({ form }: Props) { render={({ field }) => ( - Nombre de la Campaña + {t("nameLabel")} * @@ -47,12 +50,12 @@ export function StepCampaignBasics({ form }: Props) { render={({ field }) => ( - Descripción + {t("descriptionLabel")} *