From 3c03d99a16a581e7a6e7e64c44a8eaa94802f2bf Mon Sep 17 00:00:00 2001
From: kawamataryo <ba068082@gmail.com>
Date: Sat, 7 Dec 2024 17:37:30 +0900
Subject: [PATCH 1/2] chore: clean unused code

---
 src/popup.tsx | 19 -------------------
 1 file changed, 19 deletions(-)

diff --git a/src/popup.tsx b/src/popup.tsx
index 9786e53..102495c 100644
--- a/src/popup.tsx
+++ b/src/popup.tsx
@@ -32,7 +32,6 @@ function IndexPopup() {
     documentLink?: string;
   }>(null);
   const isShowErrorMessage = message?.type === MESSAGE_TYPE.ERROR;
-  const isShowSuccessMessage = message?.type === MESSAGE_TYPE.SUCCESS;
 
   const setErrorMessage = (message: string, documentLink?: string) => {
     setMessage({ type: MESSAGE_TYPE.ERROR, message, documentLink });
@@ -384,24 +383,6 @@ function IndexPopup() {
             </span>
           </div>
         )}
-        {isShowSuccessMessage && (
-          <div className="flex gap-2 items-center text-green-600 border border-green-600 p-1 rounded-md mt-2">
-            <svg
-              xmlns="http://www.w3.org/2000/svg"
-              className="stroke-current flex-shrink-0 h-6 w-6"
-              fill="none"
-              viewBox="0 0 24 24"
-            >
-              <path
-                strokeLinecap="round"
-                strokeLinejoin="round"
-                strokeWidth="2"
-                d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
-              />
-            </svg>
-            <span>Success. Try again if no results found.</span>
-          </div>
-        )}
       </form>
     </div>
   );

From fc91c4d3a2b95357f04896ecc82ee58ec84a4293 Mon Sep 17 00:00:00 2001
From: kawamataryo <ba068082@gmail.com>
Date: Sat, 7 Dec 2024 22:40:33 +0900
Subject: [PATCH 2/2] feat: add i18n

---
 .storybook/preview.ts                       |  11 +
 locales/en/messages.json                    | 244 ++++++++++++++++++++
 locales/es/messages.json                    | 244 ++++++++++++++++++++
 locales/fr/messages.json                    | 244 ++++++++++++++++++++
 locales/it/messages.json                    | 244 ++++++++++++++++++++
 locales/ja/messages.json                    | 244 ++++++++++++++++++++
 locales/pt/messages.json                    | 244 ++++++++++++++++++++
 locales/zh/messages.json                    | 244 ++++++++++++++++++++
 package-lock.json                           |   4 +-
 package.json                                |   3 +-
 src/components/ReSearchModal.tsx            |   6 +-
 src/contents/App.tsx                        |  22 +-
 src/lib/components/AsyncButton.tsx          |   2 +-
 src/lib/components/DetectedUserListItem.tsx |  18 +-
 src/lib/components/Sidebar.tsx              |  45 ++--
 src/lib/constants.ts                        |  11 +-
 src/lib/services/threadsService.ts          |   5 +-
 src/lib/utils.ts                            |  18 ++
 src/options.tsx                             |  59 +++--
 src/popup.tsx                               |  55 +++--
 tailwind.config.js                          |   8 +-
 21 files changed, 1882 insertions(+), 93 deletions(-)
 create mode 100644 locales/en/messages.json
 create mode 100644 locales/es/messages.json
 create mode 100644 locales/fr/messages.json
 create mode 100644 locales/it/messages.json
 create mode 100644 locales/ja/messages.json
 create mode 100644 locales/pt/messages.json
 create mode 100644 locales/zh/messages.json

diff --git a/.storybook/preview.ts b/.storybook/preview.ts
index 434310d..6d723eb 100644
--- a/.storybook/preview.ts
+++ b/.storybook/preview.ts
@@ -1,5 +1,16 @@
 import type { Preview } from "@storybook/react";
 import "../src/style.content.css";
