Responses em Diferentes idiomas de forma simples em NodeJs

Estou recriando uma API que originalmente os retornos de respostas estavam em inglês, tratar isso no front-end ficou desgastante; Levando em consideração que a responsabilidade de enviar informações corretas é do backend, encontrei um meio de traduzir os valores, de forma escalável, necessitando apenas que o body envie a linguagem, seja do browser ou definido pelo usuário.

Em uma pasta locales no projeto, foi adicionado subpastas de acordo com a origem da controller e o idioma, por exemplo:

src/locales/
–login/
—-|en
——|index.js
—-|pt
——|index.js

Nos arquivos index, temos um module.export que contem um objeto, validation, com cada index contendo a tradução do idioma referente ao nome da pasta.

module.exports = {
  validation: {
    emailInvalid: "Invalid email.",
    emailRequired: "Email is required.",
    passwordRequired: "Password is required.",
    notFound: "User not found",
    passwordInvalid: "Invalid email or password",
    loginSuccessfully: "Successfully"
  },
};

O Carregamento dinâmico fica por conta do arquivo em utils, localeData, que recebe a requisição e determina o idioma a partir do header Accept-Language, tratando para que caso não seja identificado, o padrão seja inglês.

const { loadLocale } = require("../validations/localeLoader");
const localeData = async (req, originData) => {
  const lang = (await req.headers["accept-language"]) || "en";
  const messages = await loadLocale(originData, lang).validation;
  return { messages };
};
module.exports = localeData;

Em loadLocale, que fica na pasta validations, temos o código que carrega um arquivo de tradução com base no idioma e no caminho do arquivo em que a tradução se encontra, fazendo as tratativas para caso o idioma enviado pelo body não exista no sistema.

const path = require("path");
const fs = require("fs");

const loadLocale = (area, lang) => {
  try {
    const localePath = path.resolve(
      __dirname,
      `../locales/${area}/${lang}/index.js`
    );

    if (fs.existsSync(localePath)) {
      return require(localePath);
    } else {
      return require(path.resolve(__dirname, `../locales/${area}/en/index.js`));
    }
  } catch (error) {
    throw new Error(
      `Locale file not found for area: ${area}, language: ${lang}`
    );
  }
};

module.exports = { loadLocale };

O “voilà” do processo, a implementação; Usando o método de login, e após importar o arquivo localeData, as mensagens de respostas são recebidas dinamicamente de acordo com a linguagem definida. É necessário criar uma constante que recebe um objeto message e aguarda uma resposta de localeData(req, “login”);

const { messages } = await localeData(req, "login");

As respostas serão enviadas de acordo com o valor do objeto validation, por exemplo:

if (!user) {
   return res.status(404).json({ error: messages.notFound });
}

Este padrão permite que a aplicação suporte múltiplos idiomas de maneira escalável, apenas adicionando novos arquivos de idiomas na pasta locales, por existir uma centralização da lógica de localização em um utilitário, a manutenção se torna fácil.

Exemplo do código completo:

const login = async (req, res) => {
  const { messages } = await localeData(req, "login");

  try {
    const { email, password } = req.body;

    const user = await getUserByEmail(email);

    if (!user) {
      return res.status(404).json({ error: messages.notFound });
    }

    const isPasswordValid = await bcrypt.compare(password, user.password);
    if (!isPasswordValid) {
      return res.status(401).json({ error: messages.passwordInvalid });
    }

    const token = generateToken({ id: user.id });
    
    await User.update(
      {
        token,
      },
      {
        where: { email },
      }
    );

    return res.status(200).json({
      message: messages.loginSuccessfully,
      user: formatUserResponse(user),
      token,
    });
  } catch (error) {
    if (error.name === "ValidationError") {
      return res.status(400).json({ errors: error.errors });
    }
    return res.status(500).json({ error: "Internal server error." });
  }
};

Seja o primeiro a comentar

Faça um comentário

Seu e-mail não será divulgado.


*