diff --git a/frontend/__tests__/components/features/home/repo-connector.test.tsx b/frontend/__tests__/components/features/home/repo-connector.test.tsx
index afbf218c79..3f7c2248e4 100644
--- a/frontend/__tests__/components/features/home/repo-connector.test.tsx
+++ b/frontend/__tests__/components/features/home/repo-connector.test.tsx
@@ -128,7 +128,7 @@ describe("RepoConnector", () => {
renderRepoConnector();
- await screen.findByText("Add GitHub repos");
+ await screen.findByText("HOME$ADD_GITHUB_REPOS");
});
it("should not render the 'add git(hub|lab) repos' links if oss mode", async () => {
diff --git a/frontend/__tests__/components/features/home/task-suggestions.test.tsx b/frontend/__tests__/components/features/home/task-suggestions.test.tsx
index 4a3fa3a1f3..9a87da5ece 100644
--- a/frontend/__tests__/components/features/home/task-suggestions.test.tsx
+++ b/frontend/__tests__/components/features/home/task-suggestions.test.tsx
@@ -53,7 +53,7 @@ describe("TaskSuggestions", () => {
it("should render an empty message if there are no tasks", async () => {
getSuggestedTasksSpy.mockResolvedValue([]);
renderTaskSuggestions();
- await screen.findByText(/No tasks available/i);
+ await screen.findByText("TASKS$NO_TASKS_AVAILABLE");
});
it("should render the task groups with the correct titles", async () => {
diff --git a/frontend/__tests__/routes/secrets-settings.test.tsx b/frontend/__tests__/routes/secrets-settings.test.tsx
index 824add9675..e2b922afad 100644
--- a/frontend/__tests__/routes/secrets-settings.test.tsx
+++ b/frontend/__tests__/routes/secrets-settings.test.tsx
@@ -473,7 +473,7 @@ describe("Secret actions", () => {
// make POST request
expect(createSecretSpy).not.toHaveBeenCalled();
- expect(screen.queryByText(/secret already exists/i)).toBeInTheDocument();
+ expect(screen.queryByText("SECRETS$SECRET_ALREADY_EXISTS")).toBeInTheDocument();
await userEvent.clear(nameInput);
await userEvent.type(nameInput, "My_Custom_Secret");
@@ -557,7 +557,7 @@ describe("Secret actions", () => {
// make POST request
expect(createSecretSpy).not.toHaveBeenCalled();
- expect(screen.queryByText(/secret already exists/i)).toBeInTheDocument();
+ expect(screen.queryByText("SECRETS$SECRET_ALREADY_EXISTS")).toBeInTheDocument();
expect(nameInput).toHaveValue(MOCK_GET_SECRETS_RESPONSE[0].name);
expect(valueInput).toHaveValue("my-custom-secret-value");
diff --git a/frontend/scripts/check-unlocalized-strings.cjs b/frontend/scripts/check-unlocalized-strings.cjs
index 9115d5913d..1f0a0d0d08 100755
--- a/frontend/scripts/check-unlocalized-strings.cjs
+++ b/frontend/scripts/check-unlocalized-strings.cjs
@@ -56,8 +56,6 @@ const NON_TEXT_ATTRIBUTES = [
"type",
"href",
"src",
- "alt",
- "placeholder",
"rel",
"target",
"style",
@@ -65,7 +63,6 @@ const NON_TEXT_ATTRIBUTES = [
"onChange",
"onSubmit",
"data-testid",
- "aria-label",
"aria-labelledby",
"aria-describedby",
"aria-hidden",
@@ -139,6 +136,7 @@ function isLikelyCode(str) {
}
function isCommonDevelopmentString(str) {
+
// Technical patterns that are definitely not UI strings
const technicalPatterns = [
// URLs and paths
@@ -191,7 +189,7 @@ function isCommonDevelopmentString(str) {
// CSS units and values
const cssUnitsPattern =
- /(px|rem|em|vh|vw|vmin|vmax|ch|ex|fr|deg|rad|turn|grad|ms|s)$/;
+ /\b\d+(px|rem|em|vh|vw|vmin|vmax|ch|ex|fr|deg|rad|turn|grad|ms|s)$|^(px|rem|em|vh|vw|vmin|vmax|ch|ex|fr|deg|rad|turn|grad|ms|s)$/;
const cssValuesPattern =
/(rgb|rgba|hsl|hsla|#[0-9a-fA-F]+|solid|absolute|relative|sticky|fixed|static|block|inline|flex|grid|none|auto|hidden|visible)/;
@@ -394,6 +392,7 @@ function isCommonDevelopmentString(str) {
}
function isLikelyUserFacingText(str) {
+
// Basic validation - skip very short strings or strings without letters
if (!str || str.length <= 2 || !/[a-zA-Z]/.test(str)) {
return false;
@@ -540,8 +539,8 @@ function isInTranslationContext(path) {
}
function scanFileForUnlocalizedStrings(filePath) {
- // Skip all suggestion files as they contain special strings
- if (filePath.includes("suggestions")) {
+ // Skip suggestion content files as they contain special strings that are already properly localized
+ if (filePath.includes("utils/suggestions/") || filePath.includes("mocks/task-suggestions-handlers.ts")) {
return [];
}
diff --git a/frontend/src/components/features/home/repo-provider-links.tsx b/frontend/src/components/features/home/repo-provider-links.tsx
index e7e23b6a7a..5aa0cf3d49 100644
--- a/frontend/src/components/features/home/repo-provider-links.tsx
+++ b/frontend/src/components/features/home/repo-provider-links.tsx
@@ -1,6 +1,9 @@
+import { useTranslation } from "react-i18next";
import { useConfig } from "#/hooks/query/use-config";
+import { I18nKey } from "#/i18n/declaration";
export function RepoProviderLinks() {
+ const { t } = useTranslation();
const { data: config } = useConfig();
const githubHref = config
@@ -10,7 +13,7 @@ export function RepoProviderLinks() {
return (
);
diff --git a/frontend/src/components/features/home/repository-selection/branch-dropdown.tsx b/frontend/src/components/features/home/repository-selection/branch-dropdown.tsx
index 8f9221304b..587bd03f50 100644
--- a/frontend/src/components/features/home/repository-selection/branch-dropdown.tsx
+++ b/frontend/src/components/features/home/repository-selection/branch-dropdown.tsx
@@ -1,5 +1,7 @@
import React from "react";
+import { useTranslation } from "react-i18next";
import { SettingsDropdownInput } from "../../settings/settings-dropdown-input";
+import { I18nKey } from "#/i18n/declaration";
export interface BranchDropdownProps {
items: { key: React.Key; label: string }[];
@@ -16,11 +18,13 @@ export function BranchDropdown({
isDisabled,
selectedKey,
}: BranchDropdownProps) {
+ const { t } = useTranslation();
+
return (
task.title === filterFor)
@@ -20,11 +23,13 @@ export function TaskSuggestions({ filterFor }: TaskSuggestionsProps) {
data-testid="task-suggestions"
className={cn("flex flex-col w-full", !hasSuggestedTasks && "gap-6")}
>
- Suggested Tasks
+ {t(I18nKey.TASKS$SUGGESTED_TASKS)}
{isLoading &&
}
- {!hasSuggestedTasks && !isLoading &&
No tasks available
}
+ {!hasSuggestedTasks && !isLoading && (
+
{t(I18nKey.TASKS$NO_TASKS_AVAILABLE)}
+ )}
{suggestedTasks?.map((taskGroup, index) => (
,
,
,
secret.name === name && secret.name !== selectedSecret,
);
if (isNameAlreadyUsed) {
- setError("Secret already exists");
+ setError(t("SECRETS$SECRET_ALREADY_EXISTS"));
return;
}
@@ -144,7 +144,7 @@ export function SecretForm({
className="w-full max-w-[350px]"
required
defaultValue={mode === "edit" && selectedSecret ? selectedSecret : ""}
- placeholder="e.g. OpenAI_API_Key"
+ placeholder={t("SECRETS$API_KEY_EXAMPLE")}
pattern="^\S*$"
/>
{error && {error}
}
diff --git a/frontend/src/components/shared/inputs/custom-model-input.tsx b/frontend/src/components/shared/inputs/custom-model-input.tsx
index b578325be9..e727bfea71 100644
--- a/frontend/src/components/shared/inputs/custom-model-input.tsx
+++ b/frontend/src/components/shared/inputs/custom-model-input.tsx
@@ -28,7 +28,7 @@ export function CustomModelInput({
id="custom-model"
name="custom-model"
defaultValue={defaultValue}
- aria-label="Custom Model"
+ aria-label={t(I18nKey.MODEL$CUSTOM_MODEL)}
classNames={{
inputWrapper: "bg-[#27272A] rounded-md text-sm px-3 py-[10px]",
}}
diff --git a/frontend/src/components/shared/modals/security/invariant/invariant.tsx b/frontend/src/components/shared/modals/security/invariant/invariant.tsx
index 9ac72184a7..debabfb431 100644
--- a/frontend/src/components/shared/modals/security/invariant/invariant.tsx
+++ b/frontend/src/components/shared/modals/security/invariant/invariant.tsx
@@ -198,46 +198,46 @@ function SecurityInvariant() {
{t(I18nKey.INVARIANT$ASK_CONFIRMATION_RISK_SEVERITY_LABEL)}