Currency Converter

Configuración

Configura un proyecto de tutorial

Introducción

Esta es una guía más detallada sobre cómo configurar un proyecto muy simple de Next.js usando gt-next. Te llevaremos de principio a fin, desde la configuración del proyecto hasta su traducción. A lo largo de este tutorial, avanzaremos de conceptos simples a otros más avanzados. Este tutorial asume que tienes un conocimiento general de TypeScript, Next.js y React.

Aquí tienes una lista de los temas que cubriremos en este tutorial:

  • Configuración de un nuevo proyecto de Next.js
  • Uso del componente <T> para traducir una app
  • Uso de componentes de variables como <Var>, <Currency>, <DateTime> y <Num> para traducir contenido dinámico
  • Uso de componentes condicionales como <Plural> y <Branch> para traducir contenido condicionado
  • Uso de enrutamiento i18n en tu app

Nuestra app será simple y nos permitirá comprobar el tipo de cambio entre divisas. Solo usaremos estilos en línea y únicamente la biblioteca gt-next para mantener las cosas lo más sencillas posible. Este ejemplo se basa en el tutorial Currency Converter de GeeksforGeeks.

Configura tu app de Next

Primero, creemos una nueva app de Next.js. Puedes hacerlo ejecutando el siguiente comando:

npx create-next-app@latest

Esto te llevará al asistente de configuración, donde podrás elegir el name de tu aplicación y la plantilla que quieres usar. Para este tutorial, usa el name currencies y selecciona «Sí» cuando se te pregunte si quieres usar TypeScript.

Ve al directorio del proyecto y ¡pongamos en marcha la app!

cd currencies
npm install
npm run dev

Esto iniciará la aplicación en http://localhost:3000.

¡Agreguemos contenido!

Ahora que tenemos nuestra app configurada, sobrescribamos el contenido de nuestra app para mostrar un conversor de divisas sencillo. Solo copia y pega el siguiente código en el archivo src/app/page.tsx y en el archivo src/app/layout.tsx.

Por ahora, no te preocupes demasiado por cómo funciona. Todo lo que hace este código es simular una solicitud a una API de tipo de cambio y mostrar la tasa de cambio entre dos divisas.

src/app/page.tsx
"use client";

import { useEffect, useState, useCallback } from "react";

// Un mapa entre dos divisas y su tipo de cambio (de -> a)
type ExchTable = Record<string, Record<string, number>>;

const EXCH_RATES: ExchTable = {
  usd: { usd: 1, inr: 73.5, eur: 0.85, jpy: 105.45, gbp: 0.72 },
  inr: { usd: 0.014, inr: 1, eur: 0.012, jpy: 1.46, gbp: 0.01 },
  eur: { usd: 1.18, inr: 85.5, eur: 1, jpy: 123.5, gbp: 0.85 },
  jpy: { usd: 0.0095, inr: 0.68, eur: 0.0081, jpy: 1, gbp: 0.0068 },
  gbp: { usd: 1.39, inr: 99.5, eur: 1.17, jpy: 146.5, gbp: 1 },
};

// algunos estilos para el botón
const buttonStyle = {
  backgroundColor: "#007bff",
  color: "white",
  border: "none",
  padding: "10px 20px",
  cursor: "pointer",
  borderRadius: "5px",
  fontSize: "16px",
  margin: "20px",
};

/**
 * Esta función simula una solicitud a una API para obtener el tipo de cambio actual.
 * Espera 1 segundo antes de devolver el tipo de cambio.
 * @returns el tipo de cambio entre dos divisas
 */
async function fetchExchangeRate(from: string, to: string): Promise<number> {
  await new Promise((resolve) => setTimeout(resolve, 1000));
  return EXCH_RATES[from][to];
}

