feat: add i18n

This commit is contained in:
kawamataryo
2024-12-07 22:40:33 +09:00
parent 3c03d99a16
commit fc91c4d3a2
21 changed files with 1882 additions and 93 deletions

View File

@@ -22,7 +22,7 @@ const AsyncButton = ({ onClick, label, className }: Props) => {
onClick={handleClick}
disabled={loading}
>
{loading ? "Processing..." : label}
{loading ? chrome.i18n.getMessage("loading") : label}
</button>
);
};

View File

@@ -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} />

View File

@@ -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" }}

View File

@@ -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 = {

View File

@@ -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, ""];
}

View File

@@ -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;
};