Skip to content

I18n translation#47

Open
Lantum-Brendan wants to merge 13 commits intoflatrun:mainfrom
Lantum-Brendan:i18n-translation
Open

I18n translation#47
Lantum-Brendan wants to merge 13 commits intoflatrun:mainfrom
Lantum-Brendan:i18n-translation

Conversation

@Lantum-Brendan
Copy link
Copy Markdown
Collaborator

@Lantum-Brendan Lantum-Brendan commented Mar 2, 2026

This PR handles a slight setup documentation modification and handles translation to locale languages.

Screencast.From.2026-03-26.14-43-44.mp4

@Lantum-Brendan Lantum-Brendan marked this pull request as ready for review March 4, 2026 13:46
@sourceant
Copy link
Copy Markdown

sourceant bot commented Mar 4, 2026

Code Review Summary

This PR successfully internationalizes the application using vue-i18n. It introduces support for English, French, German, Spanish, Portuguese, and Italian. It also cleans up the README and ESLint configuration.

🚀 Key Improvements

  • Extracted all UI strings into locale JSON files for better maintainability.
  • Introduced a LanguageSelector component with flag support.
  • Cleaned up hardcoded configuration objects in PermissionPicker and SecurityHealthCard.

💡 Minor Suggestions

  • Centralize status and token normalization logic to avoid duplication across views.
  • Fix case-sensitivity issues in existing unit tests revealed by the translation changes.

🚨 Critical Issues

  • Address failing unit tests in BackupsTab and ContainerTerminal where expectations don't match the new i18n-driven output.

Copy link
Copy Markdown

@sourceant sourceant bot left a comment

Choose a reason for hiding this comment

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

Review complete. See the overview comment for a summary.

error.value = "";
const normalizedApiKey = apiKey.trim();

if (!normalizedApiKey) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The error message 'API key is required' is still hardcoded while the rest of the app has been internationalized. This should use the $t function or be handled in the view layer with a translation key.

Suggested change
if (!normalizedApiKey) {
if (!normalizedApiKey) {
error.value = "auth.errors.apiKeyRequired";
loading.value = false;
return false;
}

@@ -951,12 +974,18 @@ const deleteCredential = async () => {
try {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

When calling templatesApi.refresh(), the response potentially contains a count or message that is being ignored in favor of a static notification. It's better to verify if the count needs to be passed to the translation string.

Suggested change
try {
const response = await templatesApi.refresh();
notifications.success(
t("settings.general.quickActions.templatesRefreshedTitle"),
t("settings.general.quickActions.templatesRefreshedDesc", { n: response.data.count || 0 })
);

Copy link
Copy Markdown
Contributor

@nfebe nfebe left a comment

Choose a reason for hiding this comment

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

The refactoring in CI is not related. Please update the PR (resolve conflicts and update the video)

This looks quite good! Thank you

Copy link
Copy Markdown

@sourceant sourceant bot left a comment

Choose a reason for hiding this comment

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

Review complete. See the overview comment for a summary.

return date.toLocaleString();
};

const formatBackupStatus = (status: string): string => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The helper function formatBackupStatus attempts to translate status strings but falls back to a manual string replacement. It's safer to ensure all possible statuses are defined in the locale files to avoid inconsistent UI labels.

Suggested change
const formatBackupStatus = (status: string): string => {
const formatBackupStatus = (status: string): string => {
const key = `deployment.backups.status.${status}`;
return te(key) ? t(key) : t('deployment.backups.status.unknown');
};

Comment on lines +1 to +9
<template>
<div class="language-dropdown" @keydown.esc="closeMenu">
<button class="language-trigger" type="button" :aria-expanded="isOpen" aria-haspopup="menu" @click="toggleMenu">
<img class="language-flag" :src="currentLanguage.flagPath" :alt="currentLanguage.code" />
<span class="language-label">{{ currentLanguage.label }}</span>
<i class="pi pi-chevron-down" />
</button>
<div v-if="isOpen" class="language-menu" role="menu">
<button
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The new LanguageSelector component lacks accessibility attributes for the list items. Using role="menu" on the container is good, but the trigger and items should have appropriate ARIA labels and keyboard navigation handling beyond just 'Esc'.

Suggested change
<template>
<div class="language-dropdown" @keydown.esc="closeMenu">
<button class="language-trigger" type="button" :aria-expanded="isOpen" aria-haspopup="menu" @click="toggleMenu">
<img class="language-flag" :src="currentLanguage.flagPath" :alt="currentLanguage.code" />
<span class="language-label">{{ currentLanguage.label }}</span>
<i class="pi pi-chevron-down" />
</button>
<div v-if="isOpen" class="language-menu" role="menu">
<button
<button
v-for="lang in languages"
:key="lang.code"
class="language-item"
type="button"
role="menuitem"
:aria-label="'Switch to ' + lang.label"
@click="selectLanguage(lang)"
>

Copy link
Copy Markdown

@sourceant sourceant bot left a comment

Choose a reason for hiding this comment

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

Review complete. See the overview comment for a summary.

>?
</p>
<p v-if="fileToDelete?.is_dir" class="warning-text">This will delete all contents inside the folder.</p>
<!-- eslint-disable vue/no-v-html -->
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Using v-html for simple string interpolation is a security risk (XSS) and unnecessary for this use case. Use standard interpolation or the <i18n-t> component if specific formatting is needed.

Suggested change
<!-- eslint-disable vue/no-v-html -->
<p>{{ $t('deployment.files.modal.delete.confirm', { name: fileToDelete?.name }) }}</p>

Copy link
Copy Markdown

@sourceant sourceant bot left a comment

Choose a reason for hiding this comment

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

Review complete. See the overview comment for a summary.

const formatBackupStatus = (status: string): string => {
const key = `deployment.backups.status.${status}`;
if (te(key)) return t(key);
return status.replace(/_/g, " ");
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

When a translation is missing, the fallback logic simply replaces underscores. This is a good safety measure, but it's often better to title-case the result for a cleaner UI.

Suggested change
return status.replace(/_/g, " ");
return status.replace(/_/g, " ").replace(/\b\w/g, l => l.toUpperCase());

@@ -0,0 +1,148 @@
<template>
<div class="language-dropdown" @keydown.esc="closeMenu">
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The container uses keydown.esc but lacks a blur/outside click handler. If a user clicks away from the menu, it will stay open. Adding a simple directive or global listener would improve UX.

Suggested change
<div class="language-dropdown" @keydown.esc="closeMenu">
<div class="language-dropdown" v-click-outside="closeMenu" @keydown.esc="closeMenu">

return t("security.events.dynamicType", { type: humanized });
};

const normalizeEventToken = (value?: string) =>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The manual string manipulation for token normalization is repeated. Consider moving this to a central utility file since it is used in multiple components (Backups, Security, Services).

Suggested change
const normalizeEventToken = (value?: string) =>
const normalizeEventToken = (value?: string) =>
normalizeToken(value);

@Lantum-Brendan Lantum-Brendan requested a review from nfebe March 26, 2026 13:49
@Lantum-Brendan
Copy link
Copy Markdown
Collaborator Author

The refactoring in CI is not related. Please update the PR (resolve conflicts and update the video)

This looks quite good! Thank you

Changes to CI file removed, conflicts resolved and video updated.

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