function Page() {
  // tipos de cambio
  const [info, setInfo] = useState<ExchTable>({});
  const options = ["usd", "inr", "eur", "jpy", "gbp"];

  // divisas
  const [from, setFrom] = useState("usd");
  const [to, setTo] = useState("inr");

  // valores
  const [input, setInput] = useState(0);
  const [output, setOutput] = useState(0);

  // Función para convertir la divisa
  const convert = useCallback(() => {
    if (info?.[from]?.[to]) {
      const rate = info[from][to];
      setOutput(input * rate);
    } else {
      setOutput(0);
    }
  }, [info, input, to, from]);

  // Llamar a la API cuando cambie la divisa de origen o destino
  useEffect(() => {
    // Si el tipo de cambio ya está disponible, convierte
    if (info?.[from]?.[to]) {
      convert();
      return;
    }
    // Obtener el tipo de cambio
    (async () => {
      const response = await fetchExchangeRate(from, to);
      // Insertar la nueva respuesta sin sobrescribir la información anterior
      setInfo((prevInfo) => ({
        ...prevInfo,
        [from]: {
          ...(prevInfo?.[from] || undefined),
          [to]: response,
        },
      }));
    })();
  }, [from, to, convert, info]);

  // Llamar a convertir cada vez que el usuario cambie la divisa
  useEffect(() => {
    convert();
  }, [info, convert]);

  // Función para intercambiar las dos divisas
  function flip() {
    const temp = from;
    setFrom(to);
    setTo(temp);
  }

  return (
    <div style={{ margin: "0 auto", width: "50%", textAlign: "center" }}>
      <div style={{ margin: "20px 0", paddingBottom: "20px" }}>
        <h1 style={{ fontSize: "2.5em", fontWeight: "bold" }}>
          Convertidor de divisas
        </h1>
      </div>
      <div style={{ flex: 2, textAlign: "center", margin: "20px 0" }}>
        <h3>Importe</h3>
        <input
          type="text"
          placeholder="Introduce el importe"
          style={{ textAlign: "center" }}
          onChange={(e) => {
            setInput(Number(e.target.value));
          }}
        />
      </div>
      <div
        style={{ display: "flex", justifyContent: "center", margin: "20px 0" }}
      >
        <div style={{ flex: 1, textAlign: "center" }}>
          <label htmlFor="from">
            <h3>De</h3>
          </label>
          <select
            name="from"
            id="from"
            value={from}
            onChange={(e) => setFrom(e.target.value)}
          >
            {options.map((option) => (
              <option key={option} value={option}>
                {option.toUpperCase()}
              </option>
            ))}
          </select>
        </div>
        <div style={{ flex: 1, textAlign: "center" }}>
          <button
            onClick={() => {
              flip();
            }}
            style={buttonStyle}
          >
            Cambiar
          </button>
        </div>
        <div style={{ flex: 1, textAlign: "center" }}>
          <label htmlFor="to">
            <h3>A</h3>
          </label>
          <select
            name="to"
            id="to"
            value={to}
            onChange={(e) => setTo(e.target.value)}
          >
            {options.map((option) => (
              <option key={option} value={option}>
                {option.toUpperCase()}
              </option>
            ))}
          </select>
        </div>
      </div>
      <div style={{ margin: "0 auto", width: "50%", textAlign: "center" }}>
        <button
          onClick={() => {
            convert();
          }}
          style={buttonStyle}
        >
          Convertir
        </button>
        <h2>Importe convertido:</h2>
        <p>{input + " " + from + " = " + output.toFixed(2) + " " + to}</p>
      </div>
    </div>
  );
}

export default Page;
src/app/layout.tsx
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";

const geistSans = Geist({
  variable: "--font-geist-sans",
  subsets: ["latin"],
});

const geistMono = Geist_Mono({
  variable: "--font-geist-mono",
  subsets: ["latin"],
});

export const metadata: Metadata = {
  title: "Convertidor de moneda",
  description: "Un convertidor de moneda sencillo",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body
        className={`${geistSans.variable} ${geistMono.variable} antialiased`}
      >
        {children}
      </body>
    </html>
  );
}

Conclusión

¿Qué te parece esta guía?