mirror of
https://github.com/aljazceru/rabbit.git
synced 2025-12-18 22:44:26 +01:00
feat: i18n
This commit is contained in:
@@ -13,6 +13,7 @@ import UserNameDisplay from '@/components/UserDisplayName';
|
|||||||
import useConfig from '@/core/useConfig';
|
import useConfig from '@/core/useConfig';
|
||||||
import useEmojiComplete from '@/hooks/useEmojiComplete';
|
import useEmojiComplete from '@/hooks/useEmojiComplete';
|
||||||
import usePersistStatus from '@/hooks/usePersistStatus';
|
import usePersistStatus from '@/hooks/usePersistStatus';
|
||||||
|
import { useTranslation } from '@/i18n/useTranslation';
|
||||||
import { textNote } from '@/nostr/event';
|
import { textNote } from '@/nostr/event';
|
||||||
import parseTextNote, { ParsedTextNote } from '@/nostr/parseTextNote';
|
import parseTextNote, { ParsedTextNote } from '@/nostr/parseTextNote';
|
||||||
import useCommands, { PublishTextNoteParams } from '@/nostr/useCommands';
|
import useCommands, { PublishTextNoteParams } from '@/nostr/useCommands';
|
||||||
@@ -28,16 +29,6 @@ type NotePostFormProps = {
|
|||||||
textAreaRef?: (textAreaRef: HTMLTextAreaElement) => void;
|
textAreaRef?: (textAreaRef: HTMLTextAreaElement) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const placeholder = (mode: NotePostFormProps['mode']) => {
|
|
||||||
switch (mode) {
|
|
||||||
case 'reply':
|
|
||||||
return '返信を投稿';
|
|
||||||
case 'normal':
|
|
||||||
default:
|
|
||||||
return 'いまどうしてる?';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const extract = (parsed: ParsedTextNote) => {
|
const extract = (parsed: ParsedTextNote) => {
|
||||||
const hashtags: string[] = [];
|
const hashtags: string[] = [];
|
||||||
const pubkeyReferences: string[] = [];
|
const pubkeyReferences: string[] = [];
|
||||||
@@ -86,6 +77,7 @@ const format = (parsed: ParsedTextNote) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const NotePostForm: Component<NotePostFormProps> = (props) => {
|
const NotePostForm: Component<NotePostFormProps> = (props) => {
|
||||||
|
const i18n = useTranslation();
|
||||||
let textAreaRef: HTMLTextAreaElement | undefined;
|
let textAreaRef: HTMLTextAreaElement | undefined;
|
||||||
let fileInputRef: HTMLInputElement | undefined;
|
let fileInputRef: HTMLInputElement | undefined;
|
||||||
|
|
||||||
@@ -108,6 +100,16 @@ const NotePostForm: Component<NotePostFormProps> = (props) => {
|
|||||||
props.onClose();
|
props.onClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const placeholder = (mode: NotePostFormProps['mode']) => {
|
||||||
|
switch (mode) {
|
||||||
|
case 'reply':
|
||||||
|
return i18n()('posting.placeholderReply');
|
||||||
|
case 'normal':
|
||||||
|
default:
|
||||||
|
return i18n()('posting.placeholder');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const { config, getEmoji } = useConfig();
|
const { config, getEmoji } = useConfig();
|
||||||
const { persistStatus, didAgreeToToS, agreeToToS } = usePersistStatus();
|
const { persistStatus, didAgreeToToS, agreeToToS } = usePersistStatus();
|
||||||
const getPubkey = usePubkey();
|
const getPubkey = usePubkey();
|
||||||
@@ -152,7 +154,7 @@ const NotePostForm: Component<NotePostFormProps> = (props) => {
|
|||||||
|
|
||||||
if (failed.length > 0) {
|
if (failed.length > 0) {
|
||||||
const filenames = failed.map((f) => f.name).join(', ');
|
const filenames = failed.map((f) => f.name).join(', ');
|
||||||
window.alert(`ファイルのアップロードに失敗しました: ${filenames}`);
|
window.alert(i18n()('posting.failedToUploadFile', { filenames }));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -194,7 +196,7 @@ const NotePostForm: Component<NotePostFormProps> = (props) => {
|
|||||||
if (publishTextNoteMutation.isLoading) return;
|
if (publishTextNoteMutation.isLoading) return;
|
||||||
|
|
||||||
if (/nsec1[0-9a-zA-Z]+/.test(text())) {
|
if (/nsec1[0-9a-zA-Z]+/.test(text())) {
|
||||||
window.alert('投稿に秘密鍵(nsec)を含めることはできません。');
|
window.alert(i18n()('posting.forbiddenToIncludeNsec'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -337,6 +339,7 @@ const NotePostForm: Component<NotePostFormProps> = (props) => {
|
|||||||
<div class="p-1">
|
<div class="p-1">
|
||||||
<Show when={props.replyTo != null}>
|
<Show when={props.replyTo != null}>
|
||||||
<div>
|
<div>
|
||||||
|
{i18n()('posting.replyToPre')}
|
||||||
<For each={notifyPubkeys()}>
|
<For each={notifyPubkeys()}>
|
||||||
{(pubkey, index) => (
|
{(pubkey, index) => (
|
||||||
<>
|
<>
|
||||||
@@ -345,7 +348,7 @@ const NotePostForm: Component<NotePostFormProps> = (props) => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</For>
|
</For>
|
||||||
に返信
|
{i18n()('posting.replyToPost')}
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
<form class="flex flex-col gap-1" onSubmit={handleSubmit}>
|
<form class="flex flex-col gap-1" onSubmit={handleSubmit}>
|
||||||
@@ -353,7 +356,7 @@ const NotePostForm: Component<NotePostFormProps> = (props) => {
|
|||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
class="rounded"
|
class="rounded"
|
||||||
placeholder="警告の理由"
|
placeholder={i18n()('posting.contentWarningReason')}
|
||||||
maxLength={32}
|
maxLength={32}
|
||||||
onInput={(ev) => setContentWarningReason(ev.currentTarget.value)}
|
onInput={(ev) => setContentWarningReason(ev.currentTarget.value)}
|
||||||
value={contentWarningReason()}
|
value={contentWarningReason()}
|
||||||
@@ -400,8 +403,8 @@ const NotePostForm: Component<NotePostFormProps> = (props) => {
|
|||||||
'w-7': mode() === 'reply',
|
'w-7': mode() === 'reply',
|
||||||
}}
|
}}
|
||||||
type="button"
|
type="button"
|
||||||
aria-label="コンテンツ警告を設定"
|
aria-label={i18n()('posting.contentWarning')}
|
||||||
title="コンテンツ警告を設定"
|
title={i18n()('posting.contentWarning')}
|
||||||
onClick={() => setContentWarning((e) => !e)}
|
onClick={() => setContentWarning((e) => !e)}
|
||||||
>
|
>
|
||||||
<span>CW</span>
|
<span>CW</span>
|
||||||
@@ -417,8 +420,8 @@ const NotePostForm: Component<NotePostFormProps> = (props) => {
|
|||||||
'w-7': mode() === 'reply',
|
'w-7': mode() === 'reply',
|
||||||
}}
|
}}
|
||||||
type="button"
|
type="button"
|
||||||
title="画像を投稿"
|
title={i18n()('posting.uploadImage')}
|
||||||
aria-label="画像を投稿"
|
aria-label={i18n()('posting.uploadImage')}
|
||||||
disabled={fileUploadDisabled()}
|
disabled={fileUploadDisabled()}
|
||||||
onClick={() => fileInputRef?.click()}
|
onClick={() => fileInputRef?.click()}
|
||||||
>
|
>
|
||||||
@@ -435,8 +438,8 @@ const NotePostForm: Component<NotePostFormProps> = (props) => {
|
|||||||
'w-7': mode() === 'reply',
|
'w-7': mode() === 'reply',
|
||||||
}}
|
}}
|
||||||
type="submit"
|
type="submit"
|
||||||
aria-label="投稿"
|
aria-label={i18n()('posting.submit')}
|
||||||
title="投稿"
|
title={i18n()('posting.submit')}
|
||||||
disabled={submitDisabled()}
|
disabled={submitDisabled()}
|
||||||
>
|
>
|
||||||
<PaperAirplane />
|
<PaperAirplane />
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import EventDisplay from '@/components/event/EventDisplay';
|
|||||||
import { type EventDisplayProps } from '@/components/event/EventDisplay';
|
import { type EventDisplayProps } from '@/components/event/EventDisplay';
|
||||||
import EventLink from '@/components/EventLink';
|
import EventLink from '@/components/EventLink';
|
||||||
import useConfig from '@/core/useConfig';
|
import useConfig from '@/core/useConfig';
|
||||||
|
import { useTranslation } from '@/i18n/useTranslation';
|
||||||
import useEvent from '@/nostr/useEvent';
|
import useEvent from '@/nostr/useEvent';
|
||||||
import ensureNonNull from '@/utils/ensureNonNull';
|
import ensureNonNull from '@/utils/ensureNonNull';
|
||||||
|
|
||||||
@@ -13,6 +14,7 @@ type EventDisplayByIdProps = Omit<EventDisplayProps, 'event'> & {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const EventDisplayById: Component<EventDisplayByIdProps> = (props) => {
|
const EventDisplayById: Component<EventDisplayByIdProps> = (props) => {
|
||||||
|
const i18n = useTranslation();
|
||||||
const [localProps, restProps] = splitProps(props, ['eventId']);
|
const [localProps, restProps] = splitProps(props, ['eventId']);
|
||||||
const { shouldMuteEvent } = useConfig();
|
const { shouldMuteEvent } = useConfig();
|
||||||
|
|
||||||
@@ -31,7 +33,7 @@ const EventDisplayById: Component<EventDisplayByIdProps> = (props) => {
|
|||||||
<Switch
|
<Switch
|
||||||
fallback={
|
fallback={
|
||||||
<span>
|
<span>
|
||||||
投稿が見つかりません
|
{i18n()('post.failedToFetchEvent')}
|
||||||
{props.eventId}
|
{props.eventId}
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
@@ -43,8 +45,7 @@ const EventDisplayById: Component<EventDisplayByIdProps> = (props) => {
|
|||||||
<Match when={eventQuery.isLoading && localProps.eventId} keyed>
|
<Match when={eventQuery.isLoading && localProps.eventId} keyed>
|
||||||
{(id) => (
|
{(id) => (
|
||||||
<div class="truncate">
|
<div class="truncate">
|
||||||
{'読み込み中 '}
|
{i18n()('general.loading')} <EventLink eventId={id} />
|
||||||
<EventLink eventId={id} />
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Match>
|
</Match>
|
||||||
|
|||||||
@@ -68,7 +68,11 @@ const ReactionDisplay: Component<ReactionDisplayProps> = (props) => {
|
|||||||
<div class="notification-event py-1">
|
<div class="notification-event py-1">
|
||||||
<Show
|
<Show
|
||||||
when={reactedEvent()}
|
when={reactedEvent()}
|
||||||
fallback={<div class="truncate">読み込み中 {eventId()}</div>}
|
fallback={
|
||||||
|
<div class="truncate">
|
||||||
|
{i18n()('general.loading')} {eventId()}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
keyed
|
keyed
|
||||||
>
|
>
|
||||||
{(ev) => <TextNoteDisplay event={ev} />}
|
{(ev) => <TextNoteDisplay event={ev} />}
|
||||||
|
|||||||
@@ -131,21 +131,14 @@ const ProfileDisplay: Component<ProfileDisplayProps> = (props) => {
|
|||||||
|
|
||||||
const latest = await fetchLatestFollowings({ pubkey: p });
|
const latest = await fetchLatestFollowings({ pubkey: p });
|
||||||
|
|
||||||
const msg = stripMargin`
|
if (
|
||||||
フォローリストが空のようです。初めてのフォローであれば問題ありません。
|
(latest.data() == null || latest.followingPubkeys().length === 0) &&
|
||||||
そうでなければ、リレーとの接続がうまくいっていない可能性があります。ページを再読み込みしてリレーと再接続してください。
|
!window.confirm(i18n()('profile.confirmUpdateEvenIfEmpty'))
|
||||||
また、他のクライアントと同じリレーを設定できているどうかご確認ください。
|
)
|
||||||
|
|
||||||
続行しますか?
|
|
||||||
`;
|
|
||||||
|
|
||||||
if ((latest.data() == null || latest.followingPubkeys().length === 0) && !window.confirm(msg))
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if ((latest?.data()?.created_at ?? 0) < (myFollowingQuery.data?.created_at ?? 0)) {
|
if ((latest?.data()?.created_at ?? 0) < (myFollowingQuery.data?.created_at ?? 0)) {
|
||||||
window.alert(
|
window.alert(i18n()('profile.failedToFetchLatestFollowList'));
|
||||||
'最新のフォローリストを取得できませんでした。リレーの接続状況が悪い可能性があります。',
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,7 +164,7 @@ const ProfileDisplay: Component<ProfileDisplayProps> = (props) => {
|
|||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('failed to update contact list', err);
|
console.error('failed to update contact list', err);
|
||||||
window.alert('フォローリストの更新に失敗しました。');
|
window.alert(i18n()('profile.failedToUpdateFollowList'));
|
||||||
} finally {
|
} finally {
|
||||||
setUpdatingContacts(false);
|
setUpdatingContacts(false);
|
||||||
}
|
}
|
||||||
@@ -184,7 +177,7 @@ const ProfileDisplay: Component<ProfileDisplayProps> = (props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const unfollow = () => {
|
const unfollow = () => {
|
||||||
if (!window.confirm('本当にフォロー解除しますか?')) return;
|
if (!window.confirm(i18n()('profile.confirmUnfollow'))) return;
|
||||||
|
|
||||||
updateContacts('unfollow', props.pubkey).catch((err) => {
|
updateContacts('unfollow', props.pubkey).catch((err) => {
|
||||||
console.log('failed to unfollow', err);
|
console.log('failed to unfollow', err);
|
||||||
@@ -384,11 +377,11 @@ const ProfileDisplay: Component<ProfileDisplayProps> = (props) => {
|
|||||||
</Show>
|
</Show>
|
||||||
<div class="flex border-t px-4 py-2">
|
<div class="flex border-t px-4 py-2">
|
||||||
<button class="flex flex-1 flex-col items-start" onClick={() => setModal('Following')}>
|
<button class="flex flex-1 flex-col items-start" onClick={() => setModal('Following')}>
|
||||||
<div class="text-sm">フォロー</div>
|
<div class="text-sm">{i18n()('profile.following')}</div>
|
||||||
<div class="text-xl">
|
<div class="text-xl">
|
||||||
<Show
|
<Show
|
||||||
when={userFollowingQuery.isFetched}
|
when={userFollowingQuery.isFetched}
|
||||||
fallback={<span class="text-sm">読み込み中</span>}
|
fallback={<span class="text-sm">{i18n()('general.loading')}</span>}
|
||||||
>
|
>
|
||||||
{userFollowingPubkeys().length}
|
{userFollowingPubkeys().length}
|
||||||
</Show>
|
</Show>
|
||||||
@@ -396,7 +389,7 @@ const ProfileDisplay: Component<ProfileDisplayProps> = (props) => {
|
|||||||
</button>
|
</button>
|
||||||
<Show when={!config().hideCount}>
|
<Show when={!config().hideCount}>
|
||||||
<div class="flex flex-1 flex-col items-start">
|
<div class="flex flex-1 flex-col items-start">
|
||||||
<div class="text-sm">フォロワー</div>
|
<div class="text-sm">{i18n()('profile.followers')}</div>
|
||||||
<div class="text-xl">
|
<div class="text-xl">
|
||||||
<Show
|
<Show
|
||||||
when={showFollowers()}
|
when={showFollowers()}
|
||||||
@@ -405,7 +398,7 @@ const ProfileDisplay: Component<ProfileDisplayProps> = (props) => {
|
|||||||
class="text-sm hover:text-stone-800 hover:underline"
|
class="text-sm hover:text-stone-800 hover:underline"
|
||||||
onClick={() => setShowFollowers(true)}
|
onClick={() => setShowFollowers(true)}
|
||||||
>
|
>
|
||||||
読み込む
|
{i18n()('profile.loadFollowers')}
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
keyed
|
keyed
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import omitBy from 'lodash/omitBy';
|
|||||||
|
|
||||||
import BasicModal from '@/components/modal/BasicModal';
|
import BasicModal from '@/components/modal/BasicModal';
|
||||||
import useConfig from '@/core/useConfig';
|
import useConfig from '@/core/useConfig';
|
||||||
|
import { useTranslation } from '@/i18n/useTranslation';
|
||||||
import { Profile } from '@/nostr/event/Profile';
|
import { Profile } from '@/nostr/event/Profile';
|
||||||
import useCommands from '@/nostr/useCommands';
|
import useCommands from '@/nostr/useCommands';
|
||||||
import useProfile from '@/nostr/useProfile';
|
import useProfile from '@/nostr/useProfile';
|
||||||
@@ -29,6 +30,7 @@ const isLNURL = (s: string) => LNURLRegex.test(s);
|
|||||||
const isInternetIdentifier = (s: string) => InternetIdentifierRegex.test(s);
|
const isInternetIdentifier = (s: string) => InternetIdentifierRegex.test(s);
|
||||||
|
|
||||||
const ProfileEdit: Component<ProfileEditProps> = (props) => {
|
const ProfileEdit: Component<ProfileEditProps> = (props) => {
|
||||||
|
const i18n = useTranslation();
|
||||||
const pubkey = usePubkey();
|
const pubkey = usePubkey();
|
||||||
const { config } = useConfig();
|
const { config } = useConfig();
|
||||||
|
|
||||||
@@ -56,11 +58,11 @@ const ProfileEdit: Component<ProfileEditProps> = (props) => {
|
|||||||
const succeeded = results.filter((res) => res.status === 'fulfilled').length;
|
const succeeded = results.filter((res) => res.status === 'fulfilled').length;
|
||||||
const failed = results.length - succeeded;
|
const failed = results.length - succeeded;
|
||||||
if (succeeded === results.length) {
|
if (succeeded === results.length) {
|
||||||
window.alert('更新しました');
|
window.alert(i18n()('profile.edit.updateSucceeded'));
|
||||||
} else if (succeeded > 0) {
|
} else if (succeeded > 0) {
|
||||||
window.alert(`${failed}個のリレーで更新に失敗しました`);
|
window.alert(i18n()('profile.edit.failedToUpdatePartially', { count: failed }));
|
||||||
} else {
|
} else {
|
||||||
window.alert('すべてのリレーで更新に失敗しました');
|
window.alert(i18n()('profile.edit.failedToUpdate'));
|
||||||
}
|
}
|
||||||
invalidateProfile()
|
invalidateProfile()
|
||||||
.then(() => query.refetch())
|
.then(() => query.refetch())
|
||||||
@@ -159,13 +161,13 @@ const ProfileEdit: Component<ProfileEditProps> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Show when={loading()}>
|
<Show when={loading()}>
|
||||||
<div class="px-4 pt-4">読み込み中...</div>
|
<div class="px-4 pt-4">{i18n()('general.loading')}</div>
|
||||||
</Show>
|
</Show>
|
||||||
<div>
|
<div>
|
||||||
<form class="flex flex-col gap-4 p-4" onSubmit={handleSubmit}>
|
<form class="flex flex-col gap-4 p-4" onSubmit={handleSubmit}>
|
||||||
<div class="flex flex-col items-start gap-1">
|
<div class="flex flex-col items-start gap-1">
|
||||||
<label class="font-bold" for="picture">
|
<label class="font-bold" for="picture">
|
||||||
アイコン
|
{i18n()('profile.edit.icon')}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
class="w-full rounded-md focus:border-rose-100 focus:ring-rose-300"
|
class="w-full rounded-md focus:border-rose-100 focus:ring-rose-300"
|
||||||
@@ -181,7 +183,7 @@ const ProfileEdit: Component<ProfileEditProps> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col items-start gap-1">
|
<div class="flex flex-col items-start gap-1">
|
||||||
<label class="font-bold" for="picture">
|
<label class="font-bold" for="picture">
|
||||||
バナー
|
{i18n()('profile.edit.banner')}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
class="w-full rounded-md focus:border-rose-100 focus:ring-rose-300"
|
class="w-full rounded-md focus:border-rose-100 focus:ring-rose-300"
|
||||||
@@ -197,7 +199,7 @@ const ProfileEdit: Component<ProfileEditProps> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col items-start gap-1">
|
<div class="flex flex-col items-start gap-1">
|
||||||
<label class="font-bold" for="name">
|
<label class="font-bold" for="name">
|
||||||
ユーザ名
|
{i18n()('profile.edit.name')}
|
||||||
</label>
|
</label>
|
||||||
<div class="flex w-full items-center gap-2">
|
<div class="flex w-full items-center gap-2">
|
||||||
<span>@</span>
|
<span>@</span>
|
||||||
@@ -218,7 +220,7 @@ const ProfileEdit: Component<ProfileEditProps> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col items-start gap-1">
|
<div class="flex flex-col items-start gap-1">
|
||||||
<label class="font-bold" for="name">
|
<label class="font-bold" for="name">
|
||||||
名前
|
{i18n()('profile.edit.displayName')}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
class="w-full rounded-md focus:border-rose-100 focus:ring-rose-300"
|
class="w-full rounded-md focus:border-rose-100 focus:ring-rose-300"
|
||||||
@@ -233,7 +235,7 @@ const ProfileEdit: Component<ProfileEditProps> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col items-start gap-1">
|
<div class="flex flex-col items-start gap-1">
|
||||||
<label class="font-bold" for="name">
|
<label class="font-bold" for="name">
|
||||||
自己紹介
|
{i18n()('profile.edit.about')}
|
||||||
</label>
|
</label>
|
||||||
<textarea
|
<textarea
|
||||||
class="w-full rounded-md focus:border-rose-100 focus:ring-rose-300"
|
class="w-full rounded-md focus:border-rose-100 focus:ring-rose-300"
|
||||||
@@ -246,7 +248,7 @@ const ProfileEdit: Component<ProfileEditProps> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col items-start gap-1">
|
<div class="flex flex-col items-start gap-1">
|
||||||
<label class="font-bold" for="name">
|
<label class="font-bold" for="name">
|
||||||
ウェブサイト
|
{i18n()('profile.edit.website')}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
class="w-full rounded-md focus:border-rose-100 focus:ring-rose-300"
|
class="w-full rounded-md focus:border-rose-100 focus:ring-rose-300"
|
||||||
@@ -261,7 +263,7 @@ const ProfileEdit: Component<ProfileEditProps> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col items-start gap-1">
|
<div class="flex flex-col items-start gap-1">
|
||||||
<label class="font-bold" for="name">
|
<label class="font-bold" for="name">
|
||||||
ドメイン認証(NIP-05)
|
{i18n()('profile.edit.nip05')}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
class="w-full rounded-md focus:border-rose-100 focus:ring-rose-300"
|
class="w-full rounded-md focus:border-rose-100 focus:ring-rose-300"
|
||||||
@@ -277,9 +279,9 @@ const ProfileEdit: Component<ProfileEditProps> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col items-start gap-1">
|
<div class="flex flex-col items-start gap-1">
|
||||||
<label class="font-bold" for="name">
|
<label class="font-bold" for="name">
|
||||||
LNURLアドレス / ライトニングアドレス
|
{i18n()('profile.edit.lightningAddress')}
|
||||||
</label>
|
</label>
|
||||||
<span class="text-xs">どちらか片方のみが保存されます。</span>
|
<span class="text-xs">{i18n()('profile.edit.lightningAddressDescription')}</span>
|
||||||
<input
|
<input
|
||||||
class="w-full rounded-md focus:border-rose-100 focus:ring-rose-300"
|
class="w-full rounded-md focus:border-rose-100 focus:ring-rose-300"
|
||||||
type="text"
|
type="text"
|
||||||
@@ -294,7 +296,7 @@ const ProfileEdit: Component<ProfileEditProps> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
<Show when={Object.entries(otherProperties()).length > 0}>
|
<Show when={Object.entries(otherProperties()).length > 0}>
|
||||||
<div>
|
<div>
|
||||||
<span class="font-bold">その他の項目</span>
|
<span class="font-bold">{i18n()('profile.edit.otherProperties')}</span>
|
||||||
<div>
|
<div>
|
||||||
<For each={Object.entries(otherProperties())}>
|
<For each={Object.entries(otherProperties())}>
|
||||||
{([key, value]) => (
|
{([key, value]) => (
|
||||||
@@ -313,17 +315,17 @@ const ProfileEdit: Component<ProfileEditProps> = (props) => {
|
|||||||
class="rounded bg-rose-300 p-2 font-bold text-white hover:bg-rose-400"
|
class="rounded bg-rose-300 p-2 font-bold text-white hover:bg-rose-400"
|
||||||
disabled={mutation.isLoading}
|
disabled={mutation.isLoading}
|
||||||
>
|
>
|
||||||
保存
|
{i18n()('profile.edit.save')}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="rounded border border-rose-300 p-2 font-bold text-rose-300 hover:border-rose-400 hover:text-rose-400"
|
class="rounded border border-rose-300 p-2 font-bold text-rose-300 hover:border-rose-400 hover:text-rose-400"
|
||||||
onClick={() => props.onClose()}
|
onClick={() => props.onClose()}
|
||||||
>
|
>
|
||||||
キャンセル
|
{i18n()('profile.edit.cancel')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<Show when={mutation.isLoading}>保存中...</Show>
|
<Show when={mutation.isLoading}>{i18n()('profile.edit.updating')}</Show>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</BasicModal>
|
</BasicModal>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import ja from '@/locales/ja';
|
import ja from '@/locales/ja';
|
||||||
|
import stripMargin from '@/utils/stripMargin';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
general: {
|
general: {
|
||||||
@@ -7,9 +8,16 @@ export default {
|
|||||||
},
|
},
|
||||||
posting: {
|
posting: {
|
||||||
placeholder: "What's happening?",
|
placeholder: "What's happening?",
|
||||||
|
placeholderReply: 'Post a reply',
|
||||||
contentWarning: 'Content warning',
|
contentWarning: 'Content warning',
|
||||||
|
contentWarningReason: 'Reason of warning',
|
||||||
uploadImage: 'Upload image',
|
uploadImage: 'Upload image',
|
||||||
submit: 'Submit',
|
submit: 'Submit',
|
||||||
|
forbiddenToIncludeNsec: 'You cannot include private key (nsec).',
|
||||||
|
failedToUploadFile: 'Failed to upload files: {{filenames}}',
|
||||||
|
replyToPre: 'Reply to',
|
||||||
|
replyToAnd: ' and ',
|
||||||
|
replyToPost: '',
|
||||||
},
|
},
|
||||||
column: {
|
column: {
|
||||||
home: 'Home',
|
home: 'Home',
|
||||||
@@ -49,6 +57,38 @@ export default {
|
|||||||
unmute: 'Unmute',
|
unmute: 'Unmute',
|
||||||
followMyself: 'Follow myself',
|
followMyself: 'Follow myself',
|
||||||
unfollowMyself: 'Unfollow myself',
|
unfollowMyself: 'Unfollow myself',
|
||||||
|
confirmUnfollow: 'Do you really want to unfollow?',
|
||||||
|
confirmUpdateEvenIfEmpty: stripMargin`
|
||||||
|
Your follow list appears to be empty.
|
||||||
|
|
||||||
|
There is no problem if you are trying to follow for the first time.
|
||||||
|
Otherwise, it may be caused by poor connections to relays.
|
||||||
|
You should reload this page to reconnect to relays.
|
||||||
|
You also should make sure you have configured the same relay list as the other clients.
|
||||||
|
|
||||||
|
Do you want to continue?
|
||||||
|
`,
|
||||||
|
failedToUpdateFollowList: 'Failed to update the follow list',
|
||||||
|
failedToFetchLatestFollowList:
|
||||||
|
'Failed to fetch the latest follow list. It may be disconnected from some relays.',
|
||||||
|
edit: {
|
||||||
|
icon: 'Icon',
|
||||||
|
banner: 'Banner image',
|
||||||
|
name: 'Username',
|
||||||
|
displayName: 'Display Name',
|
||||||
|
about: 'About',
|
||||||
|
website: 'Website',
|
||||||
|
nip05: 'Domain verification (NIP-05)',
|
||||||
|
lightningAddress: 'LNURL address / lightning address',
|
||||||
|
lightningAddressDescription: 'Only one side will be saved.',
|
||||||
|
otherProperties: 'Other properties',
|
||||||
|
save: 'Save',
|
||||||
|
cancel: 'Cancel',
|
||||||
|
updating: 'updating...',
|
||||||
|
updateSucceeded: 'Updated successfully',
|
||||||
|
failedToUpdatePartially: 'Failed to update on {{count}} relays',
|
||||||
|
failedToUpdate: 'Failed to update on all relays',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
post: {
|
post: {
|
||||||
replyToPre: 'Replying to ',
|
replyToPre: 'Replying to ',
|
||||||
@@ -71,6 +111,7 @@ export default {
|
|||||||
show: 'Click to display',
|
show: 'Click to display',
|
||||||
reason: 'Reason',
|
reason: 'Reason',
|
||||||
},
|
},
|
||||||
|
failedToFetchEvent: 'Failed to fetch event',
|
||||||
},
|
},
|
||||||
notification: {
|
notification: {
|
||||||
reposted: ' reposted',
|
reposted: ' reposted',
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import stripMargin from '@/utils/stripMargin';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
general: {
|
general: {
|
||||||
loading: '読み込み中',
|
loading: '読み込み中',
|
||||||
@@ -5,9 +7,16 @@ export default {
|
|||||||
},
|
},
|
||||||
posting: {
|
posting: {
|
||||||
placeholder: 'いまどうしてる?',
|
placeholder: 'いまどうしてる?',
|
||||||
|
placeholderReply: '返信を投稿',
|
||||||
contentWarning: 'コンテンツ警告を設定',
|
contentWarning: 'コンテンツ警告を設定',
|
||||||
|
contentWarningReason: '警告の理由',
|
||||||
uploadImage: '画像を投稿',
|
uploadImage: '画像を投稿',
|
||||||
submit: '投稿',
|
submit: '投稿',
|
||||||
|
forbiddenToIncludeNsec: '投稿に秘密鍵(nsec)を含めることはできません。',
|
||||||
|
failedToUploadFile: 'ファイルのアップロードにしました: {{filenames}}',
|
||||||
|
replyToPre: '',
|
||||||
|
replyToAnd: ' と ',
|
||||||
|
replyToPost: 'に返信',
|
||||||
},
|
},
|
||||||
column: {
|
column: {
|
||||||
home: 'ホーム',
|
home: 'ホーム',
|
||||||
@@ -47,6 +56,35 @@ export default {
|
|||||||
unmute: 'ミュート解除',
|
unmute: 'ミュート解除',
|
||||||
followMyself: '自分をフォロー',
|
followMyself: '自分をフォロー',
|
||||||
unfollowMyself: '自分をフォロー解除',
|
unfollowMyself: '自分をフォロー解除',
|
||||||
|
confirmUnfollow: '本当にフォロー解除しますか?',
|
||||||
|
confirmUpdateEvenIfEmpty: stripMargin`
|
||||||
|
フォローリストが空のようです。初めてのフォローであれば問題ありません。
|
||||||
|
そうでなければ、リレーとの接続がうまくいっていない可能性があります。ページを再読み込みしてリレーと再接続してください。
|
||||||
|
また、他のクライアントと同じリレーを設定できているどうかご確認ください。
|
||||||
|
|
||||||
|
続行しますか?
|
||||||
|
`,
|
||||||
|
failedToUpdateFollowList: 'フォローリストの更新に失敗しました',
|
||||||
|
failedToFetchLatestFollowList:
|
||||||
|
'最新のフォローリストを取得できませんでした。リレーの接続状況が悪い可能性があります。',
|
||||||
|
edit: {
|
||||||
|
icon: 'アイコン',
|
||||||
|
banner: 'バナー',
|
||||||
|
name: 'ユーザ名',
|
||||||
|
displayName: 'ユーザ名',
|
||||||
|
about: '自己紹介',
|
||||||
|
website: 'ウェブサイト',
|
||||||
|
nip05: 'ドメイン認証(NIP-05)',
|
||||||
|
lightningAddress: 'LNURLアドレス / ライトニングアドレス',
|
||||||
|
lightningAddressDescription: 'どちらか片方のみが保存されます。',
|
||||||
|
otherProperties: 'その他の項目',
|
||||||
|
save: '更新',
|
||||||
|
cancel: 'キャンセル',
|
||||||
|
updating: '更新中...',
|
||||||
|
updateSucceeded: '更新しました',
|
||||||
|
failedToUpdatePartially: '{{count}}個のリレーで更新に失敗しました',
|
||||||
|
failedToUpdate: 'すべてのリレーで更新に失敗しました',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
post: {
|
post: {
|
||||||
replyToPre: '',
|
replyToPre: '',
|
||||||
@@ -69,6 +107,7 @@ export default {
|
|||||||
show: '表示するにはクリック',
|
show: '表示するにはクリック',
|
||||||
reason: '理由',
|
reason: '理由',
|
||||||
},
|
},
|
||||||
|
failedToFetchEvent: '取得に失敗しました',
|
||||||
},
|
},
|
||||||
notification: {
|
notification: {
|
||||||
reposted: 'がリポスト',
|
reposted: 'がリポスト',
|
||||||
|
|||||||
Reference in New Issue
Block a user