feat: i18n

This commit is contained in:
Shusui MOYATANI
2023-09-09 01:09:31 +09:00
parent d2be5a9229
commit 15c8d0c924
7 changed files with 142 additions and 59 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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',

View File

@@ -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: 'がリポスト',