
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