+import messages from "../locales/en/messages.json";
+
+const getMessage = (key: string, placeholders: string[]) => {
+  return messages[key].message
+};
+
+window.chrome = {
+  i18n: {
+    getMessage: getMessage,
+  } as typeof chrome.i18n,
+} as typeof chrome;
 
 const preview: Preview = {
   parameters: {
diff --git a/locales/en/messages.json b/locales/en/messages.json
new file mode 100644
index 0000000..4a18875
--- /dev/null
+++ b/locales/en/messages.json
@@ -0,0 +1,244 @@
+{
+  "learn_more": {
+    "message": "Learn more",
+    "description": "Text for the learn more link"
+  },
+  "auth_factor_token": {
+    "message": "Auth Factor Token",
+    "description": "Text for the auth factor token input"
+  },
+  "2fa_token_sent": {
+    "message": "A 2FA token has been sent to your email",
+    "description": "Text for the 2FA token sent message"
+  },
+  "find_bluesky_users": {
+    "message": "Find Bluesky Users",
+    "description": "Text for the find bluesky users button"
+  },
+  "finding_bluesky_users": {
+    "message": "Finding Bluesky Users",
+    "description": "Text for the finding bluesky users message"
+  },
+  "password": {
+    "message": "Password",
+    "description": "Text for the password input"
+  },
+  "handle_or_email": {
+    "message": "Handle or Email",
+    "description": "Text for the handle or email input"
+  },
+  "recommended_to_use_app_password": {
+    "message": "We recommend using the [App Password](https://bsky.app/settings/app-passwords).",
+    "description": "Text for the recommended to use app password message"
+  },
+  "error_enter_password": {
+    "message": "Error: Please enter your password.",
+    "description": "Text for the error message when the password is not entered"
+  },
+  "error_enter_identifier": {
+    "message": "Error: Please enter your handle or email.",
+    "description": "Text for the error message when the handle or email is not entered"
+  },
+  "error_enter_identifier_and_password": {
+    "message": "Error: Please enter your handle or email and password.",
+    "description": "Text for the error message when the handle or email and password are not entered"
+  },
+  "error_enter_auth_factor_token": {
+    "message": "Error: Please enter your auth factor token.",
+    "description": "Text for the error message when the auth factor token is not entered"
+  },
+  "error_invalid_identifier_or_password": {
+    "message": "Error: Invalid handle or password.",
+    "description": "Text for the error message when the handle or password is invalid"
+  },
+  "error_invalid_page": {
+    "message": "Error: Invalid page. please open the target page.",
+    "description": "Text for the error message when the page is invalid"
+  },
+  "error_something_went_wrong": {
+    "message": "Error: Something went wrong. Please reload the web page and try again.",
+    "description": "Text for the error message when something went wrong"
+  },
+  "error_invalid_page_in_threads": {
+    "message": "Error: Invalid page. please open the followers or following view.",
+    "description": "Text for the error message when the page is invalid in threads"
+  },
+  "scanning_users": {
+    "message": "Scanning $service$ users to find bsky users...",
+    "description": "Text for the scanning users message",
+    "placeholders": {
+      "service": {
+        "content": "$1",
+        "example": "X"
+      }
+    }
+  },
+  "detected_users": {
+    "message": "Found <span class='text-4xl'>$users$</span> users",
+    "description": "Text for the detected users message",
+    "placeholders": {
+      "users": {
+        "content": "$1"
+      }
+    }
+  },
+  "stop_scanning_and_view_results": {
+    "message": "Stop Scanning and View Results",
+    "description": "Text for the stop scanning and view results button"
+  },
+  "resume_scanning": {
+    "message": "Resume Scanning",
+    "description": "Text for the resume scanning button"
+  },
+  "view_detected_users": {
+    "message": "View Detected Users",
+    "description": "Text for the view detected users button"
+  },
+  "follow_all_confirmation_title": {
+    "message": "Proceed with Execution?",
+    "description": "Text for the follow all confirmation title"
+  },
+  "follow_all_confirmation_message": {
+    "message": "User detection is not perfect and may include false positives.",
+    "description": "Text for the follow all confirmation message"
+  },
+  "import_list_confirmation_title": {
+    "message": "Proceed with Execution?",
+    "description": "Text for the import list confirmation title"
+  },
+  "import_list_confirmation_message": {
+    "message": "Importing a list will create a new list and add all detected users to it. This feature is experimental and may not work as expected.",
+    "description": "Text for the import list confirmation message"
+  },
+  "confirmation_cancel": {
+    "message": "Cancel",
+    "description": "Text for the confirmation cancel button"
+  },
+  "confirmation_ok": {
+    "message": "OK",
+    "description": "Text for the confirmation ok button"
+  },
+  "toast_pending": {
+    "message": "Processing...",
+    "description": "Text for the toast pending message"
+  },
+  "toast_follow_all_success": {
+    "message": "Followed $count$ users🎉",
+    "description": "Text for the toast follow all success message",
+    "placeholders": {
+      "count": {
+        "content": "$1"
+      }
+    }
+  },
+  "toast_block_all_success": {
+    "message": "Blocked $count$ users🎉",
+    "description": "Text for the toast block all success message",
+    "placeholders": {
+      "count": {
+        "content": "$1"
+      }
+    }
+  },
+  "toast_import_list_success_view_list": {
+    "message": "View Imported List",
+    "description": "Text for the toast import list success view list button"
+  },
+  "toast_import_list_success": {
+    "message": "List imported successfully🎉",
+    "description": "Text for the toast import list success message"
+  },
+  "toast_import_list_error": {
+    "message": "Failed to import list: $error$",
+    "description": "Text for the toast import list error message",
+    "placeholders": {
+      "error": {
+        "content": "$1"
+      }
+    }
+  },
+  "source": {
+    "message": "Source",
+    "description": "Text for the source"
+  },
+  "detected": {
+    "message": "Detected",
+    "description": "Text for the detected"
+  },
+  "warning_user_detection": {
+    "message": "User detection is not perfect and may include false positives.",
+    "description": "Text for the warning user detection"
+  },
+  "donate_message": {
+    "message": "This tool is developed by an individual. Your [support](https://ko-fi.com/kawamataryo) helps maintain and improve it. Thank you!",
+    "description": "Text for the donate message"
+  },
+  "follow_all": {
+    "message": "Follow All",
+    "description": "Text for the follow all button"
+  },
+  "block_all": {
+    "message": "Block All",
+    "description": "Text for the block all button"
+  },
+  "import_list": {
+    "message": "Import List",
+    "description": "Text for the import list button"
+  },
+  "followed_users": {
+    "message": "Followed Users",
+    "description": "Text for the followed users"
+  },
+  "same_handle_name": {
+    "message": "Same Handle Name",
+    "description": "Text for the same handle name"
+  },
+  "same_display_name": {
+    "message": "Same Display Name",
+    "description": "Text for the same display name"
+  },
+  "included_handle_in_description": {
+    "message": "Included Handle in Description",
+    "description": "Text for the included handle in description"
+  },
+  "blocked_user": {
+    "message": "Blocked Users",
+    "description": "Text for the blocked user"
+  },
+  "sidebar_detected_users": {
+    "message": "Detected Users",
+    "description": "Text for the sidebar detected users"
+  },
+  "button_follow_on_bluesky": {
+    "message": "Follow on Bluesky",
+    "description": "Text for the button follow on bluesky"
+  },
+  "button_following_on_bluesky": {
+    "message": "Following on Bluesky",
+    "description": "Text for the button following on bluesky"
+  },
+  "button_unfollow_on_bluesky": {
+    "message": "Unfollow on Bluesky",
+    "description": "Text for the button unfollow on bluesky"
+  },
+  "button_block_on_bluesky": {
+    "message": "Block on Bluesky",
+    "description": "Text for the button block on bluesky"
+  },
+  "button_blocking_on_bluesky": {
+    "message": "Blocking on Bluesky",
+    "description": "Text for the button blocking on bluesky"
+  },
+  "button_unblock_on_bluesky": {
+    "message": "Unblock on Bluesky",
+    "description": "Text for the button unblock on bluesky"
+  },
+  "loading": {
+    "message": "Loading...",
+    "description": "Text for the loading message"
+  },
+  "re_search_modal_title": {
+    "message": "Search Results",
+    "description": "Text for the re search modal title"
+  }
+}
diff --git a/locales/es/messages.json b/locales/es/messages.json
new file mode 100644
index 0000000..440a859
--- /dev/null
+++ b/locales/es/messages.json
@@ -0,0 +1,244 @@
+{
+  "learn_more": {
+    "message": "Más información",
+    "description": "Text for the learn more link"
+  },
+  "auth_factor_token": {
+    "message": "Token de autenticación",
+    "description": "Text for the auth factor token input"
+  },
+  "2fa_token_sent": {
+    "message": "Se ha enviado un token 2FA a tu correo electrónico",
+    "description": "Text for the 2FA token sent message"
+  },
+  "find_bluesky_users": {
+    "message": "Buscar usuarios de Bluesky",
+    "description": "Text for the find bluesky users button"
+  },
+  "finding_bluesky_users": {
+    "message": "Buscando usuarios de Bluesky",
+    "description": "Text for the finding bluesky users message"
+  },
+  "password": {
+    "message": "Contraseña",
+    "description": "Text for the password input"
+  },
+  "handle_or_email": {
+    "message": "Usuario o correo electrónico",
+    "description": "Text for the handle or email input"
+  },
+  "recommended_to_use_app_password": {
+    "message": "Recomendamos usar la [Contraseña de aplicación](https://bsky.app/settings/app-passwords).",
+    "description": "Text for the recommended to use app password message"
+  },
+  "error_enter_password": {
+    "message": "Error: Por favor, introduce tu contraseña.",
+    "description": "Text for the error message when the password is not entered"
+  },
+  "error_enter_identifier": {
+    "message": "Error: Por favor, introduce tu usuario o correo electrónico.",
+    "description": "Text for the error message when the handle or email is not entered"
+  },
+  "error_enter_identifier_and_password": {
+    "message": "Error: Por favor, introduce tu usuario o correo electrónico y contraseña.",
+    "description": "Text for the error message when the handle or email and password are not entered"
+  },
+  "error_enter_auth_factor_token": {
+    "message": "Error: Por favor, introduce tu token de autenticación.",
+    "description": "Text for the error message when the auth factor token is not entered"
+  },
+  "error_invalid_identifier_or_password": {
+    "message": "Error: Usuario o contraseña inválidos.",
+    "description": "Text for the error message when the handle or password is invalid"
+  },
+  "error_invalid_page": {
+    "message": "Error: Página inválida. Por favor, abre la página objetivo.",
+    "description": "Text for the error message when the page is invalid"
+  },
+  "error_something_went_wrong": {
+    "message": "Error: Algo salió mal. Por favor, recarga la página web e inténtalo de nuevo.",
+    "description": "Text for the error message when something went wrong"
+  },
+  "error_invalid_page_in_threads": {
+    "message": "Error: Página inválida. Por favor, abre la vista de seguidores o seguidos.",
+    "description": "Text for the error message when the page is invalid in threads"
+  },
+  "scanning_users": {
+    "message": "Escaneando usuarios de $service$ para encontrar usuarios de Bluesky...",
+    "description": "Text for the scanning users message",
+    "placeholders": {
+      "service": {
+        "content": "$1",
+        "example": "X"
+      }
+    }
+  },
+  "detected_users": {
+    "message": "Se han detectado <span class='text-4xl'>$users$</span> usuarios",
+    "description": "Text for the detected users message",
+    "placeholders": {
+      "users": {
+        "content": "$1"
+      }
+    }
+  },
+  "stop_scanning_and_view_results": {
+    "message": "Detener escaneo y ver resultados",
+    "description": "Text for the stop scanning and view results button"
+  },
+  "resume_scanning": {
+    "message": "Reanudar escaneo",
+    "description": "Text for the resume scanning button"
+  },
+  "view_detected_users": {
+    "message": "Ver usuarios detectados",
+    "description": "Text for the view detected users button"
+  },
+  "follow_all_confirmation_title": {
+    "message": "¿Proceder con la ejecución?",
+    "description": "Text for the follow all confirmation title"
+  },
+  "follow_all_confirmation_message": {
+    "message": "La detección de usuarios no es perfecta y puede incluir falsos positivos.",
+    "description": "Text for the follow all confirmation message"
+  },
+  "import_list_confirmation_title": {
+    "message": "¿Proceder con la ejecución?",
+    "description": "Text for the import list confirmation title"
+  },
+  "import_list_confirmation_message": {
+    "message": "Importar una lista creará una nueva lista y añadirá todos los usuarios detectados. Esta función es experimental y puede no funcionar como se espera.",
+    "description": "Text for the import list confirmation message"
+  },
+  "confirmation_cancel": {
+    "message": "Cancelar",
+    "description": "Text for the confirmation cancel button"
+  },
+  "confirmation_ok": {
+    "message": "Aceptar",
+    "description": "Text for the confirmation ok button"
+  },
+  "toast_pending": {
+    "message": "Procesando...",
+    "description": "Text for the toast pending message"
+  },
+  "toast_follow_all_success": {
+    "message": "Siguiendo a $count$ usuarios 🎉",
+    "description": "Text for the toast follow all success message",
+    "placeholders": {
+      "count": {
+        "content": "$1"
+      }
+    }
+  },
+  "toast_block_all_success": {
+    "message": "Bloqueados $count$ usuarios 🎉",
+    "description": "Text for the toast block all success message",
+    "placeholders": {
+      "count": {
+        "content": "$1"
+      }
+    }
+  },
+  "toast_import_list_success_view_list": {
+    "message": "Ver lista importada",
+    "description": "Text for the toast import list success view list button"
+  },
+  "toast_import_list_success": {
+    "message": "Lista importada exitosamente 🎉",
+    "description": "Text for the toast import list success message"
+  },
+  "toast_import_list_error": {
+    "message": "Error al importar lista: $error$",
+    "description": "Text for the toast import list error message",
+    "placeholders": {
+      "error": {
+        "content": "$1"
+      }
+    }
+  },
+  "source": {
+    "message": "Origen",
+    "description": "Text for the source"
+  },
+  "detected": {
+    "message": "Detectado",
+    "description": "Text for the detected"
+  },
+  "warning_user_detection": {
+    "message": "La detección de usuarios no es perfecta y puede incluir falsos positivos.",
+    "description": "Text for the warning user detection"
+  },
+  "donate_message": {
+    "message": "Esta herramienta está desarrollada por un individuo. Tu [apoyo](https://ko-fi.com/kawamataryo) ayuda a mantenerla y mejorarla. ¡Gracias!",
+    "description": "Text for the donate message"
+  },
+  "follow_all": {
+    "message": "Seguir a todos",
+    "description": "Text for the follow all button"
+  },
+  "block_all": {
+    "message": "Bloquear a todos",
+    "description": "Text for the block all button"
+  },
+  "import_list": {
+    "message": "Importar lista",
+    "description": "Text for the import list button"
+  },
+  "followed_users": {
+    "message": "Usuarios seguidos",
+    "description": "Text for the followed users"
+  },
+  "same_handle_name": {
+    "message": "Mismo nombre de usuario",
+    "description": "Text for the same handle name"
+  },
+  "same_display_name": {
+    "message": "Mismo nombre mostrado",
+    "description": "Text for the same display name"
+  },
+  "included_handle_in_description": {
+    "message": "Usuario incluido en la descripción",
+    "description": "Text for the included handle in description"
+  },
+  "blocked_user": {
+    "message": "Usuarios bloqueados",
+    "description": "Text for the blocked user"
+  },
+  "sidebar_detected_users": {
+    "message": "Usuarios detectados",
+    "description": "Text for the sidebar detected users"
+  },
+  "button_follow_on_bluesky": {
+    "message": "Seguir en Bluesky",
+    "description": "Text for the button follow on bluesky"
+  },
+  "button_following_on_bluesky": {
+    "message": "Siguiendo en Bluesky",
+    "description": "Text for the button following on bluesky"
+  },
+  "button_unfollow_on_bluesky": {
+    "message": "Dejar de seguir en Bluesky",
+    "description": "Text for the button unfollow on bluesky"
+  },
+  "button_block_on_bluesky": {
+    "message": "Bloquear en Bluesky",
+    "description": "Text for the button block on bluesky"
+  },
+  "button_blocking_on_bluesky": {
+    "message": "Bloqueando en Bluesky",
+    "description": "Text for the button blocking on bluesky"
+  },
+  "button_unblock_on_bluesky": {
+    "message": "Desbloquear en Bluesky",
+    "description": "Text for the button unblock on bluesky"
+  },
+  "loading": {
+    "message": "Cargando...",
+    "description": "Text for the loading message"
+  },
+  "re_search_modal_title": {
+    "message": "Resultados de búsqueda",
+    "description": "Text for the re search modal title"
+  }
+}
diff --git a/locales/fr/messages.json b/locales/fr/messages.json
new file mode 100644
index 0000000..dba59e7
--- /dev/null
+++ b/locales/fr/messages.json
@@ -0,0 +1,244 @@
+{
+  "learn_more": {
+    "message": "En savoir plus",
+    "description": "Text for the learn more link"
+  },
+  "auth_factor_token": {
+    "message": "Jeton d'authentification",
+    "description": "Text for the auth factor token input"
+  },
+  "2fa_token_sent": {
+    "message": "Un jeton 2FA a été envoyé à votre e-mail",
+    "description": "Text for the 2FA token sent message"
+  },
+  "find_bluesky_users": {
+    "message": "Trouver des utilisateurs Bluesky",
+    "description": "Text for the find bluesky users button"
+  },
+  "finding_bluesky_users": {
+    "message": "Recherche d'utilisateurs Bluesky",
+    "description": "Text for the finding bluesky users message"
+  },
+  "password": {
+    "message": "Mot de passe",
+    "description": "Text for the password input"
+  },
+  "handle_or_email": {
+    "message": "Identifiant ou e-mail",
+    "description": "Text for the handle or email input"
+  },
+  "recommended_to_use_app_password": {
+    "message": "Nous recommandons d'utiliser le [Mot de passe d'application](https://bsky.app/settings/app-passwords).",
+    "description": "Text for the recommended to use app password message"
+  },
+  "error_enter_password": {
+    "message": "Erreur : Veuillez saisir votre mot de passe.",
+    "description": "Text for the error message when the password is not entered"
+  },
+  "error_enter_identifier": {
+    "message": "Erreur : Veuillez saisir votre identifiant ou e-mail.",
+    "description": "Text for the error message when the handle or email is not entered"
+  },
+  "error_enter_identifier_and_password": {
+    "message": "Erreur : Veuillez saisir votre identifiant ou e-mail et mot de passe.",
+    "description": "Text for the error message when the handle or email and password are not entered"
+  },
+  "error_enter_auth_factor_token": {
+    "message": "Erreur : Veuillez saisir votre jeton d'authentification.",
+    "description": "Text for the error message when the auth factor token is not entered"
+  },
+  "error_invalid_identifier_or_password": {
+    "message": "Erreur : Identifiant ou mot de passe invalide.",
+    "description": "Text for the error message when the handle or password is invalid"
+  },
+  "error_invalid_page": {
+    "message": "Erreur : Page invalide. Veuillez ouvrir la page cible.",
+    "description": "Text for the error message when the page is invalid"
+  },
+  "error_something_went_wrong": {
+    "message": "Erreur : Une erreur s'est produite. Veuillez recharger la page web et réessayer.",
+    "description": "Text for the error message when something went wrong"
+  },
+  "error_invalid_page_in_threads": {
+    "message": "Erreur : Page invalide. Veuillez ouvrir la vue des abonnés ou des abonnements.",
+    "description": "Text for the error message when the page is invalid in threads"
+  },
+  "scanning_users": {
+    "message": "Analyse des utilisateurs $service$ pour trouver des utilisateurs Bluesky...",
+    "description": "Text for the scanning users message",
+    "placeholders": {
+      "service": {
+        "content": "$1",
+        "example": "X"
+      }
+    }
+  },
+  "detected_users": {
+    "message": "<span class='text-4xl'>$users$</span> utilisateurs détectés",
+    "description": "Text for the detected users message",
+    "placeholders": {
+      "users": {
+        "content": "$1"
+      }
+    }
+  },
+  "stop_scanning_and_view_results": {
+    "message": "Arrêter l'analyse et voir les résultats",
+    "description": "Text for the stop scanning and view results button"
+  },
+  "resume_scanning": {
+    "message": "Reprendre l'analyse",
+    "description": "Text for the resume scanning button"
+  },
+  "view_detected_users": {
+    "message": "Voir les utilisateurs détectés",
+    "description": "Text for the view detected users button"
+  },
+  "follow_all_confirmation_title": {
+    "message": "Procéder à l'exécution ?",
+    "description": "Text for the follow all confirmation title"
+  },
+  "follow_all_confirmation_message": {
+    "message": "La détection des utilisateurs n'est pas parfaite et peut inclure des faux positifs.",
+    "description": "Text for the follow all confirmation message"
+  },
+  "import_list_confirmation_title": {
+    "message": "Procéder à l'exécution ?",
+    "description": "Text for the import list confirmation title"
+  },
+  "import_list_confirmation_message": {
+    "message": "L'importation d'une liste créera une nouvelle liste et y ajoutera tous les utilisateurs détectés. Cette fonctionnalité est expérimentale et peut ne pas fonctionner comme prévu.",
+    "description": "Text for the import list confirmation message"
+  },
+  "confirmation_cancel": {
+    "message": "Annuler",
+    "description": "Text for the confirmation cancel button"
+  },
+  "confirmation_ok": {
+    "message": "OK",
+    "description": "Text for the confirmation ok button"
+  },
+  "toast_pending": {
+    "message": "Traitement en cours...",
+    "description": "Text for the toast pending message"
+  },
+  "toast_follow_all_success": {
+    "message": "$count$ utilisateurs suivis 🎉",
+    "description": "Text for the toast follow all success message",
+    "placeholders": {
+      "count": {
+        "content": "$1"
+      }
+    }
+  },
+  "toast_block_all_success": {
+    "message": "$count$ utilisateurs bloqués 🎉",
+    "description": "Text for the toast block all success message",
+    "placeholders": {
+      "count": {
+        "content": "$1"
+      }
+    }
+  },
+  "toast_import_list_success_view_list": {
+    "message": "Voir la liste importée",
+    "description": "Text for the toast import list success view list button"
+  },
+  "toast_import_list_success": {
+    "message": "Liste importée avec succès 🎉",
+    "description": "Text for the toast import list success message"
+  },
+  "toast_import_list_error": {
+    "message": "Échec de l'importation de la liste : $error$",
+    "description": "Text for the toast import list error message",
+    "placeholders": {
+      "error": {
+        "content": "$1"
+      }
+    }
+  },
+  "source": {
+    "message": "Source",
+    "description": "Text for the source"
+  },
+  "detected": {
+    "message": "Détecté",
+    "description": "Text for the detected"
+  },
+  "warning_user_detection": {
+    "message": "La détection des utilisateurs n'est pas parfaite et peut inclure des faux positifs.",
+    "description": "Text for the warning user detection"
+  },
+  "donate_message": {
+    "message": "Cet outil est développé par un individu. Votre [soutien](https://ko-fi.com/kawamataryo) aide à le maintenir et à l'améliorer. Merci !",
+    "description": "Text for the donate message"
+  },
+  "follow_all": {
+    "message": "Suivre tous",
+    "description": "Text for the follow all button"
+  },
+  "block_all": {
+    "message": "Bloquer tous",
+    "description": "Text for the block all button"
+  },
+  "import_list": {
+    "message": "Importer la liste",
+    "description": "Text for the import list button"
+  },
+  "followed_users": {
+    "message": "Utilisateurs suivis",
+    "description": "Text for the followed users"
+  },
+  "same_handle_name": {
+    "message": "Même identifiant",
+    "description": "Text for the same handle name"
+  },
+  "same_display_name": {
+    "message": "Même nom d'affichage",
+    "description": "Text for the same display name"
+  },
+  "included_handle_in_description": {
+    "message": "Identifiant inclus dans la description",
+    "description": "Text for the included handle in description"
+  },
+  "blocked_user": {
+    "message": "Utilisateurs bloqués",
+    "description": "Text for the blocked user"
+  },
+  "sidebar_detected_users": {
+    "message": "Utilisateurs détectés",
+    "description": "Text for the sidebar detected users"
+  },
+  "button_follow_on_bluesky": {
+    "message": "Suivre sur Bluesky",
+    "description": "Text for the button follow on bluesky"
+  },
+  "button_following_on_bluesky": {
+    "message": "Suivi sur Bluesky",
+    "description": "Text for the button following on bluesky"
+  },
+  "button_unfollow_on_bluesky": {
+    "message": "Ne plus suivre sur Bluesky",
+    "description": "Text for the button unfollow on bluesky"
+  },
+  "button_block_on_bluesky": {
+    "message": "Bloquer sur Bluesky",
+    "description": "Text for the button block on bluesky"
+  },
+  "button_blocking_on_bluesky": {
+    "message": "Bloqué sur Bluesky",
+    "description": "Text for the button blocking on bluesky"
+  },
+  "button_unblock_on_bluesky": {
+    "message": "Débloquer sur Bluesky",
+    "description": "Text for the button unblock on bluesky"
+  },
+  "loading": {
+    "message": "Chargement...",
+    "description": "Text for the loading message"
+  },
+  "re_search_modal_title": {
+    "message": "Résultats de recherche",
+    "description": "Text for the re search modal title"
+  }
+}
diff --git a/locales/it/messages.json b/locales/it/messages.json
new file mode 100644
index 0000000..f3edde3
--- /dev/null
+++ b/locales/it/messages.json
@@ -0,0 +1,244 @@
+{
+  "learn_more": {
+    "message": "Scopri di più",
+    "description": "Text for the learn more link"
+  },
+  "auth_factor_token": {
+    "message": "Token di autenticazione",
+    "description": "Text for the auth factor token input"
+  },
+  "2fa_token_sent": {
+    "message": "Un token 2FA è stato inviato alla tua email",
+    "description": "Text for the 2FA token sent message"
+  },
+  "find_bluesky_users": {
+    "message": "Trova utenti Bluesky",
+    "description": "Text for the find bluesky users button"
+  },
+  "finding_bluesky_users": {
+    "message": "Ricerca utenti Bluesky",
+    "description": "Text for the finding bluesky users message"
+  },
+  "password": {
+    "message": "Password",
+    "description": "Text for the password input"
+  },
+  "handle_or_email": {
+    "message": "Nome utente o Email",
+    "description": "Text for the handle or email input"
+  },
+  "recommended_to_use_app_password": {
+    "message": "Si consiglia di utilizzare la [Password dell'app](https://bsky.app/settings/app-passwords).",
+    "description": "Text for the recommended to use app password message"
+  },
+  "error_enter_password": {
+    "message": "Errore: Inserisci la tua password.",
+    "description": "Text for the error message when the password is not entered"
+  },
+  "error_enter_identifier": {
+    "message": "Errore: Inserisci il tuo nome utente o email.",
+    "description": "Text for the error message when the handle or email is not entered"
+  },
+  "error_enter_identifier_and_password": {
+    "message": "Errore: Inserisci il tuo nome utente o email e password.",
+    "description": "Text for the error message when the handle or email and password are not entered"
+  },
+  "error_enter_auth_factor_token": {
+    "message": "Errore: Inserisci il tuo token di autenticazione.",
+    "description": "Text for the error message when the auth factor token is not entered"
+  },
+  "error_invalid_identifier_or_password": {
+    "message": "Errore: Nome utente o password non validi.",
+    "description": "Text for the error message when the handle or password is invalid"
+  },
+  "error_invalid_page": {
+    "message": "Errore: Pagina non valida. Apri la pagina di destinazione.",
+    "description": "Text for the error message when the page is invalid"
+  },
+  "error_something_went_wrong": {
+    "message": "Errore: Qualcosa è andato storto. Ricarica la pagina web e riprova.",
+    "description": "Text for the error message when something went wrong"
+  },
+  "error_invalid_page_in_threads": {
+    "message": "Errore: Pagina non valida. Apri la vista follower o seguiti.",
+    "description": "Text for the error message when the page is invalid in threads"
+  },
+  "scanning_users": {
+    "message": "Scansione degli utenti $service$ per trovare utenti Bluesky...",
+    "description": "Text for the scanning users message",
+    "placeholders": {
+      "service": {
+        "content": "$1",
+        "example": "X"
+      }
+    }
+  },
+  "detected_users": {
+    "message": "Trovati <span class='text-4xl'>$users$</span> utenti",
+    "description": "Text for the detected users message",
+    "placeholders": {
+      "users": {
+        "content": "$1"
+      }
+    }
+  },
+  "stop_scanning_and_view_results": {
+    "message": "Interrompi scansione e visualizza risultati",
+    "description": "Text for the stop scanning and view results button"
+  },
+  "resume_scanning": {
+    "message": "Riprendi scansione",
+    "description": "Text for the resume scanning button"
+  },
+  "view_detected_users": {
+    "message": "Visualizza utenti rilevati",
+    "description": "Text for the view detected users button"
+  },
+  "follow_all_confirmation_title": {
+    "message": "Procedere con l'esecuzione?",
+    "description": "Text for the follow all confirmation title"
+  },
+  "follow_all_confirmation_message": {
+    "message": "Il rilevamento degli utenti non è perfetto e potrebbe includere falsi positivi.",
+    "description": "Text for the follow all confirmation message"
+  },
+  "import_list_confirmation_title": {
+    "message": "Procedere con l'esecuzione?",
+    "description": "Text for the import list confirmation title"
+  },
+  "import_list_confirmation_message": {
+    "message": "L'importazione di una lista creerà una nuova lista e aggiungerà tutti gli utenti rilevati. Questa funzione è sperimentale e potrebbe non funzionare come previsto.",
+    "description": "Text for the import list confirmation message"
+  },
+  "confirmation_cancel": {
+    "message": "Annulla",
+    "description": "Text for the confirmation cancel button"
+  },
+  "confirmation_ok": {
+    "message": "OK",
+    "description": "Text for the confirmation ok button"
+  },
+  "toast_pending": {
+    "message": "Elaborazione in corso...",
+    "description": "Text for the toast pending message"
+  },
+  "toast_follow_all_success": {
+    "message": "Seguiti $count$ utenti 🎉",
+    "description": "Text for the toast follow all success message",
+    "placeholders": {
+      "count": {
+        "content": "$1"
+      }
+    }
+  },
+  "toast_block_all_success": {
+    "message": "Bloccati $count$ utenti 🎉",
+    "description": "Text for the toast block all success message",
+    "placeholders": {
+      "count": {
+        "content": "$1"
+      }
+    }
+  },
+  "toast_import_list_success_view_list": {
+    "message": "Visualizza lista importata",
+    "description": "Text for the toast import list success view list button"
+  },
+  "toast_import_list_success": {
+    "message": "Lista importata con successo 🎉",
+    "description": "Text for the toast import list success message"
+  },
+  "toast_import_list_error": {
+    "message": "Impossibile importare la lista: $error$",
+    "description": "Text for the toast import list error message",
+    "placeholders": {
+      "error": {
+        "content": "$1"
+      }
+    }
+  },
+  "source": {
+    "message": "Fonte",
+    "description": "Text for the source"
+  },
+  "detected": {
+    "message": "Rilevato",
+    "description": "Text for the detected"
+  },
+  "warning_user_detection": {
+    "message": "Il rilevamento degli utenti non è perfetto e potrebbe includere falsi positivi.",
+    "description": "Text for the warning user detection"
+  },
+  "donate_message": {
+    "message": "Questo strumento è sviluppato da un individuo. Il tuo [supporto](https://ko-fi.com/kawamataryo) aiuta a mantenerlo e migliorarlo. Grazie!",
+    "description": "Text for the donate message"
+  },
+  "follow_all": {
+    "message": "Segui tutti",
+    "description": "Text for the follow all button"
+  },
+  "block_all": {
+    "message": "Blocca tutti",
+    "description": "Text for the block all button"
+  },
+  "import_list": {
+    "message": "Importa lista",
+    "description": "Text for the import list button"
+  },
+  "followed_users": {
+    "message": "Utenti seguiti",
+    "description": "Text for the followed users"
+  },
+  "same_handle_name": {
+    "message": "Stesso nome utente",
+    "description": "Text for the same handle name"
+  },
+  "same_display_name": {
+    "message": "Stesso nome visualizzato",
+    "description": "Text for the same display name"
+  },
+  "included_handle_in_description": {
+    "message": "Nome utente incluso nella descrizione",
+    "description": "Text for the included handle in description"
+  },
+  "blocked_user": {
+    "message": "Utenti bloccati",
+    "description": "Text for the blocked user"
+  },
+  "sidebar_detected_users": {
+    "message": "Utenti rilevati",
+    "description": "Text for the sidebar detected users"
+  },
+  "button_follow_on_bluesky": {
+    "message": "Segui su Bluesky",
+    "description": "Text for the button follow on bluesky"
+  },
+  "button_following_on_bluesky": {
+    "message": "Segui già su Bluesky",
+    "description": "Text for the button following on bluesky"
+  },
+  "button_unfollow_on_bluesky": {
+    "message": "Smetti di seguire su Bluesky",
+    "description": "Text for the button unfollow on bluesky"
+  },
+  "button_block_on_bluesky": {
+    "message": "Blocca su Bluesky",
+    "description": "Text for the button block on bluesky"
+  },
+  "button_blocking_on_bluesky": {
+    "message": "Bloccato su Bluesky",
+    "description": "Text for the button blocking on bluesky"
+  },
+  "button_unblock_on_bluesky": {
+    "message": "Sblocca su Bluesky",
+    "description": "Text for the button unblock on bluesky"
+  },
+  "loading": {
+    "message": "Caricamento...",
+    "description": "Text for the loading message"
+  },
+  "re_search_modal_title": {
+    "message": "Risultati della ricerca",
+    "description": "Text for the re search modal title"
+  }
+}
diff --git a/locales/ja/messages.json b/locales/ja/messages.json
new file mode 100644
index 0000000..a7d369b
--- /dev/null
+++ b/locales/ja/messages.json
@@ -0,0 +1,244 @@
+{
+  "learn_more": {
+    "message": "詳細を見る",
+    "description": "Text for the learn more link"
+  },
+  "auth_factor_token": {
+    "message": "2要素認証トークン",
+    "description": "Text for the auth factor token input"
+  },
+  "2fa_token_sent": {
+    "message": "2要素認証トークンがメールに送信されました",
+    "description": "Text for the 2FA token sent message"
+  },
+  "find_bluesky_users": {
+    "message": "Blueskyユーザーを探す",
+    "description": "Text for the find bluesky users button"
+  },
+  "finding_bluesky_users": {
+    "message": "検索中",
+    "description": "Text for the finding bluesky users message"
+  },
+  "password": {
+    "message": "パスワード",
+    "description": "Text for the password input"
+  },
+  "handle_or_email": {
+    "message": "ハンドル名またはメールアドレス",
+    "description": "Text for the handle or email input"
+  },
+  "recommended_to_use_app_password": {
+    "message": "[アプリパスワード](https://bsky.app/settings/app-passwords)の使用をお勧めします",
+    "description": "Text for the recommended to use app password message"
+  },
+  "error_enter_password": {
+    "message": "エラー: パスワードを入力してください。",
+    "description": "Text for the error message when the password is not entered"
+  },
+  "error_enter_identifier": {
+    "message": "エラー: ハンドルまたはメールを入力してください。",
+    "description": "Text for the error message when the handle or email is not entered"
+  },
+  "error_enter_identifier_and_password": {
+    "message": "エラー: ハンドルまたはメールとパスワードを入力してください。",
+    "description": "Text for the error message when the handle or email and password are not entered"
+  },
+  "error_enter_auth_factor_token": {
+    "message": "エラー: 2要素認証トークンを入力してください。",
+    "description": "Text for the error message when the auth factor token is not entered"
+  },
+  "error_invalid_identifier_or_password": {
+    "message": "エラー: ハンドル名またはパスワードが無効です。",
+    "description": "Text for the error message when the handle or password is invalid"
+  },
+  "error_invalid_page": {
+    "message": "エラー: 無効なページです。対象のページを開いてください。",
+    "description": "Text for the error message when the page is invalid"
+  },
+  "error_something_went_wrong": {
+    "message": "エラー: 問題が発生しました。ウェブページをリロードして再試行してください。",
+    "description": "Text for the error message when something went wrong"
+  },
+  "error_invalid_page_in_threads": {
+    "message": "エラー: 無効なページです。フォロワー/フォロー中一覧を開いてください。",
+    "description": "Text for the error message when the page is invalid in threads"
+  },
+  "scanning_users": {
+    "message": "$service$のからBlueskyのユーザーを検索中...",
+    "description": "Text for the scanning users message",
+    "placeholders": {
+      "service": {
+        "content": "$1",
+        "example": "X"
+      }
+    }
+  },
+  "detected_users": {
+    "message": "<span class='text-4xl'>$users$</span> 人を検出",
+    "description": "Text for the detected users message",
+    "placeholders": {
+      "users": {
+        "content": "$1"
+      }
+    }
+  },
+  "stop_scanning_and_view_results": {
+    "message": "検索を停止して結果を表示",
+    "description": "Text for the stop scanning and view results button"
+  },
+  "resume_scanning": {
+    "message": "検索を再開",
+    "description": "Text for the resume scanning button"
+  },
+  "view_detected_users": {
+    "message": "検出したユーザーを表示",
+    "description": "Text for the view detected users button"
+  },
+  "follow_all_confirmation_title": {
+    "message": "実行しますか?",
+    "description": "Text for the follow all confirmation title"
+  },
+  "follow_all_confirmation_message": {
+    "message": "ユーザー検出は完璧ではありません。誤検出が含まれる可能性があります。",
+    "description": "Text for the follow all confirmation message"
+  },
+  "import_list_confirmation_title": {
+    "message": "実行しますか?",
+    "description": "Text for the import list confirmation title"
+  },
+  "import_list_confirmation_message": {
+    "message": "リストのインポートは実験的な機能です。また、ユーザー検出は完璧ではありません。誤検出が含まれる可能性があります。",
+    "description": "Text for the import list confirmation message"
+  },
+  "confirmation_cancel": {
+    "message": "キャンセル",
+    "description": "Text for the confirmation cancel button"
+  },
+  "confirmation_ok": {
+    "message": "OK",
+    "description": "Text for the confirmation ok button"
+  },
+  "toast_pending": {
+    "message": "処理中...",
+    "description": "Text for the toast pending message"
+  },
+  "toast_follow_all_success": {
+    "message": "$count$ユーザーをフォローしました🎉",
+    "description": "Text for the toast follow all success message",
+    "placeholders": {
+      "count": {
+        "content": "$1"
+      }
+    }
+  },
+  "toast_block_all_success": {
+    "message": "$count$ユーザーをブロックしました🎉",
+    "description": "Text for the toast block all success message",
+    "placeholders": {
+      "count": {
+        "content": "$1"
+      }
+    }
+  },
+  "toast_import_list_success": {
+    "message": "リストをインポートしました🎉",
+    "description": "Text for the toast import list success message"
+  },
+  "toast_import_list_success_view_list": {
+    "message": "リストを表示",
+    "description": "Text for the toast import list success view list button"
+  },
+  "toast_import_list_error": {
+    "message": "リストのインポートに失敗しました: $error$",
+    "description": "Text for the toast import list error message",
+    "placeholders": {
+      "error": {
+        "content": "$1"
+      }
+    }
+  },
+  "source": {
+    "message": "検出元",
+    "description": "Text for the source"
+  },
+  "detected": {
+    "message": "結果",
+    "description": "Text for the detected"
+  },
+  "warning_user_detection": {
+    "message": "ユーザー検出は完璧ではありません。誤検出が含まれる可能性があります。",
+    "description": "Text for the warning user detection"
+  },
+  "donate_message": {
+    "message": "このツールは個人で開発しています。[支援](https://ko-fi.com/kawamataryo)いただけると、今後の開発の励みになります。",
+    "description": "Text for the donate message"
+  },
+  "follow_all": {
+    "message": "全てをフォロー",
+    "description": "Text for the follow all button"
+  },
+  "block_all": {
+    "message": "全てをブロック",
+    "description": "Text for the block all button"
+  },
+  "import_list": {
+    "message": "リストをインポート",
+    "description": "Text for the import list button"
+  },
+  "followed_users": {
+    "message": "フォロー中のユーザー",
+    "description": "Text for the followed users"
+  },
+  "same_handle_name": {
+    "message": "同じハンドル名",
+    "description": "Text for the same handle name"
+  },
+  "same_display_name": {
+    "message": "同じ表示名",
+    "description": "Text for the same display name"
+  },
+  "included_handle_in_description": {
+    "message": "ハンドル名を概要に含む",
+    "description": "Text for the included handle in description"
+  },
+  "blocked_user": {
+    "message": "ブロック中のユーザー",
+    "description": "Text for the blocked user"
+  },
+  "sidebar_detected_users": {
+    "message": "検出したユーザー数",
+    "description": "Text for the sidebar detected users"
+  },
+  "button_follow_on_bluesky": {
+    "message": "Blueskyでフォロー",
+    "description": "Text for the button follow on bluesky"
+  },
+  "button_following_on_bluesky": {
+    "message": "Blueskyでフォロー中",
+    "description": "Text for the button following on bluesky"
+  },
+  "button_unfollow_on_bluesky": {
+    "message": "Blueskyでフォロー解除",
+    "description": "Text for the button unfollow on bluesky"
+  },
+  "button_block_on_bluesky": {
+    "message": "Blueskyでブロック",
+    "description": "Text for the button block on bluesky"
+  },
+  "button_blocking_on_bluesky": {
+    "message": "Blueskyでブロック中",
+    "description": "Text for the button blocking on bluesky"
+  },
+  "button_unblock_on_bluesky": {
+    "message": "Blueskyでブロック解除",
+    "description": "Text for the button unblock on bluesky"
+  },
+  "loading": {
+    "message": "読み込み中...",
+    "description": "Text for the loading message"
+  },
+  "re_search_modal_title": {
+    "message": "再検索",
+    "description": "Text for the re search modal title"
+  }
+}
diff --git a/locales/pt/messages.json b/locales/pt/messages.json
new file mode 100644
index 0000000..cfe1d97
--- /dev/null
+++ b/locales/pt/messages.json
@@ -0,0 +1,244 @@
+{
+  "learn_more": {
+    "message": "Saiba mais",
+    "description": "Text for the learn more link"
+  },
+  "auth_factor_token": {
+    "message": "Token de autenticação",
+    "description": "Text for the auth factor token input"
+  },
+  "2fa_token_sent": {
+    "message": "Um token 2FA foi enviado para seu e-mail",
+    "description": "Text for the 2FA token sent message"
+  },
+  "find_bluesky_users": {
+    "message": "Encontrar usuários do Bluesky",
+    "description": "Text for the find bluesky users button"
+  },
+  "finding_bluesky_users": {
+    "message": "Procurando usuários do Bluesky",
+    "description": "Text for the finding bluesky users message"
+  },
+  "password": {
+    "message": "Senha",
+    "description": "Text for the password input"
+  },
+  "handle_or_email": {
+    "message": "Nome de usuário ou E-mail",
+    "description": "Text for the handle or email input"
+  },
+  "recommended_to_use_app_password": {
+    "message": "Recomendamos usar a [Senha do aplicativo](https://bsky.app/settings/app-passwords).",
+    "description": "Text for the recommended to use app password message"
+  },
+  "error_enter_password": {
+    "message": "Erro: Por favor, insira sua senha.",
+    "description": "Text for the error message when the password is not entered"
+  },
+  "error_enter_identifier": {
+    "message": "Erro: Por favor, insira seu nome de usuário ou e-mail.",
+    "description": "Text for the error message when the handle or email is not entered"
+  },
+  "error_enter_identifier_and_password": {
+    "message": "Erro: Por favor, insira seu nome de usuário ou e-mail e senha.",
+    "description": "Text for the error message when the handle or email and password are not entered"
+  },
+  "error_enter_auth_factor_token": {
+    "message": "Erro: Por favor, insira seu token de autenticação.",
+    "description": "Text for the error message when the auth factor token is not entered"
+  },
+  "error_invalid_identifier_or_password": {
+    "message": "Erro: Nome de usuário ou senha inválidos.",
+    "description": "Text for the error message when the handle or password is invalid"
+  },
+  "error_invalid_page": {
+    "message": "Erro: Página inválida. Por favor, abra a página de destino.",
+    "description": "Text for the error message when the page is invalid"
+  },
+  "error_something_went_wrong": {
+    "message": "Erro: Algo deu errado. Por favor, recarregue a página e tente novamente.",
+    "description": "Text for the error message when something went wrong"
+  },
+  "error_invalid_page_in_threads": {
+    "message": "Erro: Página inválida. Por favor, abra a visualização de seguidores ou seguindo.",
+    "description": "Text for the error message when the page is invalid in threads"
+  },
+  "scanning_users": {
+    "message": "Escaneando usuários do $service$ para encontrar usuários do Bluesky...",
+    "description": "Text for the scanning users message",
+    "placeholders": {
+      "service": {
+        "content": "$1",
+        "example": "X"
+      }
+    }
+  },
+  "detected_users": {
+    "message": "Encontramos <span class='text-4xl'>$users$</span> usuários",
+    "description": "Text for the detected users message",
+    "placeholders": {
+      "users": {
+        "content": "$1"
+      }
+    }
+  },
+  "stop_scanning_and_view_results": {
+    "message": "Parar escaneamento e ver resultados",
+    "description": "Text for the stop scanning and view results button"
+  },
+  "resume_scanning": {
+    "message": "Retomar escaneamento",
+    "description": "Text for the resume scanning button"
+  },
+  "view_detected_users": {
+    "message": "Ver usuários detectados",
+    "description": "Text for the view detected users button"
+  },
+  "follow_all_confirmation_title": {
+    "message": "Prosseguir com a execução?",
+    "description": "Text for the follow all confirmation title"
+  },
+  "follow_all_confirmation_message": {
+    "message": "A detecção de usuários não é perfeita e pode incluir falsos positivos.",
+    "description": "Text for the follow all confirmation message"
+  },
+  "import_list_confirmation_title": {
+    "message": "Prosseguir com a execução?",
+    "description": "Text for the import list confirmation title"
+  },
+  "import_list_confirmation_message": {
+    "message": "Importar uma lista criará uma nova lista e adicionará todos os usuários detectados. Este recurso é experimental e pode não funcionar como esperado.",
+    "description": "Text for the import list confirmation message"
+  },
+  "confirmation_cancel": {
+    "message": "Cancelar",
+    "description": "Text for the confirmation cancel button"
+  },
+  "confirmation_ok": {
+    "message": "OK",
+    "description": "Text for the confirmation ok button"
+  },
+  "toast_pending": {
+    "message": "Processando...",
+    "description": "Text for the toast pending message"
+  },
+  "toast_follow_all_success": {
+    "message": "Seguindo $count$ usuários 🎉",
+    "description": "Text for the toast follow all success message",
+    "placeholders": {
+      "count": {
+        "content": "$1"
+      }
+    }
+  },
+  "toast_block_all_success": {
+    "message": "Bloqueados $count$ usuários 🎉",
+    "description": "Text for the toast block all success message",
+    "placeholders": {
+      "count": {
+        "content": "$1"
+      }
+    }
+  },
+  "toast_import_list_success_view_list": {
+    "message": "Ver lista importada",
+    "description": "Text for the toast import list success view list button"
+  },
+  "toast_import_list_success": {
+    "message": "Lista importada com sucesso 🎉",
+    "description": "Text for the toast import list success message"
+  },
+  "toast_import_list_error": {
+    "message": "Falha ao importar lista: $error$",
+    "description": "Text for the toast import list error message",
+    "placeholders": {
+      "error": {
+        "content": "$1"
+      }
+    }
+  },
+  "source": {
+    "message": "Fonte",
+    "description": "Text for the source"
+  },
+  "detected": {
+    "message": "Detectado",
+    "description": "Text for the detected"
+  },
+  "warning_user_detection": {
+    "message": "A detecção de usuários não é perfeita e pode incluir falsos positivos.",
+    "description": "Text for the warning user detection"
+  },
+  "donate_message": {
+    "message": "Esta ferramenta é desenvolvida por um indivíduo. Seu [apoio](https://ko-fi.com/kawamataryo) ajuda a mantê-la e melhorá-la. Obrigado!",
+    "description": "Text for the donate message"
+  },
+  "follow_all": {
+    "message": "Seguir todos",
+    "description": "Text for the follow all button"
+  },
+  "block_all": {
+    "message": "Bloquear todos",
+    "description": "Text for the block all button"
+  },
+  "import_list": {
+    "message": "Importar lista",
+    "description": "Text for the import list button"
+  },
+  "followed_users": {
+    "message": "Usuários seguidos",
+    "description": "Text for the followed users"
+  },
+  "same_handle_name": {
+    "message": "Mesmo nome de usuário",
+    "description": "Text for the same handle name"
+  },
+  "same_display_name": {
+    "message": "Mesmo nome de exibição",
+    "description": "Text for the same display name"
+  },
+  "included_handle_in_description": {
+    "message": "Nome de usuário incluído na descrição",
+    "description": "Text for the included handle in description"
+  },
+  "blocked_user": {
+    "message": "Usuários bloqueados",
+    "description": "Text for the blocked user"
+  },
+  "sidebar_detected_users": {
+    "message": "Usuários detectados",
+    "description": "Text for the sidebar detected users"
+  },
+  "button_follow_on_bluesky": {
+    "message": "Seguir no Bluesky",
+    "description": "Text for the button follow on bluesky"
+  },
+  "button_following_on_bluesky": {
+    "message": "Seguindo no Bluesky",
+    "description": "Text for the button following on bluesky"
+  },
+  "button_unfollow_on_bluesky": {
+    "message": "Deixar de seguir no Bluesky",
+    "description": "Text for the button unfollow on bluesky"
+  },
+  "button_block_on_bluesky": {
+    "message": "Bloquear no Bluesky",
+    "description": "Text for the button block on bluesky"
+  },
+  "button_blocking_on_bluesky": {
+    "message": "Bloqueado no Bluesky",
+    "description": "Text for the button blocking on bluesky"
+  },
+  "button_unblock_on_bluesky": {
+    "message": "Desbloquear no Bluesky",
+    "description": "Text for the button unblock on bluesky"
+  },
+  "loading": {
+    "message": "Carregando...",
+    "description": "Text for the loading message"
+  },
+  "re_search_modal_title": {
+    "message": "Resultados da pesquisa",
+    "description": "Text for the re search modal title"
+  }
+}
diff --git a/locales/zh/messages.json b/locales/zh/messages.json
new file mode 100644
index 0000000..b7cb1ac
--- /dev/null
+++ b/locales/zh/messages.json
@@ -0,0 +1,244 @@
+{
+  "learn_more": {
+    "message": "了解更多",
+    "description": "Text for the learn more link"
+  },
+  "auth_factor_token": {
+    "message": "验证令牌",
+    "description": "Text for the auth factor token input"
+  },
+  "2fa_token_sent": {
+    "message": "双重验证令牌已发送至您的邮箱",
+    "description": "Text for the 2FA token sent message"
+  },
+  "find_bluesky_users": {
+    "message": "查找Bluesky用户",
+    "description": "Text for the find bluesky users button"
+  },
+  "finding_bluesky_users": {
+    "message": "正在查找Bluesky用户",
+    "description": "Text for the finding bluesky users message"
+  },
+  "password": {
+    "message": "密码",
+    "description": "Text for the password input"
+  },
+  "handle_or_email": {
+    "message": "用户名或邮箱",
+    "description": "Text for the handle or email input"
+  },
+  "recommended_to_use_app_password": {
+    "message": "建议使用[应用密码](https://bsky.app/settings/app-passwords)",
+    "description": "Text for the recommended to use app password message"
+  },
+  "error_enter_password": {
+    "message": "错误:请输入密码",
+    "description": "Text for the error message when the password is not entered"
+  },
+  "error_enter_identifier": {
+    "message": "错误:请输入用户名或邮箱",
+    "description": "Text for the error message when the handle or email is not entered"
+  },
+  "error_enter_identifier_and_password": {
+    "message": "错误:请输入用户名或邮箱和密码",
+    "description": "Text for the error message when the handle or email and password are not entered"
+  },
+  "error_enter_auth_factor_token": {
+    "message": "错误:请输入验证令牌",
+    "description": "Text for the error message when the auth factor token is not entered"
+  },
+  "error_invalid_identifier_or_password": {
+    "message": "错误:用户名或密码无效",
+    "description": "Text for the error message when the handle or password is invalid"
+  },
+  "error_invalid_page": {
+    "message": "错误:无效页面。请打开目标页面",
+    "description": "Text for the error message when the page is invalid"
+  },
+  "error_something_went_wrong": {
+    "message": "错误:出现问题。请刷新网页并重试",
+    "description": "Text for the error message when something went wrong"
+  },
+  "error_invalid_page_in_threads": {
+    "message": "错误:无效页面。请打开关注者或正在关注的视图",
+    "description": "Text for the error message when the page is invalid in threads"
+  },
+  "scanning_users": {
+    "message": "正在扫描$service$用户以查找Bluesky用户...",
+    "description": "Text for the scanning users message",
+    "placeholders": {
+      "service": {
+        "content": "$1",
+        "example": "X"
+      }
+    }
+  },
+  "detected_users": {
+    "message": "发现<span class='text-4xl'>$users$</span>位用户",
+    "description": "Text for the detected users message",
+    "placeholders": {
+      "users": {
+        "content": "$1"
+      }
+    }
+  },
+  "stop_scanning_and_view_results": {
+    "message": "停止扫描并查看结果",
+    "description": "Text for the stop scanning and view results button"
+  },
+  "resume_scanning": {
+    "message": "继续扫描",
+    "description": "Text for the resume scanning button"
+  },
+  "view_detected_users": {
+    "message": "查看检测到的用户",
+    "description": "Text for the view detected users button"
+  },
+  "follow_all_confirmation_title": {
+    "message": "是否继续执行?",
+    "description": "Text for the follow all confirmation title"
+  },
+  "follow_all_confirmation_message": {
+    "message": "用户检测并非完美,可能包含误报",
+    "description": "Text for the follow all confirmation message"
+  },
+  "import_list_confirmation_title": {
+    "message": "是否继续执行?",
+    "description": "Text for the import list confirmation title"
+  },
+  "import_list_confirmation_message": {
+    "message": "导入列表将创建一个新列表并添加所有检测到的用户。此功能为实验性功能,可能无法按预期工作",
+    "description": "Text for the import list confirmation message"
+  },
+  "confirmation_cancel": {
+    "message": "取消",
+    "description": "Text for the confirmation cancel button"
+  },
+  "confirmation_ok": {
+    "message": "确定",
+    "description": "Text for the confirmation ok button"
+  },
+  "toast_pending": {
+    "message": "处理中...",
+    "description": "Text for the toast pending message"
+  },
+  "toast_follow_all_success": {
+    "message": "已关注$count$个用户 🎉",
+    "description": "Text for the toast follow all success message",
+    "placeholders": {
+      "count": {
+        "content": "$1"
+      }
+    }
+  },
+  "toast_block_all_success": {
+    "message": "已屏蔽$count$个用户🎉",
+    "description": "Text for the toast block all success message",
+    "placeholders": {
+      "count": {
+        "content": "$1"
+      }
+    }
+  },
+  "toast_import_list_success_view_list": {
+    "message": "查看导入的列表",
+    "description": "Text for the toast import list success view list button"
+  },
+  "toast_import_list_success": {
+    "message": "列表导入成功🎉",
+    "description": "Text for the toast import list success message"
+  },
+  "toast_import_list_error": {
+    "message": "导入列表失败:$error$",
+    "description": "Text for the toast import list error message",
+    "placeholders": {
+      "error": {
+        "content": "$1"
+      }
+    }
+  },
+  "source": {
+    "message": "来源",
+    "description": "Text for the source"
+  },
+  "detected": {
+    "message": "已检测",
+    "description": "Text for the detected"
+  },
+  "warning_user_detection": {
+    "message": "用户检测并非完美,可能包含误报",
+    "description": "Text for the warning user detection"
+  },
+  "donate_message": {
+    "message": "此工具由个人开发。您的[支持](https://ko-fi.com/kawamataryo)有助于维护和改进它。",
+    "description": "Text for the donate message"
+  },
+  "follow_all": {
+    "message": "关注全部",
+    "description": "Text for the follow all button"
+  },
+  "block_all": {
+    "message": "屏蔽全部",
+    "description": "Text for the block all button"
+  },
+  "import_list": {
+    "message": "导入列表",
+    "description": "Text for the import list button"
+  },
+  "followed_users": {
+    "message": "已关注的用户",
+    "description": "Text for the followed users"
+  },
+  "same_handle_name": {
+    "message": "相同用户名",
+    "description": "Text for the same handle name"
+  },
+  "same_display_name": {
+    "message": "相同显示名称",
+    "description": "Text for the same display name"
+  },
+  "included_handle_in_description": {
+    "message": "简介中包含用户名",
+    "description": "Text for the included handle in description"
+  },
+  "blocked_user": {
+    "message": "已屏蔽的用户",
+    "description": "Text for the blocked user"
+  },
+  "sidebar_detected_users": {
+    "message": "检测到的用户",
+    "description": "Text for the sidebar detected users"
+  },
+  "button_follow_on_bluesky": {
+    "message": "在Bluesky上关注",
+    "description": "Text for the button follow on bluesky"
+  },
+  "button_following_on_bluesky": {
+    "message": "在Bluesky上关注中",
+    "description": "Text for the button following on bluesky"
+  },
+  "button_unfollow_on_bluesky": {
+    "message": "在Bluesky上取消关注",
+    "description": "Text for the button unfollow on bluesky"
+  },
+  "button_block_on_bluesky": {
+    "message": "在Bluesky上屏蔽",
+    "description": "Text for the button block on bluesky"
+  },
+  "button_blocking_on_bluesky": {
+    "message": "在Bluesky上屏蔽中",
+    "description": "Text for the button blocking on bluesky"
+  },
+  "button_unblock_on_bluesky": {
+    "message": "在Bluesky上取消屏蔽",
+    "description": "Text for the button unblock on bluesky"
+  },
+  "loading": {
+    "message": "加载中...",
+    "description": "Text for the loading message"
+  },
+  "re_search_modal_title": {
+    "message": "搜索结果",
+    "description": "Text for the re search modal title"
+  }
+}
diff --git a/package-lock.json b/package-lock.json
index cdf5f19..2ddc739 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "sky-follower-bridge",
-  "version": "1.5.0",
+  "version": "2.1.0",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "sky-follower-bridge",
-      "version": "1.5.0",
+      "version": "2.1.0",
       "dependencies": {
         "@atproto/api": "^0.13.12",
         "@changesets/cli": "^2.27.1",
diff --git a/package.json b/package.json
index 05dbb05..1ee729d 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
 {
   "name": "sky-follower-bridge",
   "displayName": "Sky Follower Bridge",
-  "version": "2.0.1",
+  "version": "2.1.0",
   "description": "Instantly find and follow the same users from your Twitter follows on Bluesky.",
   "author": "kawamataryou",
   "scripts": {
@@ -64,6 +64,7 @@
     "typescript": "5.3.3"
   },
   "manifest": {
+    "default_locale": "en",
     "host_permissions": [
       "https://bsky.social/*",
       "https://twitter.com/*",
diff --git a/src/components/ReSearchModal.tsx b/src/components/ReSearchModal.tsx
index 41f62aa..3491185 100644
--- a/src/components/ReSearchModal.tsx
+++ b/src/components/ReSearchModal.tsx
@@ -26,12 +26,14 @@ const ReSearchModal: React.FC<ReSearchModalProps> = ({
 }) => {
   return (
     <Modal open={open} width={600} onClose={onClose}>
-      <h2 className="text-lg font-bold text-center py-2">Search Results</h2>
+      <h2 className="text-lg font-bold text-center py-2">
+        {chrome.i18n.getMessage("re_search_modal_title")}
+      </h2>
       {reSearchResults.users.length === 0 && (
         <div className="text-center flex justify-center items-center flex-col gap-4 mt-5">
           <span className="loading loading-spinner loading-lg" />
           <div className="text-center flex justify-center items-center text-sm">
-            Loading...
+            {chrome.i18n.getMessage("loading")}
           </div>
         </div>
       )}
diff --git a/src/contents/App.tsx b/src/contents/App.tsx
index 247740a..5608279 100644
--- a/src/contents/App.tsx
+++ b/src/contents/App.tsx
@@ -108,12 +108,18 @@ const App = () => {
         <div className="flex flex-col gap-2 items-center">
           {loading && (
             <p className="text-lg font-bold">
-              Scanning {serviceName} users to find bsky users...
+              {chrome.i18n.getMessage("scanning_users", [serviceName])}
             </p>
           )}
-          <p className="text-2xl font-bold">
-            Detected <span className="text-4xl">{users.length}</span> users
-          </p>
+          <p
+            className="text-2xl font-bold"
+            // biome-ignore lint/security/noDangerouslySetInnerHtml: <explanation>
+            dangerouslySetInnerHTML={{
+              __html: chrome.i18n.getMessage("detected_users", [
+                users.length.toString(),
+              ]),
+            }}
+          />
           {errorMessage && <AlertError>{errorMessage}</AlertError>}
           {loading && (
             <>
@@ -122,7 +128,7 @@ const App = () => {
                 className="btn btn-primary mt-5 btn-ghost"
                 onClick={stopAndShowDetectedUsers}
               >
-                Stop Scanning and View Results
+                {chrome.i18n.getMessage("stop_scanning_and_view_results")}
               </button>
               <LoadingCards />
             </>
@@ -133,7 +139,7 @@ const App = () => {
               className="btn btn-primary mt-5"
               onClick={restart}
             >
-              Resume Scanning
+              {chrome.i18n.getMessage("resume_scanning")}
             </button>
           )}
           {!loading && isBottomReached && (
@@ -143,14 +149,14 @@ const App = () => {
                 className="btn btn-primary mt-5"
                 onClick={openOptionPage}
               >
-                View Detected Users
+                {chrome.i18n.getMessage("view_detected_users")}
               </button>
               <button
                 type="button"
                 className="btn btn-primary mt-5 btn-ghost"
                 onClick={restart}
               >
-                Resume Scanning
+                {chrome.i18n.getMessage("resume_scanning")}
               </button>
             </div>
           )}
diff --git a/src/lib/components/AsyncButton.tsx b/src/lib/components/AsyncButton.tsx
index 351f692..9c7362d 100644
--- a/src/lib/components/AsyncButton.tsx
+++ b/src/lib/components/AsyncButton.tsx
@@ -22,7 +22,7 @@ const AsyncButton = ({ onClick, label, className }: Props) => {
       onClick={handleClick}
       disabled={loading}
     >
-      {loading ? "Processing..." : label}
+      {loading ? chrome.i18n.getMessage("loading") : label}
     </button>
   );
 };
diff --git a/src/lib/components/DetectedUserListItem.tsx b/src/lib/components/DetectedUserListItem.tsx
index 46a06de..54cda82 100644
--- a/src/lib/components/DetectedUserListItem.tsx
+++ b/src/lib/components/DetectedUserListItem.tsx
@@ -29,16 +29,16 @@ const DetectedUserListItem = ({
       match(actionMode)
         .with(ACTION_MODE.FOLLOW, ACTION_MODE.IMPORT_LIST, () => {
           const follow = {
-            label: "Follow on Bluesky",
+            label: chrome.i18n.getMessage("button_follow_on_bluesky"),
             class: "btn-primary",
           };
           const following = {
-            label: "Following on Bluesky",
+            label: chrome.i18n.getMessage("button_following_on_bluesky"),
             class:
               "btn-outline hover:bg-transparent hover:border hover:bg-transparent hover:text-base-content",
           };
           const unfollow = {
-            label: "Unfollow on Bluesky",
+            label: chrome.i18n.getMessage("button_unfollow_on_bluesky"),
             class:
               "text-red-500 hover:bg-transparent hover:border hover:border-red-500",
           };
@@ -52,16 +52,16 @@ const DetectedUserListItem = ({
         })
         .with(ACTION_MODE.BLOCK, () => {
           const block = {
-            label: "Block on Bluesky",
+            label: chrome.i18n.getMessage("button_block_on_bluesky"),
             class: "btn-primary",
           };
           const blocking = {
-            label: "Blocking on Bluesky",
+            label: chrome.i18n.getMessage("button_blocking_on_bluesky"),
             class:
               "btn-outline hover:bg-transparent hover:border hover:bg-transparent hover:text-base-content",
           };
           const unblock = {
-            label: "Unblock on Bluesky",
+            label: chrome.i18n.getMessage("button_unblock_on_bluesky"),
             class:
               "text-red-500 hover:bg-transparent hover:border hover:border-red-500",
           };
@@ -106,12 +106,12 @@ const DetectedUserListItem = ({
     <div>
       <div className={`w-full border-l-8 border-${matchTypeColor}`}>
         <div
-          className={`w-full border-t border-gray-500 text-${matchTypeColor} grid grid-cols-[22%_1fr]`}
+          className={`w-full border-t border-gray-500 text-${matchTypeColor} grid grid-cols-[22%_1fr] text-xs`}
         >
-          <div className="px-3 bg-slate-100 dark:bg-slate-800">
+          <div className="px-3 bg-slate-100 dark:bg-slate-800" />
+          <div className="px-3">
             {MATCH_TYPE_LABEL_AND_COLOR[user.matchType].label}
           </div>
-          <div className="px-3" />
         </div>
         <div className="bg-base-100 w-full relative grid grid-cols-[22%_1fr] gap-5">
           <DetectedUserSource user={user} />
diff --git a/src/lib/components/Sidebar.tsx b/src/lib/components/Sidebar.tsx
index 7116448..cc9c289 100644
--- a/src/lib/components/Sidebar.tsx
+++ b/src/lib/components/Sidebar.tsx
@@ -1,5 +1,6 @@
 import React from "react";
 import { match } from "ts-pattern";
+import { getMessageWithLink } from "~lib/utils";
 import type { MatchType, MatchTypeFilterValue } from "../../types";
 import {
   ACTION_MODE,
@@ -78,18 +79,19 @@ const Sidebar = ({
               </svg>
             </div>
             <div className="stat-title text-lg text-base-content font-bold">
-              Detected users
+              {chrome.i18n.getMessage("sidebar_detected_users")}
             </div>
             <div className="stat-value text-base-content">{detectedCount}</div>
             <div className="stat-desc">
-              Same handle name: {matchTypeStats[BSKY_USER_MATCH_TYPE.HANDLE]}
+              {chrome.i18n.getMessage("same_handle_name")}:{" "}
+              {matchTypeStats[BSKY_USER_MATCH_TYPE.HANDLE]}
             </div>
             <div className="stat-desc">
-              Same display name:{" "}
+              {chrome.i18n.getMessage("same_display_name")}:{" "}
               {matchTypeStats[BSKY_USER_MATCH_TYPE.DISPLAY_NAME]}
             </div>
             <div className="stat-desc">
-              Included handle in description:{" "}
+              {chrome.i18n.getMessage("included_handle_in_description")}:{" "}
               {matchTypeStats[BSKY_USER_MATCH_TYPE.DESCRIPTION]}
             </div>
           </div>
@@ -118,7 +120,7 @@ const Sidebar = ({
               <span className="text-sm">
                 {key === BSKY_USER_MATCH_TYPE.FOLLOWING &&
                 actionMode === ACTION_MODE.BLOCK
-                  ? "Blocked users"
+                  ? chrome.i18n.getMessage("blocked_user")
                   : MATCH_TYPE_LABEL_AND_COLOR[key].label}
               </span>
               <input
@@ -151,30 +153,39 @@ const Sidebar = ({
         </div>
         {match(actionMode)
           .with(ACTION_MODE.FOLLOW, () => (
-            <AsyncButton onClick={followAll} label="Follow All" />
+            <AsyncButton
+              onClick={followAll}
+              label={chrome.i18n.getMessage("follow_all")}
+            />
           ))
           .with(ACTION_MODE.BLOCK, () => (
-            <AsyncButton onClick={blockAll} label="Block All" />
+            <AsyncButton
+              onClick={blockAll}
+              label={chrome.i18n.getMessage("block_all")}
+            />
           ))
           .with(ACTION_MODE.IMPORT_LIST, () => (
-            <AsyncButton onClick={importList} label="Import List" />
+            <AsyncButton
+              onClick={importList}
+              label={chrome.i18n.getMessage("import_list")}
+            />
           ))
           .otherwise(() => null)}
         <p className="text-xs">
-          ⚠️ User detection is not perfect and may include false positives.
+          ⚠️ {chrome.i18n.getMessage("warning_user_detection")}
         </p>
       </div>
       <div className="mt-auto">
         <div className="divider" />
-        <p className="mb-2">
-          If you find this tool helpful, I'd appreciate{" "}
-          <a href="https://ko-fi.com/X8X315UWFN" className="link">
-            your support
-          </a>{" "}
-          to help me maintain and improve it ☕
-        </p>
+        <p
+          className="mb-2 text-xs"
+          // biome-ignore lint/security/noDangerouslySetInnerHtml: <explanation>
+          dangerouslySetInnerHTML={{
+            __html: getMessageWithLink("donate_message"),
+          }}
+        />
         <a
-          href="https://ko-fi.com/X8X315UWFN"
+          href="https://ko-fi.com/kawamataryo"
           target="_blank"
           rel="noreferrer"
           style={{ display: "inline-block" }}
diff --git a/src/lib/constants.ts b/src/lib/constants.ts
index 083ff8d..e661463 100644
--- a/src/lib/constants.ts
+++ b/src/lib/constants.ts
@@ -75,19 +75,19 @@ export const MAX_RELOAD_COUNT = 1;
 
 export const MATCH_TYPE_LABEL_AND_COLOR = {
   [BSKY_USER_MATCH_TYPE.HANDLE]: {
-    label: "Same handle name",
+    label: chrome.i18n.getMessage("same_handle_name"),
     color: "info",
   },
   [BSKY_USER_MATCH_TYPE.DISPLAY_NAME]: {
-    label: "Same display name",
+    label: chrome.i18n.getMessage("same_display_name"),
     color: "warning",
   },
   [BSKY_USER_MATCH_TYPE.DESCRIPTION]: {
-    label: "Included handle in description",
+    label: chrome.i18n.getMessage("included_handle_in_description"),
     color: "secondary",
   },
   [BSKY_USER_MATCH_TYPE.FOLLOWING]: {
-    label: "Followed users",
+    label: chrome.i18n.getMessage("followed_users"),
     color: "success",
   },
 };
@@ -95,6 +95,9 @@ export const MATCH_TYPE_LABEL_AND_COLOR = {
 export const AUTH_FACTOR_TOKEN_REQUIRED_ERROR_MESSAGE =
   "AuthFactorTokenRequiredError";
 
+export const INVALID_IDENTIFIER_OR_PASSWORD_ERROR_MESSAGE =
+  "Invalid identifier or password";
+
 export const RATE_LIMIT_ERROR_MESSAGE = "Rate limit";
 
 export const DOCUMENT_LINK = {
diff --git a/src/lib/services/threadsService.ts b/src/lib/services/threadsService.ts
index 189b2bf..d21ce00 100644
--- a/src/lib/services/threadsService.ts
+++ b/src/lib/services/threadsService.ts
@@ -28,10 +28,7 @@ export class ThreadsService extends AbstractService {
       '[role="dialog"] [role="tab"]>[role="button"]',
     );
     if (!isTargetPage) {
-      return [
-        false,
-        "Invalid page. please open the following or followers view.",
-      ];
+      return [false, chrome.i18n.getMessage("error_invalid_page_in_threads")];
     }
     return [true, ""];
   }
diff --git a/src/lib/utils.ts b/src/lib/utils.ts
index a04ab3e..3fe9d92 100644
--- a/src/lib/utils.ts
+++ b/src/lib/utils.ts
@@ -36,3 +36,21 @@ export const findFirstScrollableElements = (
 
   return scrollableElements[0] ?? null;
 };
+
+export const getMessageWithLink = (
+  key: string,
+  placeholders: string[] = [],
+) => {
+  const linkPattern = /\[(.*?)\]\((.*?)\)/g;
+  let message = chrome.i18n.getMessage(key, placeholders);
+  const links = message.matchAll(linkPattern);
+  for (const link of links) {
+    const [fullMatch, text, url] = link;
+    message = message.replace(
+      fullMatch,
+      `<a href="${url}" target="_blank" class="link" rel="noreferrer">${text}</a>`,
+    );
+  }
+
+  return message;
+};
diff --git a/src/options.tsx b/src/options.tsx
index ce1131e..2c81109 100644
--- a/src/options.tsx
+++ b/src/options.tsx
@@ -31,22 +31,20 @@ const Option = () => {
     confirm: followAllConfirm,
     ConfirmationDialog: FollowAllConfirmationDialog,
   } = useConfirm({
-    title: "Proceed with Execution?",
-    message:
-      "User detection is not perfect and may include false positives. Do you still want to proceed?",
-    cancelText: "Cancel",
-    okText: "OK",
+    title: chrome.i18n.getMessage("follow_all_confirmation_title"),
+    message: chrome.i18n.getMessage("follow_all_confirmation_message"),
+    cancelText: chrome.i18n.getMessage("confirmation_cancel"),
+    okText: chrome.i18n.getMessage("confirmation_ok"),
   });
 
   const {
     confirm: importListConfirm,
     ConfirmationDialog: ImportListConfirmationDialog,
   } = useConfirm({
-    title: "Proceed with Execution?",
-    message:
-      "Importing a list will create a new list and add all detected users to it. This feature is experimental and may not work as expected. Do you still want to proceed?",
-    cancelText: "Cancel",
-    okText: "OK",
+    title: chrome.i18n.getMessage("import_list_confirmation_title"),
+    message: chrome.i18n.getMessage("import_list_confirmation_message"),
+    cancelText: chrome.i18n.getMessage("confirmation_cancel"),
+    okText: chrome.i18n.getMessage("confirmation_ok"),
   });
 
   const handleFollowAll = async () => {
@@ -54,10 +52,16 @@ const Option = () => {
       return;
     }
     toast.promise(followAll, {
-      pending: "Processing...",
+      pending: chrome.i18n.getMessage("toast_pending"),
       success: {
         render({ data }) {
-          return <span className="font-bold">Followed {data} users🎉</span>;
+          return (
+            <span className="font-bold">
+              {chrome.i18n.getMessage("toast_follow_all_success", [
+                data.toString(),
+              ])}
+            </span>
+          );
         },
       },
     });
@@ -68,10 +72,16 @@ const Option = () => {
       return;
     }
     toast.promise(blockAll, {
-      pending: "Processing...",
+      pending: chrome.i18n.getMessage("toast_pending"),
       success: {
         render({ data }) {
-          return <span className="font-bold">Blocked {data} users🎉</span>;
+          return (
+            <span className="font-bold">
+              {chrome.i18n.getMessage("toast_block_all_success", [
+                data.toString(),
+              ])}
+            </span>
+          );
         },
       },
     });
@@ -82,15 +92,17 @@ const Option = () => {
       return;
     }
     toast.promise(importList, {
-      pending: "Processing...",
+      pending: chrome.i18n.getMessage("toast_pending"),
       success: {
         render({ data }) {
           return (
             <>
-              <span className="font-bold">List imported successfully🎉</span>
+              <span className="font-bold">
+                {chrome.i18n.getMessage("toast_import_list_success")}
+              </span>
               <br />
               <a href={data} target="_blank" rel="noreferrer" className="link">
-                View Imported List
+                {chrome.i18n.getMessage("toast_import_list_success_view_list")}
               </a>
             </>
           );
@@ -98,8 +110,9 @@ const Option = () => {
       },
       error: {
         render({ data }) {
-          console.log(data);
-          return `Failed to import list: ${data}`;
+          return chrome.i18n.getMessage("toast_import_list_error", [
+            data as string,
+          ]);
         },
       },
     });
@@ -153,8 +166,12 @@ const Option = () => {
         </div>
         <div className="flex-1 ml-80 p-6 pt-0 overflow-y-auto">
           <div className="grid grid-cols-[22%_1fr] sticky top-0 z-10 bg-base-100 border-b-[1px] border-gray-500">
-            <h2 className="text-lg font-bold text-center py-2">Source</h2>
-            <h2 className="text-lg font-bold text-center py-2">Detected</h2>
+            <h2 className="text-lg font-bold text-center py-2">
+              {chrome.i18n.getMessage("source")}
+            </h2>
+            <h2 className="text-lg font-bold text-center py-2">
+              {chrome.i18n.getMessage("detected")}
+            </h2>
           </div>
           <div className="flex flex-col border-b-[1px] border-gray-500">
             {filteredUsers.map((user) => (
diff --git a/src/popup.tsx b/src/popup.tsx
index 102495c..3eac655 100644
--- a/src/popup.tsx
+++ b/src/popup.tsx
@@ -10,6 +10,7 @@ import {
   AUTH_FACTOR_TOKEN_REQUIRED_ERROR_MESSAGE,
   BSKY_DOMAIN,
   DOCUMENT_LINK,
+  INVALID_IDENTIFIER_OR_PASSWORD_ERROR_MESSAGE,
   MAX_RELOAD_COUNT,
   MESSAGE_NAMES,
   MESSAGE_TYPE,
@@ -17,6 +18,7 @@ import {
   STORAGE_KEYS,
   TARGET_URLS_REGEX,
 } from "~lib/constants";
+import { getMessageWithLink } from "~lib/utils";
 
 function IndexPopup() {
   const [isLoading, setIsLoading] = useState(false);
@@ -81,19 +83,21 @@ function IndexPopup() {
 
   const validateForm = () => {
     if (!password && !identifier) {
-      setErrorMessage("Error: Please enter your password and identifier.");
+      setErrorMessage(
+        chrome.i18n.getMessage("error_enter_identifier_and_password"),
+      );
       return false;
     }
     if (!password) {
-      setErrorMessage("Error: Please enter your password.");
+      setErrorMessage(chrome.i18n.getMessage("error_enter_password"));
       return false;
     }
     if (!identifier) {
-      setErrorMessage("Error: Please enter your identifier.");
+      setErrorMessage(chrome.i18n.getMessage("error_enter_identifier"));
       return false;
     }
     if (isShowAuthFactorTokenInput && !authFactorToken) {
-      setErrorMessage("Error: Please enter your auth factor token.");
+      setErrorMessage(chrome.i18n.getMessage("error_enter_auth_factor_token"));
       return false;
     }
     return true;
@@ -115,7 +119,7 @@ function IndexPopup() {
 
     if (!Object.values(TARGET_URLS_REGEX).some((r) => r.test(currentUrl))) {
       setErrorMessage(
-        "Error: Invalid page. please open the target page.",
+        chrome.i18n.getMessage("error_invalid_page"),
         DOCUMENT_LINK.PAGE_ERROR,
       );
       return;
@@ -166,6 +170,13 @@ function IndexPopup() {
           await saveShowAuthFactorTokenInputToStorage(true);
         } else if (error.message.includes(RATE_LIMIT_ERROR_MESSAGE)) {
           setErrorMessage(error.message, DOCUMENT_LINK.RATE_LIMIT_ERROR);
+        } else if (
+          error.message.includes(INVALID_IDENTIFIER_OR_PASSWORD_ERROR_MESSAGE)
+        ) {
+          setErrorMessage(
+            chrome.i18n.getMessage("error_invalid_identifier_or_password"),
+            DOCUMENT_LINK.LOGIN_ERROR,
+          );
         } else {
           setErrorMessage(error.message, DOCUMENT_LINK.LOGIN_ERROR);
         }
@@ -202,7 +213,7 @@ function IndexPopup() {
         await searchBskyUser();
       } else {
         setErrorMessage(
-          "Error: Something went wrong. Please reload the web page and try again.",
+          chrome.i18n.getMessage("error_something_went_wrong"),
           DOCUMENT_LINK.OTHER_ERROR,
         );
       }
@@ -258,7 +269,7 @@ function IndexPopup() {
                 d="M17.982 18.725A7.488 7.488 0 0012 15.75a7.488 7.488 0 00-5.982 2.975m11.963 0a9 9 0 10-11.963 0m11.963 0A8.966 8.966 0 0112 21a8.966 8.966 0 01-5.982-2.275M15 9.75a3 3 0 11-6 0 3 3 0 016 0z"
               />
             </svg>
-            Handle or Email
+            {chrome.i18n.getMessage("handle_or_email")}
           </div>
           <input
             type="text"
@@ -286,20 +297,17 @@ function IndexPopup() {
               />
             </svg>
             <p>
-              Password
+              {chrome.i18n.getMessage("password")}
               <br />
             </p>
           </div>
           <span className="text-xs">
-            We recommend using the{" "}
-            <a
-              href="https://bsky.app/settings/app-passwords"
-              target="_blank"
-              rel="noreferrer"
-              className="link"
-            >
-              App Password.
-            </a>
+            <span
+              // biome-ignore lint/security/noDangerouslySetInnerHtml: <explanation>
+              dangerouslySetInnerHTML={{
+                __html: getMessageWithLink("recommended_to_use_app_password"),
+              }}
+            />
           </span>
           <input
             type="password"
@@ -322,8 +330,8 @@ function IndexPopup() {
                 className="w-4 h-4"
               >
                 <path
-                  stroke-linecap="round"
-                  stroke-linejoin="round"
+                  strokeLinecap="round"
+                  strokeLinejoin="round"
                   d="M16.5 6v.75m0 3v.75m0 3v.75m0 3V18m-9-5.25h5.25M7.5 15h3M3.375 5.25c-.621 0-1.125.504-1.125 1.125v3.026a2.999 2.999 0 0 1 0 5.198v3.026c0 .621.504 1.125 1.125 1.125h17.25c.621 0 1.125-.504 1.125-1.125v-3.026a2.999 2.999 0 0 1 0-5.198V6.375c0-.621-.504-1.125-1.125-1.125H3.375Z"
                 />
               </svg>
@@ -350,10 +358,12 @@ function IndexPopup() {
           disabled={isLoading}
         >
           {isLoading && <span className="w-4 loading loading-spinner" />}
-          {isLoading ? "Finding Bluesky Users" : "Find Bluesky Users"}
+          {isLoading
+            ? chrome.i18n.getMessage("finding_bluesky_users")
+            : chrome.i18n.getMessage("find_bluesky_users")}
         </button>
         {isShowErrorMessage && (
-          <div className="flex gap-2 items-center text-red-600 border border-red-600 p-2 rounded-md mt-2">
+          <div className="flex gap-2 items-center text-red-600 border border-red-600 p-2 rounded-md mt-2 text-xs">
             <svg
               xmlns="http://www.w3.org/2000/svg"
               className="stroke-current flex-shrink-0 h-6 w-6"
@@ -376,10 +386,9 @@ function IndexPopup() {
                   rel="noreferrer"
                   className="link ml-2"
                 >
-                  Learn more
+                  {chrome.i18n.getMessage("learn_more")}
                 </a>
               )}
-              .
             </span>
           </div>
         )}
diff --git a/tailwind.config.js b/tailwind.config.js
index e343277..9f935f7 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -8,7 +8,13 @@ module.exports = {
   safelist: [
     {
       pattern: /(badge|border|bg|text|border-b)-(info|warning|secondary|neutral|success)/,
-    }
+    },
+    {
+      pattern: /(link)/,
+    },
+    {
+      pattern: /(text-4xl)/,
+    },
   ],
   daisyui: {
     themes: [