このコミットが含まれているのは:
@@ -8,6 +8,8 @@ const COLUMN_COUNT = 12
|
||||
|
||||
|
||||
const DevModeWatermark: FC = () => {
|
||||
const { t } = useTranslation ('shared')
|
||||
const text = t ('labels.development', { returnDetails: true })
|
||||
return (
|
||||
<div
|
||||
aria-hidden="true"
|
||||
@@ -29,13 +31,14 @@ const DevModeWatermark: FC = () => {
|
||||
{Array.from ({ length: COLUMN_COUNT }, (_, columnIndex) => (
|
||||
<span
|
||||
key={columnIndex}
|
||||
lang={text.usedLng}
|
||||
className={
|
||||
'whitespace-nowrap text-3xl font-bold '
|
||||
+ 'tracking-[0.3em] text-neutral-950/5 '
|
||||
+ 'dark:text-white/10'
|
||||
}
|
||||
style={{ fontFamily: 'Nikumaru' }}>
|
||||
開発モード
|
||||
{text.res}
|
||||
</span>))}
|
||||
</div>))}
|
||||
</div>
|
||||
@@ -44,3 +47,5 @@ const DevModeWatermark: FC = () => {
|
||||
|
||||
|
||||
export default DevModeWatermark
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import nikumaru from '@/assets/fonts/nikumaru.otf'
|
||||
import errorImg from '@/assets/images/not-found.gif'
|
||||
import MainArea from '@/components/layout/MainArea'
|
||||
import { SITE_TITLE } from '@/config'
|
||||
import { useUITranslation } from '@/lib/useUITranslation'
|
||||
|
||||
import type { FC } from 'react'
|
||||
|
||||
@@ -11,17 +12,16 @@ type Props = { status: number }
|
||||
|
||||
|
||||
const ErrorScreen: FC<Props> = ({ status }) => {
|
||||
const [message, rightMsg, leftMsg]: [string, string, string] = (() => {
|
||||
const { message: translate, label } = useUITranslation ('errors')
|
||||
const [message]: [string, string, string] = (() => {
|
||||
switch (status)
|
||||
{
|
||||
case 403:
|
||||
return ['権限ないよ(笑)', '帰れ!', '帰れ!']
|
||||
case 404:
|
||||
return ['ページないよ(笑)', '帰れ!', '帰れ!']
|
||||
case 500:
|
||||
return ['鯖でエラー出たって(嘲笑)', '管理人に', '聯絡を!']
|
||||
case 503:
|
||||
return ['鯖死んでるよ(泣)', '管理人に', '聯絡を!']
|
||||
return [translate (`${ status }.message`).text,
|
||||
translate (`${ status }.right`).text, translate (`${ status }.left`).text]
|
||||
default:
|
||||
throw new Error
|
||||
}
|
||||
@@ -51,13 +51,13 @@ const ErrorScreen: FC<Props> = ({ status }) => {
|
||||
style={{ fontFamily: 'Nikumaru' }}>
|
||||
<p>{status}</p>
|
||||
<div className="flex flex-row space-x-1 sm:space-x-2 md:space-x-4">
|
||||
<p style={{ writingMode: 'vertical-rl' }}>{leftMsg}</p>
|
||||
<img className="max-w-[70vw]" src={errorImg} alt="逃げたギター"/>
|
||||
<p style={{ writingMode: 'vertical-rl' }}>{rightMsg}</p>
|
||||
<p style={{ writingMode: 'vertical-rl' }}>{label (`${ status }.left`)}</p>
|
||||
<img className="max-w-[70vw]" src={errorImg} alt={translate ('imageAlt').text}/>
|
||||
<p style={{ writingMode: 'vertical-rl' }}>{label (`${ status }.right`)}</p>
|
||||
</div>
|
||||
<p className="mr-[-.5em]">{message}</p>
|
||||
<p className="mr-[-.5em]">{label (`${ status }.message`)}</p>
|
||||
</div>
|
||||
</MainArea>)
|
||||
}
|
||||
|
||||
export default ErrorScreen
|
||||
export default ErrorScreen
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { useRef, useState } from 'react'
|
||||
|
||||
import { useLocale } from '@/lib/useLocale'
|
||||
import { useUITranslation } from '@/lib/useUITranslation'
|
||||
import TagSearchBox from '@/components/TagSearchBox'
|
||||
import FormField from '@/components/common/FormField'
|
||||
import TextArea from '@/components/common/TextArea'
|
||||
@@ -39,6 +40,7 @@ type Props = Omit<ComponentPropsWithoutRef<'textarea'>, 'value' | 'onChange' | '
|
||||
|
||||
|
||||
const PostFormTagsArea: FC<Props> = ({ tags, setTags, errors, ...rest }) => {
|
||||
const { label } = useUITranslation ('navigation')
|
||||
const locale = useLocale ()
|
||||
const ref = useRef<HTMLTextAreaElement> (null)
|
||||
|
||||
@@ -79,7 +81,7 @@ const PostFormTagsArea: FC<Props> = ({ tags, setTags, errors, ...rest }) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<FormField className="relative w-full" label="タグ" messages={errors}>
|
||||
<FormField className="relative w-full" label={label ('tags')} messages={errors}>
|
||||
{({ describedBy, invalid }) => (
|
||||
<>
|
||||
<TextArea
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useRef } from 'react'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
|
||||
import PrefetchLink from '@/components/PrefetchLink'
|
||||
import { useUITranslation } from '@/lib/useUITranslation'
|
||||
import { useClientBehaviourSettings } from '@/lib/useClientBehaviourSettings'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useSharedTransitionStore } from '@/stores/sharedTransitionStore'
|
||||
@@ -16,6 +17,7 @@ type Props = { posts: Post[]
|
||||
|
||||
|
||||
const PostList: FC<Props> = ({ posts, onClick }) => {
|
||||
const { label } = useUITranslation ('shared')
|
||||
const location = useLocation ()
|
||||
const behaviourSettings = useClientBehaviourSettings ()
|
||||
const thumbnailMode = behaviourSettings.thumbnailMode ?? 'normal'
|
||||
@@ -75,7 +77,7 @@ const PostList: FC<Props> = ({ posts, onClick }) => {
|
||||
? (
|
||||
<div className="flex h-full w-full items-center justify-center
|
||||
bg-muted text-center text-xs text-muted-foreground">
|
||||
サムネイルなし
|
||||
{label ('labels.noThumbnail')}
|
||||
</div>)
|
||||
: (
|
||||
<img
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import DateTimeField, { toMinutePrecisionIsoUtc } from '@/components/common/DateTimeField'
|
||||
import FormField from '@/components/common/FormField'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { useUITranslation } from '@/lib/useUITranslation'
|
||||
|
||||
import type { FC } from 'react'
|
||||
|
||||
@@ -19,9 +20,11 @@ const PostOriginalCreatedTimeField: FC<Props> = (
|
||||
setOriginalCreatedFrom,
|
||||
originalCreatedBefore,
|
||||
setOriginalCreatedBefore,
|
||||
errors }: Props) => {
|
||||
errors }: Props,
|
||||
) => {
|
||||
const { label } = useUITranslation ('shared')
|
||||
return (
|
||||
<FormField label="オリジナルの作成日時" messages={errors}>
|
||||
<FormField label={label ('labels.originalCreatedAt')} messages={errors}>
|
||||
{({ describedBy, invalid }) => (
|
||||
<>
|
||||
<div className="my-1 flex">
|
||||
@@ -46,7 +49,7 @@ const PostOriginalCreatedTimeField: FC<Props> = (
|
||||
d.setMinutes (d.getMinutes () + 1)
|
||||
setOriginalCreatedBefore (toMinutePrecisionIsoUtc (d.toISOString ()))
|
||||
}}/>
|
||||
以降
|
||||
{label ('labels.since')}
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
@@ -56,7 +59,7 @@ const PostOriginalCreatedTimeField: FC<Props> = (
|
||||
onClick={() => {
|
||||
setOriginalCreatedFrom (null)
|
||||
}}>
|
||||
リセット
|
||||
{label ('actions.reset')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -71,7 +74,7 @@ const PostOriginalCreatedTimeField: FC<Props> = (
|
||||
invalid={invalid}
|
||||
value={originalCreatedBefore ?? undefined}
|
||||
onChange={setOriginalCreatedBefore}/>
|
||||
より前
|
||||
{label ('labels.before')}
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
@@ -81,7 +84,7 @@ const PostOriginalCreatedTimeField: FC<Props> = (
|
||||
onClick={() => {
|
||||
setOriginalCreatedBefore (null)
|
||||
}}>
|
||||
リセット
|
||||
{label ('actions.reset')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useEffect, useState } from 'react'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
|
||||
import { useLocale, useLocaleNavigate } from '@/lib/useLocale'
|
||||
import { useUITranslation } from '@/lib/useUITranslation'
|
||||
import { apiGet } from '@/lib/api'
|
||||
import { inputClass } from '@/lib/utils'
|
||||
|
||||
@@ -15,6 +16,7 @@ import type { Tag } from '@/types'
|
||||
|
||||
|
||||
const TagSearch: FC = () => {
|
||||
const { message } = useUITranslation ('shared')
|
||||
const locale = useLocale ()
|
||||
const location = useLocation ()
|
||||
const navigate = useLocaleNavigate ()
|
||||
@@ -108,7 +110,7 @@ const TagSearch: FC = () => {
|
||||
<div className="relative w-full">
|
||||
<input type="text"
|
||||
data-shortcut-focus-search="true"
|
||||
placeholder="タグ検索..."
|
||||
placeholder={message ('labels.tagSearch').text}
|
||||
value={search}
|
||||
onChange={whenChanged}
|
||||
onFocus={() => setSuggestionsVsbl (true)}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useEffect, useState } from 'react'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
|
||||
import { useLocale, useLocaleNavigate } from '@/lib/useLocale'
|
||||
import { useUITranslation } from '@/lib/useUITranslation'
|
||||
import TagLink from '@/components/TagLink'
|
||||
import TagSearch from '@/components/TagSearch'
|
||||
import SectionTitle from '@/components/common/SectionTitle'
|
||||
@@ -23,6 +24,7 @@ type Props = { posts: Post[]
|
||||
|
||||
|
||||
const TagSidebar: FC<Props> = ({ posts, onClick }) => {
|
||||
const { label } = useUITranslation ('shared')
|
||||
const locale = useLocale ()
|
||||
const navigate = useLocaleNavigate ()
|
||||
const behaviourSettings = useClientBehaviourSettings ()
|
||||
@@ -71,7 +73,7 @@ const TagSidebar: FC<Props> = ({ posts, onClick }) => {
|
||||
|
||||
const TagBlock = (
|
||||
<>
|
||||
<SectionTitle>タグ</SectionTitle>
|
||||
<SectionTitle>{label ('navigation:tags')}</SectionTitle>
|
||||
<ul>
|
||||
{CATEGORIES.flatMap (cat => cat in tags ? (
|
||||
tags[cat].map (tag => (
|
||||
@@ -90,7 +92,7 @@ const TagSidebar: FC<Props> = ({ posts, onClick }) => {
|
||||
</motion.div>)}
|
||||
</li>))) : [])}
|
||||
</ul>
|
||||
<SectionTitle>関聯</SectionTitle>
|
||||
<SectionTitle>{label ('labels.relations')}</SectionTitle>
|
||||
<a href="#"
|
||||
onClick={ev => {
|
||||
ev.preventDefault ()
|
||||
@@ -108,7 +110,7 @@ const TagSidebar: FC<Props> = ({ posts, onClick }) => {
|
||||
}
|
||||
}) ())
|
||||
}}>
|
||||
ランダム
|
||||
{label ('actions.random')}
|
||||
</a>
|
||||
</>)
|
||||
|
||||
@@ -148,7 +150,7 @@ const TagSidebar: FC<Props> = ({ posts, onClick }) => {
|
||||
ev.preventDefault ()
|
||||
setTagsVsbl (v => !(v))
|
||||
}}>
|
||||
{tagsVsbl ? '▲▲▲ タグ一覧を閉じる ▲▲▲' : '▼▼▼ タグ一覧を表示 ▼▼▼'}
|
||||
{tagsVsbl ? label ('labels.hideTags') : label ('labels.showTags')}
|
||||
</a>
|
||||
</SidebarComponent>)
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import { isLocaleCode,
|
||||
switchLocalePath,
|
||||
unlocalisedPath } from '@/lib/locales'
|
||||
import { uiMessage, uiMessageLanguage } from '@/lib/messages'
|
||||
import { useUITranslation } from '@/lib/useUITranslation'
|
||||
import { materialsKeys, tagsKeys, wikiKeys } from '@/lib/queryKeys'
|
||||
import { useClientBehaviourSettings } from '@/lib/useClientBehaviourSettings'
|
||||
import { canEditContent } from '@/lib/users'
|
||||
@@ -125,6 +126,7 @@ export const menuOutline = (
|
||||
|
||||
|
||||
const TopNav: FC<Props> = ({ user, locales }) => {
|
||||
const { label } = useUITranslation ('shared')
|
||||
const locale = useLocale ()
|
||||
const localeName = locales.find (entry => entry.code === locale)?.name
|
||||
const navigate = useLocaleNavigate ()
|
||||
@@ -206,21 +208,21 @@ const TopNav: FC<Props> = ({ user, locales }) => {
|
||||
const topNavTransition =
|
||||
animationsOff
|
||||
? { duration: 0 }
|
||||
: reducedAnimations
|
||||
? { duration: .08, ease: 'linear' as const }
|
||||
: { duration: .2, ease: 'easeOut' as const }
|
||||
: (reducedAnimations
|
||||
? { duration: .08, ease: 'linear' as const }
|
||||
: { duration: .2, ease: 'easeOut' as const })
|
||||
const opacityTransition =
|
||||
animationsOff
|
||||
? { duration: 0 }
|
||||
: reducedAnimations
|
||||
? { duration: .08 }
|
||||
: { duration: .12 }
|
||||
: (reducedAnimations
|
||||
? { duration: .08 }
|
||||
: { duration: .12 })
|
||||
const highlightTransitionClass =
|
||||
animationsOff
|
||||
? undefined
|
||||
: reducedAnimations
|
||||
? 'transition-[transform,width] duration-75 ease-linear'
|
||||
: 'transition-[transform,width] duration-200 ease-out'
|
||||
: (reducedAnimations
|
||||
? 'transition-[transform,width] duration-75 ease-linear'
|
||||
: 'transition-[transform,width] duration-200 ease-out')
|
||||
|
||||
const prevActiveIdxRef = useRef<number> (activeIdx)
|
||||
|
||||
@@ -382,8 +384,7 @@ const TopNav: FC<Props> = ({ user, locales }) => {
|
||||
onClick={() => {
|
||||
scroll (0, 0)
|
||||
}}>
|
||||
{/* TODO: ここも外出ししろ. */}
|
||||
ぼざクリ タグ広場
|
||||
{label ('labels.brand')}
|
||||
</PrefetchLink>
|
||||
|
||||
<div ref={navRef}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import Separator from '@/components/MenuSeparator'
|
||||
import PrefetchLink from '@/components/PrefetchLink'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useUITranslation } from '@/lib/useUITranslation'
|
||||
|
||||
import type { FC } from 'react'
|
||||
|
||||
@@ -11,6 +12,7 @@ type Props = { user: User | null,
|
||||
|
||||
|
||||
const TopNavUser: FC<Props> = ({ user, sp }) => {
|
||||
const { label } = useUITranslation ('shared')
|
||||
if (!(user))
|
||||
return
|
||||
|
||||
@@ -24,7 +26,7 @@ const TopNavUser: FC<Props> = ({ user, sp }) => {
|
||||
<>
|
||||
{sp && <Separator/>}
|
||||
<PrefetchLink to="/users/settings" className={className}>
|
||||
{user.name || '名もなきニジラー'}
|
||||
{user.name || label ('labels.anonymous')}
|
||||
</PrefetchLink>
|
||||
</>)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import ReactMarkdown from 'react-markdown'
|
||||
import remarkGFM from 'remark-gfm'
|
||||
|
||||
import { useLocale } from '@/lib/useLocale'
|
||||
import { useUITranslation } from '@/lib/useUITranslation'
|
||||
import PrefetchLink from '@/components/PrefetchLink'
|
||||
import { wikiKeys } from '@/lib/queryKeys'
|
||||
import remarkWikiAutoLink from '@/lib/remark-wiki-autolink'
|
||||
@@ -27,6 +28,7 @@ const mdComponents = { a: (({ href, children }) => (
|
||||
|
||||
|
||||
const WikiBody: FC<Props> = ({ title, body }) => {
|
||||
const { message } = useUITranslation ('wiki')
|
||||
const locale = useLocale ()
|
||||
const { data } = useQuery ({
|
||||
enabled: Boolean (body),
|
||||
@@ -39,8 +41,9 @@ const WikiBody: FC<Props> = ({ title, body }) => {
|
||||
|
||||
return (
|
||||
<ReactMarkdown components={mdComponents} remarkPlugins={remarkPlugins}>
|
||||
{body || `このページは存在しません。[新規作成してください](/wiki/new?title=${ encodeURIComponent (title) })。`}
|
||||
{body || message ('missingPage', {
|
||||
url: `/wiki/new?title=${ encodeURIComponent (title) }` }).text}
|
||||
</ReactMarkdown>)
|
||||
}
|
||||
|
||||
export default WikiBody
|
||||
export default WikiBody
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import PostOriginalCreatedTimeField from '@/components/PostOriginalCreatedTimeField'
|
||||
import PostTagsField from '@/components/posts/PostTagsField'
|
||||
import PostTextField from '@/components/posts/PostTextField'
|
||||
import TranslatedText from '@/components/TranslatedText'
|
||||
|
||||
import type { FC, ReactNode } from 'react'
|
||||
|
||||
@@ -54,7 +55,7 @@ const PostCoreDataFields: FC<PostCoreDataFieldsProps> = (
|
||||
) => (
|
||||
<>
|
||||
<PostTextField
|
||||
label="タイトル"
|
||||
label={<TranslatedText namespace="shared" messageKey="labels.title"/>}
|
||||
value={title.value}
|
||||
disabled={title.disabled}
|
||||
warnings={title.warnings}
|
||||
@@ -82,7 +83,7 @@ const PostCoreDataFields: FC<PostCoreDataFieldsProps> = (
|
||||
rows={tags.rows}/>
|
||||
|
||||
<PostTextField
|
||||
label="親投稿"
|
||||
label={<TranslatedText namespace="shared" messageKey="labels.parentPosts"/>}
|
||||
value={parentPostIds.value}
|
||||
disabled={parentPostIds.disabled}
|
||||
warnings={parentPostIds.warnings}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import PostTextField from '@/components/posts/PostTextField'
|
||||
import TranslatedText from '@/components/TranslatedText'
|
||||
|
||||
import type { FC } from 'react'
|
||||
|
||||
@@ -16,12 +17,11 @@ const PostDurationField: FC<Props> = (
|
||||
disabled },
|
||||
) => (
|
||||
<PostTextField
|
||||
label="動画時間"
|
||||
label={<TranslatedText namespace="shared" messageKey="labels.duration"/>}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
errors={errors}
|
||||
disabled={disabled}
|
||||
type="text"/>
|
||||
)
|
||||
type="text"/>)
|
||||
|
||||
export default PostDurationField
|
||||
|
||||
@@ -5,7 +5,7 @@ import { inputClass } from '@/lib/utils'
|
||||
import type { FC, ReactNode } from 'react'
|
||||
|
||||
type Props = {
|
||||
label: string
|
||||
label: ReactNode
|
||||
value: string
|
||||
onChange: (value: string) => void
|
||||
warnings?: string[]
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useUITranslation } from '@/lib/useUITranslation'
|
||||
|
||||
import type { FC } from 'react'
|
||||
|
||||
@@ -15,10 +16,11 @@ type Props = {
|
||||
const PostThumbnailPreview: FC<Props> = (
|
||||
{ url,
|
||||
file,
|
||||
alt = 'サムネール',
|
||||
alt,
|
||||
className = 'h-16 w-16',
|
||||
referrerPolicy },
|
||||
) => {
|
||||
const { message } = useUITranslation ('shared')
|
||||
const [failed, setFailed] = useState (false)
|
||||
const [fileUrl, setFileUrl] = useState<string | null> (null)
|
||||
|
||||
@@ -55,7 +57,7 @@ const PostThumbnailPreview: FC<Props> = (
|
||||
return (
|
||||
<img
|
||||
src={resolvedUrl}
|
||||
alt={alt}
|
||||
alt={alt ?? message ('labels.thumbnail').text}
|
||||
referrerPolicy={referrerPolicy}
|
||||
className={cn (className, 'rounded border border-border object-cover')}
|
||||
onError={() => setFailed (true)}/>)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import StatusBadge from '@/components/common/StatusBadge'
|
||||
import TranslatedText from '@/components/TranslatedText'
|
||||
|
||||
import type { FC } from 'react'
|
||||
|
||||
@@ -8,14 +9,6 @@ import type { PostImportBadgeValue } from '@/components/posts/import/postImportR
|
||||
type Props = {
|
||||
value: PostImportBadgeValue }
|
||||
|
||||
const LABELS: Record<PostImportBadgeValue, string> = {
|
||||
ready: '登録可能',
|
||||
error: '登録不可',
|
||||
warning: '警告',
|
||||
skipped: 'スキップ',
|
||||
created: '登録済み',
|
||||
failed: '登録失敗' }
|
||||
|
||||
const TONES: Record<PostImportBadgeValue, StatusBadgeTone> = {
|
||||
ready: 'success',
|
||||
error: 'warning',
|
||||
@@ -27,7 +20,7 @@ const TONES: Record<PostImportBadgeValue, StatusBadgeTone> = {
|
||||
|
||||
const PostImportStatusBadge: FC<Props> = ({ value }) => (
|
||||
<StatusBadge tone={TONES[value]}>
|
||||
{LABELS[value]}
|
||||
<TranslatedText namespace="imports" messageKey={`status.${ value }`}/>
|
||||
</StatusBadge>)
|
||||
|
||||
export default PostImportStatusBadge
|
||||
|
||||
@@ -12,7 +12,7 @@ type Props = {
|
||||
const PostImportThumbnailPreview: FC<Props> = (
|
||||
{ url,
|
||||
file,
|
||||
alt = 'サムネール',
|
||||
alt,
|
||||
className = 'h-16 w-16' },
|
||||
) => (
|
||||
<PostThumbnailPreview
|
||||
|
||||
+18
-15
@@ -1,13 +1,6 @@
|
||||
import { createInstance } from 'i18next'
|
||||
import { initReactI18next } from 'react-i18next'
|
||||
|
||||
import ainuNavigation from '@/locales/ain-Kana/navigation.json'
|
||||
import ainuCommon from '@/locales/ain-Kana/common.json'
|
||||
import ainuTags from '@/locales/ain-Kana/tags.json'
|
||||
import nipponeseNavigation from '@/locales/ja/navigation.json'
|
||||
import nipponeseCommon from '@/locales/ja/common.json'
|
||||
import nipponeseTags from '@/locales/ja/tags.json'
|
||||
|
||||
import type { LocaleCode } from '@/lib/locales'
|
||||
|
||||
export const i18n = createInstance ()
|
||||
@@ -17,6 +10,8 @@ export const i18n = createInstance ()
|
||||
const translatedResource = (resource: Record<string, unknown>): Record<string, unknown> => {
|
||||
const untranslated = new Set (
|
||||
Array.isArray (resource._untranslated) ? resource._untranslated : [])
|
||||
if (untranslated.has ('*'))
|
||||
return { }
|
||||
const select = (entries: Record<string, unknown>, prefix = ''): Record<string, unknown> =>
|
||||
Object.fromEntries (Object.entries (entries).flatMap (([key, value]) => {
|
||||
const path = prefix ? `${ prefix }.${ key }` : key
|
||||
@@ -32,6 +27,19 @@ const translatedResource = (resource: Record<string, unknown>): Record<string, u
|
||||
}
|
||||
|
||||
|
||||
const dictionaries = import.meta.glob<Record<string, unknown>> ('../locales/*/*.json', {
|
||||
eager: true, import: 'default' })
|
||||
const resources: Record<string, Record<string, Record<string, unknown>>> = { }
|
||||
for (const [path, dictionary] of Object.entries (dictionaries))
|
||||
{
|
||||
const [, , locale, filename] = path.split ('/')
|
||||
resources[locale] ??= { }
|
||||
resources[locale][filename.slice (0, -5)] =
|
||||
locale === 'ain-Kana' ? translatedResource (dictionary) : dictionary
|
||||
}
|
||||
const namespaces = Object.keys (resources.ja ?? { })
|
||||
|
||||
|
||||
void i18n.use (initReactI18next).init ({
|
||||
lng: 'ja',
|
||||
fallbackLng: 'ja',
|
||||
@@ -39,13 +47,8 @@ void i18n.use (initReactI18next).init ({
|
||||
load: 'currentOnly',
|
||||
initAsync: false,
|
||||
defaultNS: 'navigation',
|
||||
ns: ['navigation', 'common', 'tags'],
|
||||
resources: {
|
||||
ja: { navigation: nipponeseNavigation, common: nipponeseCommon, tags: nipponeseTags },
|
||||
'ain-Kana': {
|
||||
navigation: ainuNavigation,
|
||||
common: translatedResource (ainuCommon),
|
||||
tags: translatedResource (ainuTags) } },
|
||||
ns: namespaces,
|
||||
resources,
|
||||
interpolation: { escapeValue: false },
|
||||
returnNull: false,
|
||||
returnEmptyString: false,
|
||||
@@ -77,7 +80,7 @@ export const untranslatedKeys = (
|
||||
|
||||
if (import.meta.env.DEV)
|
||||
{
|
||||
for (const namespace of ['navigation', 'common', 'tags'])
|
||||
for (const namespace of namespaces)
|
||||
{
|
||||
const missing = untranslatedKeys ('ain-Kana', namespace)
|
||||
if (missing.length > 0)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { createElement } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import LocalisedText from '@/components/LocalisedText'
|
||||
import { useLocale } from '@/lib/useLocale'
|
||||
|
||||
|
||||
@@ -12,5 +14,11 @@ export const useUITranslation = (namespace = 'common') => {
|
||||
return { text: result.res, language: result.usedLng }
|
||||
}
|
||||
|
||||
return { message }
|
||||
const label = (key: string, values?: Record<string, string | number>) => {
|
||||
const result = message (key, values)
|
||||
return createElement (LocalisedText, {
|
||||
language: result.language, children: result.text })
|
||||
}
|
||||
|
||||
return { message, label }
|
||||
}
|
||||
|
||||
@@ -1,38 +1,23 @@
|
||||
{
|
||||
"_untranslated": [
|
||||
"countLabel", "categoryLabel", "stateLabel", "loading", "fetchError",
|
||||
"states", "categories.deerjikist", "categories.meme", "categories.character",
|
||||
"categories.general", "categories.meta", "categories.nico", "pagination", "tagLink"
|
||||
],
|
||||
"countLabel": "件数",
|
||||
"categoryLabel": "カテゴリ",
|
||||
"stateLabel": "状態",
|
||||
{ "countLabel": "チピㇱキㇷ゚",
|
||||
"categoryLabel": "イソネカ",
|
||||
"stateLabel": "シㇼキ",
|
||||
"search": "イフナラ",
|
||||
"loading": "Loading...",
|
||||
"fetchError": "データ取得エラー",
|
||||
"states": {
|
||||
"active": "有効",
|
||||
"deprecated": "廃止"
|
||||
},
|
||||
"categories": {
|
||||
"deerjikist": "ニジラー",
|
||||
"meme": "原作・ネタ元・ミーム等",
|
||||
"character": "キャラクター",
|
||||
"general": "一般",
|
||||
"material": "チカㇻペ",
|
||||
"meta": "メタタグ",
|
||||
"nico": "ニコニコタグ"
|
||||
},
|
||||
"pagination": {
|
||||
"label": "Pagination",
|
||||
"first": "最初のページ",
|
||||
"previous": "前のページ",
|
||||
"next": "次のページ",
|
||||
"last": "最後のページ"
|
||||
},
|
||||
"tagLink": {
|
||||
"missingMaterial": "{{name}} 素材情報が存在しません.",
|
||||
"missingDeerjikist": "{{name}} に関する情報が存在しません.",
|
||||
"missingWiki": "{{name}} Wiki が存在しません."
|
||||
}
|
||||
}
|
||||
"loading": "タツ゚ㇺ アウㇰ コㇿ アン…",
|
||||
"fetchError": "タツ゚ㇺ アウㇰ エアイカㇷ゚",
|
||||
"states": { "active": "アㇱカイ",
|
||||
"deprecated": "タネ ソモ アエイワンケ" },
|
||||
"categories": { "deerjikist": "ユㇰ ニチカ ウタㇻ",
|
||||
"meme": "MEME",
|
||||
"character": "イノカ",
|
||||
"general": "ウサ オルㇱペ",
|
||||
"material": "チカㇻペ",
|
||||
"meta": "タㇰ オルㇱペ タㇰ",
|
||||
"nico": "ニコニコ タㇰ" },
|
||||
"pagination": { "label": "ソㇱ",
|
||||
"first": "イヨッタ ホㇱキ ソㇱ",
|
||||
"previous": "ホㇱキ ソㇱ",
|
||||
"next": "トゥタヌ ソㇱ",
|
||||
"last": "イヨッタ イヨㇱノ アン ソㇱ" },
|
||||
"tagLink": { "missingMaterial": "{{name}}:チカㇻペ アスㇽソㇱ ソモ アン。",
|
||||
"missingDeerjikist": "{{name}}:アスㇽソㇱ ソモ アン。",
|
||||
"missingWiki": "{{name}}:Wiki ソモ アン。" } }
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"_untranslated": ["*"],
|
||||
"403": { "message": "権限ないよ(笑)", "right": "帰れ!", "left": "帰れ!" },
|
||||
"404": { "message": "ページないよ(笑)", "right": "帰れ!", "left": "帰れ!" },
|
||||
"500": { "message": "鯖でエラー出たって(嘲笑)", "right": "管理人に", "left": "聯絡を!" },
|
||||
"503": { "message": "鯖死んでるよ(泣)", "right": "管理人に", "left": "聯絡を!" },
|
||||
"imageAlt": "逃げたギター"
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"_untranslated": ["*"],
|
||||
"title": "ヘルプ", "search": "ヘルプ検索", "history": "ヘルプ改定履歴",
|
||||
"effectiveSince": "有効開始年月日", "empty": "該当する記事がありません.",
|
||||
"diffTitle": "ヘルプ 差分: {{title}}", "articleTitle": "{{title}} ヘルプ"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"_untranslated": ["*"],
|
||||
"status": {
|
||||
"ready": "登録可能", "error": "登録不可", "warning": "警告",
|
||||
"skipped": "スキップ", "created": "登録済み", "failed": "登録失敗"
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
"allHistory": "フㇱコアンペ オピッタ",
|
||||
"help": "ヤイカオパㇱ",
|
||||
"tags": "タㇰ",
|
||||
"master": "タㇰ オピッタ",
|
||||
"master": "オピッタ",
|
||||
"nicoLinks": "ニコニコ ウコテㇷ゚",
|
||||
"materials": "チカㇻペ",
|
||||
"suppressions": "アエアイカㇷ゚テㇷ゚",
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"_untranslated": ["*"],
|
||||
"labels": {
|
||||
"title": "タイトル", "content": "内容", "body": "本文",
|
||||
"lastUpdated": "最終更新", "thumbnail": "サムネール",
|
||||
"noThumbnail": "サムネイルなし", "thumbnailMissing": "サムネールなし",
|
||||
"parentPosts": "親投稿", "duration": "動画時間", "platform": "プラットフォーム",
|
||||
"code": "コード", "information": "情報", "relations": "関聯",
|
||||
"uploadedBy": "耕作者: ", "uploadedAt": "耕作日時: ", "link": "リンク: ",
|
||||
"originalPostedAt": "オリジナルの投稿日時: ", "originalCreatedAt": "オリジナルの作成日時",
|
||||
"brand": "ぼざクリ タグ広場", "since": "以降", "before": "より前",
|
||||
"tagSearch": "タグ検索...", "hideTags": "▲▲▲ タグ一覧を閉じる ▲▲▲",
|
||||
"showTags": "▼▼▼ タグ一覧を表示 ▼▼▼",
|
||||
"resultsCount": "{{count}} 件", "menu": "メニュー", "anonymous": "名もなきニジラー",
|
||||
"bot": "bot操作", "deprecatedSuffix": "(廃止)", "development": "開発モード"
|
||||
},
|
||||
"actions": {
|
||||
"update": "更新", "edit": "編輯", "add": "追加", "reset": "リセット",
|
||||
"cancel": "取消", "retry": "再試行", "skip": "スキップ", "random": "ランダム",
|
||||
"old": "< 古", "new": "新 >", "oldest": "(最古)", "latest": "(最新)",
|
||||
"diff": "差分", "restore": "復元", "show": "表示"
|
||||
},
|
||||
"messages": {
|
||||
"loading": "読み込み中...", "loadingEllipsis": "読込中……",
|
||||
"updated": "更新しました.", "updateFailed": "更新失敗", "failed": "失敗……",
|
||||
"checkInput": "入力を確認してください.", "checkInputModern": "入力を確認してください。",
|
||||
"checkInputContent": "入力内容を確認してください.", "reviewInput": "入力を見直してください.",
|
||||
"postSuccess": "投稿成功!", "postFailed": "投稿失敗", "resetChanges": "変更をリセットしますか?"
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,5 @@
|
||||
{
|
||||
"_untranslated": [
|
||||
"name", "postCount", "firstRecordedAt", "definitionUpdatedAt", "aliases",
|
||||
"parents", "firstRecordedColumn", "updatedAt", "empty"
|
||||
],
|
||||
{ "_untranslated": ["name", "postCount", "firstRecordedAt", "definitionUpdatedAt", "aliases",
|
||||
"parents", "firstRecordedColumn", "updatedAt", "empty"],
|
||||
"title": "タㇰ",
|
||||
"name": "名前",
|
||||
"postCount": "広場の投稿数",
|
||||
@@ -13,5 +10,4 @@
|
||||
"firstRecordedColumn": "最初の記載日時",
|
||||
"updatedAt": "更新日時",
|
||||
"history": "フㇱコアンペ",
|
||||
"empty": "結果ないよ(笑)"
|
||||
}
|
||||
"empty": "結果ないよ(笑)" }
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"_untranslated": ["*"],
|
||||
"newPage": "新規 Wiki ページ",
|
||||
"editPage": "Wiki ページを編輯",
|
||||
"history": "Wiki 変更履歴",
|
||||
"historyHeading": "Wiki 履歴", "change": "変更", "dateTime": "日時",
|
||||
"diffTitle": "Wiki 差分: {{title}}",
|
||||
"missingPage": "このページは存在しません。[新規作成してください]({{url}})。"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"403": { "message": "権限ないよ(笑)", "right": "帰れ!", "left": "帰れ!" },
|
||||
"404": { "message": "ページないよ(笑)", "right": "帰れ!", "left": "帰れ!" },
|
||||
"500": { "message": "鯖でエラー出たって(嘲笑)", "right": "管理人に", "left": "聯絡を!" },
|
||||
"503": { "message": "鯖死んでるよ(泣)", "right": "管理人に", "left": "聯絡を!" },
|
||||
"imageAlt": "逃げたギター"
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"title": "ヘルプ", "search": "ヘルプ検索", "history": "ヘルプ改定履歴",
|
||||
"effectiveSince": "有効開始年月日", "empty": "該当する記事がありません.",
|
||||
"diffTitle": "ヘルプ 差分: {{title}}", "articleTitle": "{{title}} ヘルプ"
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"status": {
|
||||
"ready": "登録可能", "error": "登録不可", "warning": "警告",
|
||||
"skipped": "スキップ", "created": "登録済み", "failed": "登録失敗"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"labels": {
|
||||
"title": "タイトル", "content": "内容", "body": "本文",
|
||||
"lastUpdated": "最終更新", "thumbnail": "サムネール",
|
||||
"noThumbnail": "サムネイルなし", "thumbnailMissing": "サムネールなし",
|
||||
"parentPosts": "親投稿", "duration": "動画時間", "platform": "プラットフォーム",
|
||||
"code": "コード", "information": "情報", "relations": "関聯",
|
||||
"uploadedBy": "耕作者: ", "uploadedAt": "耕作日時: ", "link": "リンク: ",
|
||||
"originalPostedAt": "オリジナルの投稿日時: ", "originalCreatedAt": "オリジナルの作成日時",
|
||||
"brand": "ぼざクリ タグ広場", "since": "以降", "before": "より前",
|
||||
"tagSearch": "タグ検索...", "hideTags": "▲▲▲ タグ一覧を閉じる ▲▲▲",
|
||||
"showTags": "▼▼▼ タグ一覧を表示 ▼▼▼",
|
||||
"resultsCount": "{{count}} 件", "menu": "メニュー", "anonymous": "名もなきニジラー",
|
||||
"bot": "bot操作", "deprecatedSuffix": "(廃止)", "development": "開発モード"
|
||||
},
|
||||
"actions": {
|
||||
"update": "更新", "edit": "編輯", "add": "追加", "reset": "リセット",
|
||||
"cancel": "取消", "retry": "再試行", "skip": "スキップ", "random": "ランダム",
|
||||
"old": "< 古", "new": "新 >", "oldest": "(最古)", "latest": "(最新)",
|
||||
"diff": "差分", "restore": "復元", "show": "表示"
|
||||
},
|
||||
"messages": {
|
||||
"loading": "読み込み中...", "loadingEllipsis": "読込中……",
|
||||
"updated": "更新しました.", "updateFailed": "更新失敗", "failed": "失敗……",
|
||||
"checkInput": "入力を確認してください.", "checkInputModern": "入力を確認してください。",
|
||||
"checkInputContent": "入力内容を確認してください.", "reviewInput": "入力を見直してください.",
|
||||
"postSuccess": "投稿成功!", "postFailed": "投稿失敗", "resetChanges": "変更をリセットしますか?"
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
{
|
||||
"title": "タグ",
|
||||
{ "title": "タグ",
|
||||
"name": "名前",
|
||||
"postCount": "広場の投稿数",
|
||||
"firstRecordedAt": "はじめて記載された日時",
|
||||
@@ -9,5 +8,4 @@
|
||||
"firstRecordedColumn": "最初の記載日時",
|
||||
"updatedAt": "更新日時",
|
||||
"history": "耕作履歴",
|
||||
"empty": "結果ないよ(笑)"
|
||||
}
|
||||
"empty": "結果ないよ(笑)" }
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"newPage": "新規 Wiki ページ",
|
||||
"editPage": "Wiki ページを編輯",
|
||||
"history": "Wiki 変更履歴",
|
||||
"historyHeading": "Wiki 履歴", "change": "変更", "dateTime": "日時",
|
||||
"diffTitle": "Wiki 差分: {{title}}",
|
||||
"missingPage": "このページは存在しません。[新規作成してください]({{url}})。"
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import { menuOutline } from '@/components/TopNav'
|
||||
import SectionTitle from '@/components/common/SectionTitle'
|
||||
import MainArea from '@/components/layout/MainArea'
|
||||
import { SITE_TITLE } from '@/config'
|
||||
import { useUITranslation } from '@/lib/useUITranslation'
|
||||
|
||||
import type { FC } from 'react'
|
||||
|
||||
@@ -13,13 +14,14 @@ import type { User } from '@/types'
|
||||
|
||||
|
||||
const MorePage: FC = () => {
|
||||
const { message } = useUITranslation ('shared')
|
||||
const menu = menuOutline (
|
||||
{ tag: null, wikiId: null, user: { } as User, pathName: location.pathname })
|
||||
|
||||
return (
|
||||
<MainArea className="md:flex">
|
||||
<Helmet>
|
||||
<title>{`メニュー | ${ SITE_TITLE }`}</title>
|
||||
<title>{message ('labels.menu').text} | {SITE_TITLE}</title>
|
||||
</Helmet>
|
||||
|
||||
{[...Array (Math.ceil (menu.length / 4)).keys ()].map (i => (
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useEffect, useMemo, useState } from 'react'
|
||||
import { useParams } from 'react-router-dom'
|
||||
|
||||
import { useLocale } from '@/lib/useLocale'
|
||||
import { useUITranslation } from '@/lib/useUITranslation'
|
||||
import TagLink from '@/components/TagLink'
|
||||
import FieldError from '@/components/common/FieldError'
|
||||
import FormField from '@/components/common/FormField'
|
||||
@@ -25,6 +26,7 @@ type DeerjikistFormField =
|
||||
|
||||
|
||||
const DeerjikistDetailPage: FC = () => {
|
||||
const { label } = useUITranslation ('shared')
|
||||
const locale = useLocale ()
|
||||
const { id } = useParams ()
|
||||
const tagId = String (id ?? '')
|
||||
@@ -54,12 +56,13 @@ const DeerjikistDetailPage: FC = () => {
|
||||
setData (await apiPut<Deerjikist[]> (`/tags/${ id }/deerjikists`, data, { locale }))
|
||||
qc.invalidateQueries ({ queryKey: tagsKeys.root })
|
||||
|
||||
toast ({ description: '更新しました.' })
|
||||
toast ({ description: label ('messages.updated') })
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
applyValidationError (e)
|
||||
toast ({ title: '更新失敗', description: '入力内容を確認してください.' })
|
||||
toast ({ title: label ('messages.updateFailed'),
|
||||
description: label ('messages.checkInputContent') })
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -106,7 +109,7 @@ const DeerjikistDetailPage: FC = () => {
|
||||
|
||||
{/* プラットフォーム */}
|
||||
<FormField
|
||||
label="プラットフォーム"
|
||||
label={label ('labels.platform')}
|
||||
messages={fieldErrors[`deerjikists${ i }Platform`]}>
|
||||
{({ describedBy, invalid }) => (
|
||||
<select
|
||||
@@ -131,7 +134,7 @@ const DeerjikistDetailPage: FC = () => {
|
||||
|
||||
{/* コード */}
|
||||
<FormField
|
||||
label="コード"
|
||||
label={label ('labels.code')}
|
||||
messages={fieldErrors[`deerjikists${ i }Code`]}>
|
||||
{({ describedBy, invalid }) => (
|
||||
<input
|
||||
@@ -166,7 +169,7 @@ const DeerjikistDetailPage: FC = () => {
|
||||
(disabled
|
||||
? 'text-gray-300 bg-gray-500'
|
||||
: 'text-white bg-blue-500'))}>
|
||||
更新
|
||||
{label ('actions.update')}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -11,6 +11,7 @@ import { SITE_TITLE } from '@/config'
|
||||
import { getHelpChanges, helpEditionDate } from '@/lib/help'
|
||||
import { localeLanguage } from '@/lib/locales'
|
||||
import { useLocale } from '@/lib/useLocale'
|
||||
import { useUITranslation } from '@/lib/useUITranslation'
|
||||
import { helpKeys } from '@/lib/queryKeys'
|
||||
import { LIST_LIMIT_OPTIONS } from '@/lib/settings'
|
||||
import { dateString } from '@/lib/utils'
|
||||
@@ -19,6 +20,7 @@ import type { FC } from 'react'
|
||||
|
||||
|
||||
const HelpChangesPage: FC = () => {
|
||||
const { message, label } = useUITranslation ('help')
|
||||
const language = localeLanguage (useLocale ())
|
||||
const [searchParams, setSearchParams] = useSearchParams ()
|
||||
const identifier = searchParams.get ('identifier') ?? ''
|
||||
@@ -51,12 +53,12 @@ const HelpChangesPage: FC = () => {
|
||||
return (
|
||||
<MainArea className="min-w-0 max-w-full">
|
||||
<Helmet>
|
||||
<title>{`ヘルプ改定履歴 | ${ SITE_TITLE }`}</title>
|
||||
<title>{message ('history').text} | {SITE_TITLE}</title>
|
||||
</Helmet>
|
||||
<div className="flex flex-wrap items-center justify-between gap-3">
|
||||
<PageTitle>ヘルプ改定履歴</PageTitle>
|
||||
<PageTitle>{label ('history')}</PageTitle>
|
||||
<label className="flex items-center gap-2 text-xs text-muted-foreground">
|
||||
<span>件数</span>
|
||||
<span>{label ('common:countLabel')}</span>
|
||||
<select value={limit} onChange={ev => handleLimit (ev.target.value)}
|
||||
className="rounded border border-border bg-background px-2 py-1 text-sm
|
||||
text-foreground">
|
||||
@@ -67,7 +69,7 @@ const HelpChangesPage: FC = () => {
|
||||
</div>
|
||||
|
||||
{isLoading
|
||||
? <p className="mt-4">読み込み中...</p>
|
||||
? <p className="mt-4">{label ('shared:messages.loading')}</p>
|
||||
: (error
|
||||
? <p className="mt-4 text-red-600" role="alert">{error.message}</p>
|
||||
: (
|
||||
@@ -77,9 +79,9 @@ const HelpChangesPage: FC = () => {
|
||||
border-collapse">
|
||||
<thead className="border-b-2 border-black dark:border-white">
|
||||
<tr>
|
||||
<th className="p-2 text-left">差分</th>
|
||||
<th className="p-2 text-left">タイトル</th>
|
||||
<th className="p-2 text-left">有効開始年月日</th>
|
||||
<th className="p-2 text-left">{label ('shared:actions.diff')}</th>
|
||||
<th className="p-2 text-left">{label ('shared:labels.title')}</th>
|
||||
<th className="p-2 text-left">{label ('effectiveSince')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -97,7 +99,7 @@ const HelpChangesPage: FC = () => {
|
||||
<td className="p-2">
|
||||
{entry.pred != null && (
|
||||
<PrefetchLink to={diffTo}>
|
||||
差分
|
||||
{label ('shared:actions.diff')}
|
||||
</PrefetchLink>)}
|
||||
</td>
|
||||
<td className="p-2">
|
||||
|
||||
@@ -12,6 +12,7 @@ import { getHelpArticle,
|
||||
resolveHelpLanguage } from '@/lib/help'
|
||||
import { localeLanguage } from '@/lib/locales'
|
||||
import { useLocale } from '@/lib/useLocale'
|
||||
import { useUITranslation } from '@/lib/useUITranslation'
|
||||
import { dateString } from '@/lib/utils'
|
||||
import { helpKeys } from '@/lib/queryKeys'
|
||||
import NotFound from '@/pages/NotFound'
|
||||
@@ -20,6 +21,7 @@ import type { FC } from 'react'
|
||||
|
||||
|
||||
const HelpDetailPage: FC = () => {
|
||||
const { message, label } = useUITranslation ('help')
|
||||
const locale = useLocale ()
|
||||
const { identifier = '' } = useParams ()
|
||||
const location = useLocation ()
|
||||
@@ -71,7 +73,9 @@ const HelpDetailPage: FC = () => {
|
||||
return (
|
||||
<MainArea>
|
||||
<Helmet>
|
||||
<title>{current?.title || identifier} ヘルプ | {SITE_TITLE}</title>
|
||||
<title>
|
||||
{message ('articleTitle', { title: current?.title || identifier }).text} | {SITE_TITLE}
|
||||
</title>
|
||||
</Helmet>
|
||||
|
||||
{requestedEdition && (
|
||||
@@ -81,9 +85,9 @@ const HelpDetailPage: FC = () => {
|
||||
? (
|
||||
<PrefetchLink
|
||||
to={`/help/${ encodeURIComponent (identifier) }?edition=${ pred }`}>
|
||||
< 古
|
||||
{label ('shared:actions.old')}
|
||||
</PrefetchLink>)
|
||||
: '(最古)'}
|
||||
: label ('shared:actions.oldest')}
|
||||
|
||||
<span>
|
||||
{editionDate != null && dateString (editionDate, 'hour')}
|
||||
@@ -94,13 +98,13 @@ const HelpDetailPage: FC = () => {
|
||||
? (
|
||||
<PrefetchLink
|
||||
to={`/help/${ encodeURIComponent (identifier) }?edition=${ succ }`}>
|
||||
新 >
|
||||
{label ('shared:actions.new')}
|
||||
</PrefetchLink>)
|
||||
: '(最新)'}
|
||||
: label ('shared:actions.latest')}
|
||||
</div>)}
|
||||
|
||||
<article lang={language} className="prose dark:prose-invert mx-auto p-4">
|
||||
{Content ? <Content/> : <div>Loading...</div>}
|
||||
{Content ? <Content/> : <div>{label ('common:loading')}</div>}
|
||||
</article>
|
||||
</MainArea>)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import { SITE_TITLE } from '@/config'
|
||||
import { getHelpArticle, getHelpDiff, resolveHelpLanguage } from '@/lib/help'
|
||||
import { localeLanguage } from '@/lib/locales'
|
||||
import { useLocale } from '@/lib/useLocale'
|
||||
import { useUITranslation } from '@/lib/useUITranslation'
|
||||
import { helpKeys } from '@/lib/queryKeys'
|
||||
import { cn } from '@/lib/utils'
|
||||
import NotFound from '@/pages/NotFound'
|
||||
@@ -19,6 +20,7 @@ const markers = { added: '+ ', removed: '- ', unchanged: ' ' }
|
||||
|
||||
|
||||
const HelpDiffPage: FC = () => {
|
||||
const { message } = useUITranslation ('help')
|
||||
const locale = useLocale ()
|
||||
const { identifier = '' } = useParams ()
|
||||
const [searchParams] = useSearchParams ()
|
||||
@@ -47,7 +49,7 @@ const HelpDiffPage: FC = () => {
|
||||
return (
|
||||
<MainArea className="min-w-0 max-w-full">
|
||||
<Helmet>
|
||||
<title>{`ヘルプ 差分: ${ title } | ${ SITE_TITLE }`}</title>
|
||||
<title>{message ('diffTitle', { title }).text} | {SITE_TITLE}</title>
|
||||
</Helmet>
|
||||
<PageTitle>{title}</PageTitle>
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import { compareHelpEditions,
|
||||
normaliseHelpSearchText } from '@/lib/help'
|
||||
import { localeLanguage } from '@/lib/locales'
|
||||
import { useLocale } from '@/lib/useLocale'
|
||||
import { useUITranslation } from '@/lib/useUITranslation'
|
||||
import { helpKeys } from '@/lib/queryKeys'
|
||||
import { LIST_LIMIT_OPTIONS } from '@/lib/settings'
|
||||
import { dateString, inputClass } from '@/lib/utils'
|
||||
@@ -32,6 +33,7 @@ const defaultDirection: Record<HelpOrderField, 'asc' | 'desc'> = {
|
||||
|
||||
|
||||
const HelpSearchPage: FC = () => {
|
||||
const { message, label } = useUITranslation ('help')
|
||||
const language = localeLanguage (useLocale ())
|
||||
const [searchParams, setSearchParams] = useSearchParams ()
|
||||
const pageValue = searchParams.get ('page') ?? '1'
|
||||
@@ -111,14 +113,14 @@ const HelpSearchPage: FC = () => {
|
||||
return (
|
||||
<MainArea className="min-w-0 max-w-full">
|
||||
<Helmet>
|
||||
<title>{`ヘルプ | ${ SITE_TITLE }`}</title>
|
||||
<title>{message ('title').text} | {SITE_TITLE}</title>
|
||||
</Helmet>
|
||||
|
||||
<div className="max-w-xl">
|
||||
<div className="flex flex-wrap items-center justify-between gap-3">
|
||||
<PageTitle>ヘルプ検索</PageTitle>
|
||||
<PageTitle>{label ('search')}</PageTitle>
|
||||
<label className="flex items-center gap-2 text-xs text-muted-foreground">
|
||||
<span>件数</span>
|
||||
<span>{label ('common:countLabel')}</span>
|
||||
<select value={limit} onChange={ev => handleLimit (ev.target.value)}
|
||||
className="rounded border border-border bg-background px-2 py-1 text-sm
|
||||
text-foreground">
|
||||
@@ -128,19 +130,19 @@ const HelpSearchPage: FC = () => {
|
||||
</label>
|
||||
</div>
|
||||
<form onSubmit={handleSearch} className="space-y-2">
|
||||
<FormField label="タイトル">
|
||||
<FormField label={label ('shared:labels.title')}>
|
||||
{({ invalid }) => (
|
||||
<input type="text"
|
||||
aria-label="タイトル"
|
||||
aria-label={message ('shared:labels.title').text}
|
||||
value={title}
|
||||
onChange={e => setTitle (e.target.value)}
|
||||
className={inputClass (invalid)}/>)}
|
||||
</FormField>
|
||||
|
||||
<FormField label="内容">
|
||||
<FormField label={label ('shared:labels.content')}>
|
||||
{({ invalid }) => (
|
||||
<input type="text"
|
||||
aria-label="内容"
|
||||
aria-label={message ('shared:labels.content').text}
|
||||
value={text}
|
||||
onChange={e => setText (e.target.value)}
|
||||
className={inputClass (invalid)}/>)}
|
||||
@@ -149,19 +151,21 @@ const HelpSearchPage: FC = () => {
|
||||
<div className="py-3">
|
||||
<button type="submit"
|
||||
className="bg-blue-500 text-white px-4 py-2 rounded">
|
||||
検索
|
||||
{label ('common:search')}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{loading
|
||||
? <p className="mt-4">読み込み中...</p>
|
||||
? <p className="mt-4">{label ('shared:messages.loading')}</p>
|
||||
: (error
|
||||
? <p className="mt-4 text-red-600" role="alert">{error.message}</p>
|
||||
: (
|
||||
<div className="mt-4">
|
||||
<p className="mb-2 text-sm" aria-live="polite">{results.length} 件</p>
|
||||
<p className="mb-2 text-sm" aria-live="polite">
|
||||
{label ('shared:labels.resultsCount', { count: results.length })}
|
||||
</p>
|
||||
<div className="min-w-0 max-w-full overflow-x-auto">
|
||||
<table className="table-auto w-full min-w-[640px] whitespace-nowrap
|
||||
border-collapse text-gray-900 dark:text-gray-100">
|
||||
@@ -170,14 +174,14 @@ const HelpSearchPage: FC = () => {
|
||||
<th className="p-2 text-left">
|
||||
<SortHeader<HelpOrderField>
|
||||
by="title"
|
||||
label="タイトル"
|
||||
label={label ('shared:labels.title')}
|
||||
currentOrder={order}
|
||||
defaultDirection={defaultDirection}/>
|
||||
</th>
|
||||
<th className="p-2 text-left">
|
||||
<SortHeader<HelpOrderField>
|
||||
by="updated_at"
|
||||
label="有効開始年月日"
|
||||
label={label ('effectiveSince')}
|
||||
currentOrder={order}
|
||||
defaultDirection={defaultDirection}/>
|
||||
</th>
|
||||
@@ -206,7 +210,7 @@ const HelpSearchPage: FC = () => {
|
||||
</table>
|
||||
</div>
|
||||
{totalPages > 0 && <Pagination page={page} totalPages={totalPages}/>}
|
||||
{results.length === 0 && <p className="mt-4">該当する記事がありません.</p>}
|
||||
{results.length === 0 && <p className="mt-4">{label ('empty')}</p>}
|
||||
</div>))}
|
||||
</MainArea>)
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import LocalisedText from '@/components/LocalisedText'
|
||||
import PrefetchLink from '@/components/PrefetchLink'
|
||||
import SortHeader from '@/components/SortHeader'
|
||||
import TagLink from '@/components/TagLink'
|
||||
import TranslatedText from '@/components/TranslatedText'
|
||||
import DateTimeField from '@/components/common/DateTimeField'
|
||||
import FormField from '@/components/common/FormField'
|
||||
import PageTitle from '@/components/common/PageTitle'
|
||||
@@ -76,8 +75,7 @@ const parseOrder = (value: string | null): FetchTagsOrder | null => {
|
||||
|
||||
const TagListPage: FC = () => {
|
||||
const locale = useLocale ()
|
||||
const { message } = useUITranslation ('tags')
|
||||
const label = (key: string) => <TranslatedText namespace="tags" messageKey={key}/>
|
||||
const { message, label } = useUITranslation ('tags')
|
||||
const behaviourSettings = useClientBehaviourSettings ()
|
||||
const animationMode = behaviourSettings.animation ?? 'normal'
|
||||
const location = useLocation ()
|
||||
@@ -265,7 +263,9 @@ const TagListPage: FC = () => {
|
||||
className={inputClass (invalid)}>
|
||||
<option value=""> </option>
|
||||
{CATEGORIES.map (cat => (
|
||||
<option key={cat} value={cat}
|
||||
<option
|
||||
key={cat}
|
||||
value={cat}
|
||||
lang={message (`common:categories.${ cat }`).language}>
|
||||
{message (`common:categories.${ cat }`).text}
|
||||
</option>))}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Helmet } from 'react-helmet-async'
|
||||
import { useLocation, useParams } from 'react-router-dom'
|
||||
|
||||
import { useLocale, useLocaleNavigate } from '@/lib/useLocale'
|
||||
import { useUITranslation } from '@/lib/useUITranslation'
|
||||
import PostList from '@/components/PostList'
|
||||
import PrefetchLink from '@/components/PrefetchLink'
|
||||
import TagLink from '@/components/TagLink'
|
||||
@@ -25,6 +26,7 @@ import type { Tag } from '@/types'
|
||||
|
||||
|
||||
const WikiDetailPage: FC = () => {
|
||||
const { message, label } = useUITranslation ('shared')
|
||||
const locale = useLocale ()
|
||||
const params = useParams ()
|
||||
const title = params.title ?? ''
|
||||
@@ -128,7 +130,8 @@ const WikiDetailPage: FC = () => {
|
||||
return (
|
||||
<MainArea>
|
||||
<Helmet>
|
||||
<title>{`${ effectiveTitle }${ deprecated ? '(廃止)' : '' } Wiki | ${ SITE_TITLE }`}</title>
|
||||
<title>{`${ effectiveTitle }${ deprecated
|
||||
? message ('labels.deprecatedSuffix').text : '' } Wiki | ${ SITE_TITLE }`}</title>
|
||||
{!(wikiPage?.body) && <meta name="robots" content="noindex"/>}
|
||||
</Helmet>
|
||||
|
||||
@@ -137,15 +140,15 @@ const WikiDetailPage: FC = () => {
|
||||
border border-gray-700 rounded px-2 py-1 mb-4">
|
||||
{wikiPage.pred ? (
|
||||
<PrefetchLink to={`/wiki/${ encodeURIComponent (title) }?version=${ wikiPage.pred }`}>
|
||||
< 古
|
||||
</PrefetchLink>) : '(最古)'}
|
||||
{label ('actions.old')}
|
||||
</PrefetchLink>) : label ('actions.oldest')}
|
||||
|
||||
<span>{wikiPage.updatedAt}</span>
|
||||
|
||||
{wikiPage.succ ? (
|
||||
<PrefetchLink to={`/wiki/${ encodeURIComponent (title) }?version=${ wikiPage.succ }`}>
|
||||
新 >
|
||||
</PrefetchLink>) : '(最新)'}
|
||||
{label ('actions.new')}
|
||||
</PrefetchLink>) : label ('actions.latest')}
|
||||
</div>)}
|
||||
|
||||
<article className="prose dark:prose-invert mx-auto p-4">
|
||||
@@ -156,14 +159,14 @@ const WikiDetailPage: FC = () => {
|
||||
withWiki={false}
|
||||
withCount={false}
|
||||
{...(version && { to: `/wiki/${ encodeURIComponent (title) }` })}/>
|
||||
{deprecated && <span>(廃止)</span>}
|
||||
{deprecated && label ('labels.deprecatedSuffix')}
|
||||
</h1>
|
||||
{waiting ? <div>Loading...</div> : <WikiBody title={title} body={wikiPage?.body}/>}
|
||||
{waiting ? label ('common:loading') : <WikiBody title={title} body={wikiPage?.body}/>}
|
||||
|
||||
{(!(version) && posts.length > 0) && (
|
||||
<div className="not-prose">
|
||||
<TabGroup>
|
||||
<Tab name="広場">
|
||||
<Tab name={message ('navigation:posts').text}>
|
||||
<PostList posts={posts}/>
|
||||
</Tab>
|
||||
</TabGroup>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Helmet } from 'react-helmet-async'
|
||||
import { useLocation, useParams } from 'react-router-dom'
|
||||
|
||||
import { useLocale } from '@/lib/useLocale'
|
||||
import { useUITranslation } from '@/lib/useUITranslation'
|
||||
import PageTitle from '@/components/common/PageTitle'
|
||||
import MainArea from '@/components/layout/MainArea'
|
||||
import { SITE_TITLE } from '@/config'
|
||||
@@ -15,6 +16,7 @@ import type { WikiPageDiff } from '@/types'
|
||||
|
||||
|
||||
const WikiDiffPage: FC = () => {
|
||||
const { message, label } = useUITranslation ('wiki')
|
||||
const locale = useLocale ()
|
||||
const { id } = useParams ()
|
||||
|
||||
@@ -26,7 +28,8 @@ const WikiDiffPage: FC = () => {
|
||||
const from = query.get ('from')
|
||||
const to = query.get ('to')
|
||||
const displayTitle = diff
|
||||
? `${ diff.title }${ diff.deprecatedAt != null ? '(廃止)' : '' }`
|
||||
? `${ diff.title }${ diff.deprecatedAt != null
|
||||
? message ('shared:labels.deprecatedSuffix').text : '' }`
|
||||
: ''
|
||||
|
||||
useEffect (() => {
|
||||
@@ -38,7 +41,7 @@ const WikiDiffPage: FC = () => {
|
||||
return (
|
||||
<MainArea>
|
||||
<Helmet>
|
||||
<title>{`Wiki 差分: ${ displayTitle } | ${ SITE_TITLE }`}</title>
|
||||
<title>{message ('diffTitle', { title: displayTitle }).text} | {SITE_TITLE}</title>
|
||||
</Helmet>
|
||||
<PageTitle>{displayTitle}</PageTitle>
|
||||
<div className="prose mx-auto p-4">
|
||||
@@ -49,7 +52,7 @@ const WikiDiffPage: FC = () => {
|
||||
d.type === 'removed' && 'bg-red-200 dark:bg-red-800')}>
|
||||
{d.content}
|
||||
</p>)))
|
||||
: 'Loading...'}
|
||||
: label ('common:loading')}
|
||||
</div>
|
||||
</MainArea>)
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import MdEditor from 'react-markdown-editor-lite'
|
||||
import { useParams } from 'react-router-dom'
|
||||
|
||||
import { useLocale, useLocaleNavigate } from '@/lib/useLocale'
|
||||
import { useUITranslation } from '@/lib/useUITranslation'
|
||||
import FieldError from '@/components/common/FieldError'
|
||||
import FormField from '@/components/common/FormField'
|
||||
import MainArea from '@/components/layout/MainArea'
|
||||
@@ -37,6 +38,7 @@ type WikiFormField = 'title' | 'body'
|
||||
|
||||
|
||||
const WikiEditPage: FC<Props> = ({ user }) => {
|
||||
const { message, label } = useUITranslation ('wiki')
|
||||
const locale = useLocale ()
|
||||
const editable = canEditContent (user)
|
||||
|
||||
@@ -66,13 +68,14 @@ const WikiEditPage: FC<Props> = ({ user }) => {
|
||||
qc.setQueryData (wikiKeys.show (title, { }, locale),
|
||||
(prev: WikiPage) => ({ ...prev, title, body }))
|
||||
qc.invalidateQueries ({ queryKey: wikiKeys.root })
|
||||
toast ({ title: '投稿成功!' })
|
||||
toast ({ title: label ('shared:messages.postSuccess') })
|
||||
navigate (`/wiki/${ title }`)
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
applyValidationError (e)
|
||||
toast ({ title: '投稿失敗', description: '入力を確認してください。' })
|
||||
toast ({ title: label ('shared:messages.postFailed'),
|
||||
description: label ('shared:messages.checkInputModern') })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,18 +98,18 @@ const WikiEditPage: FC<Props> = ({ user }) => {
|
||||
return (
|
||||
<MainArea>
|
||||
<Helmet>
|
||||
<title>{`Wiki ページを編輯 | ${ SITE_TITLE }`}</title>
|
||||
<title>{`${ message ('editPage').text } | ${ SITE_TITLE }`}</title>
|
||||
</Helmet>
|
||||
<div className="max-w-xl mx-auto p-4 space-y-4">
|
||||
<h1 className="text-2xl font-bold mb-2">Wiki ページを編輯</h1>
|
||||
<h1 className="text-2xl font-bold mb-2">{label ('editPage')}</h1>
|
||||
|
||||
{loading ? 'Loading...' : (
|
||||
{loading ? label ('common:loading') : (
|
||||
<>
|
||||
<FieldError messages={baseErrors}/>
|
||||
|
||||
{/* タイトル */}
|
||||
{/* TODO: タグ補完 */}
|
||||
<FormField label="タイトル" messages={fieldErrors.title}>
|
||||
<FormField label={label ('shared:labels.title')} messages={fieldErrors.title}>
|
||||
{({ describedBy, invalid }) => (
|
||||
<input type="text"
|
||||
value={title}
|
||||
@@ -117,7 +120,7 @@ const WikiEditPage: FC<Props> = ({ user }) => {
|
||||
</FormField>
|
||||
|
||||
{/* 本文 */}
|
||||
<FormField label="本文" messages={fieldErrors.body}>
|
||||
<FormField label={label ('shared:labels.body')} messages={fieldErrors.body}>
|
||||
{() => (
|
||||
<MdEditor value={body}
|
||||
style={{ height: '500px' }}
|
||||
@@ -129,7 +132,7 @@ const WikiEditPage: FC<Props> = ({ user }) => {
|
||||
{/* 送信 */}
|
||||
<button onClick={handleSubmit}
|
||||
className="px-4 py-2 bg-blue-600 text-white rounded disabled:bg-gray-400">
|
||||
編輯
|
||||
{label ('shared:actions.edit')}
|
||||
</button>
|
||||
</>)}
|
||||
</div>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Helmet } from 'react-helmet-async'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
|
||||
import { useLocale } from '@/lib/useLocale'
|
||||
import { useUITranslation } from '@/lib/useUITranslation'
|
||||
import PrefetchLink from '@/components/PrefetchLink'
|
||||
import PageTitle from '@/components/common/PageTitle'
|
||||
import MainArea from '@/components/layout/MainArea'
|
||||
@@ -16,6 +17,7 @@ import type { WikiPageChange } from '@/types'
|
||||
|
||||
|
||||
const WikiHistoryPage: FC = () => {
|
||||
const { message, label } = useUITranslation ('wiki')
|
||||
const locale = useLocale ()
|
||||
const [changes, setChanges] = useState<WikiPageChange[]> ([])
|
||||
|
||||
@@ -32,18 +34,18 @@ const WikiHistoryPage: FC = () => {
|
||||
return (
|
||||
<MainArea>
|
||||
<Helmet>
|
||||
<title>{`Wiki 変更履歴 | ${ SITE_TITLE }`}</title>
|
||||
<title>{message ('history').text} | {SITE_TITLE}</title>
|
||||
</Helmet>
|
||||
|
||||
<PageTitle>Wiki 履歴</PageTitle>
|
||||
<PageTitle>{label ('historyHeading')}</PageTitle>
|
||||
|
||||
<table className="table-auto w-full border-collapse">
|
||||
<thead className="border-b-2 border-black dark:border-white">
|
||||
<tr>
|
||||
<th></th>
|
||||
<th className="p-2 text-left">タイトル</th>
|
||||
<th className="p-2 text-left">変更</th>
|
||||
<th className="p-2 text-left">日時</th>
|
||||
<th className="p-2 text-left">{label ('shared:labels.title')}</th>
|
||||
<th className="p-2 text-left">{label ('change')}</th>
|
||||
<th className="p-2 text-left">{label ('dateTime')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -53,7 +55,7 @@ const WikiHistoryPage: FC = () => {
|
||||
{change.pred != null && (
|
||||
<PrefetchLink
|
||||
to={`/wiki/${ change.wikiPage.id }/diff?from=${ change.pred }&to=${ change.revisionId }`}>
|
||||
差分
|
||||
{label ('shared:actions.diff')}
|
||||
</PrefetchLink>)}
|
||||
</td>
|
||||
<td className="p-2">
|
||||
@@ -61,10 +63,10 @@ const WikiHistoryPage: FC = () => {
|
||||
to={`/wiki/${ encodeURIComponent (change.wikiPage.title) }?version=${ change.revisionId }`}>
|
||||
{change.wikiPage.title}
|
||||
</PrefetchLink>
|
||||
{change.wikiPage.deprecatedAt != null && <span>(廃止)</span>}
|
||||
{change.wikiPage.deprecatedAt != null && label ('shared:labels.deprecatedSuffix')}
|
||||
</td>
|
||||
<td className="p-2">
|
||||
{change.pred == null ? '新規' : '更新'}
|
||||
{change.pred == null ? label ('navigation:create') : label ('shared:actions.update')}
|
||||
</td>
|
||||
<td className="p-2">
|
||||
<PrefetchLink to={`/users/${ change.user.id }`}>
|
||||
|
||||
@@ -7,6 +7,7 @@ import { useLocation } from 'react-router-dom'
|
||||
import 'react-markdown-editor-lite/lib/index.css'
|
||||
|
||||
import { useLocale, useLocaleNavigate } from '@/lib/useLocale'
|
||||
import { useUITranslation } from '@/lib/useUITranslation'
|
||||
import FieldError from '@/components/common/FieldError'
|
||||
import FormField from '@/components/common/FormField'
|
||||
import MainArea from '@/components/layout/MainArea'
|
||||
@@ -36,6 +37,7 @@ type WikiFormField = 'title' | 'body'
|
||||
|
||||
|
||||
const WikiNewPage: FC<Props> = ({ user }) => {
|
||||
const { message, label } = useUITranslation ('wiki')
|
||||
const locale = useLocale ()
|
||||
const editable = canEditContent (user)
|
||||
|
||||
@@ -67,13 +69,14 @@ const WikiNewPage: FC<Props> = ({ user }) => {
|
||||
await Promise.all ([
|
||||
qc.invalidateQueries ({ queryKey: wikiKeys.root }),
|
||||
qc.invalidateQueries ({ queryKey: tagsKeys.root })])
|
||||
toast ({ title: '投稿成功!' })
|
||||
toast ({ title: label ('shared:messages.postSuccess') })
|
||||
navigate (`/wiki/${ encodeURIComponent (data.title) }`)
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
applyValidationError (e)
|
||||
toast ({ title: '投稿失敗', description: '入力を確認してください。' })
|
||||
toast ({ title: label ('shared:messages.postFailed'),
|
||||
description: label ('shared:messages.checkInputModern') })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,15 +86,15 @@ const WikiNewPage: FC<Props> = ({ user }) => {
|
||||
return (
|
||||
<MainArea>
|
||||
<Helmet>
|
||||
<title>{`新規 Wiki ページ | ${ SITE_TITLE }`}</title>
|
||||
<title>{`${ message ('newPage').text } | ${ SITE_TITLE }`}</title>
|
||||
</Helmet>
|
||||
<div className="max-w-xl mx-auto p-4 space-y-4">
|
||||
<h1 className="text-2xl font-bold mb-2">新規 Wiki ページ</h1>
|
||||
<h1 className="text-2xl font-bold mb-2">{label ('newPage')}</h1>
|
||||
<FieldError messages={baseErrors}/>
|
||||
|
||||
{/* タイトル */}
|
||||
{/* TODO: タグ補完 */}
|
||||
<FormField label="タイトル" messages={fieldErrors.title}>
|
||||
<FormField label={label ('shared:labels.title')} messages={fieldErrors.title}>
|
||||
{({ describedBy, invalid }) => (
|
||||
<input type="text"
|
||||
value={title}
|
||||
@@ -102,7 +105,7 @@ const WikiNewPage: FC<Props> = ({ user }) => {
|
||||
</FormField>
|
||||
|
||||
{/* 本文 */}
|
||||
<FormField label="本文" messages={fieldErrors.body}>
|
||||
<FormField label={label ('shared:labels.body')} messages={fieldErrors.body}>
|
||||
{() => (
|
||||
<MdEditor value={body}
|
||||
style={{ height: '500px' }}
|
||||
@@ -114,7 +117,7 @@ const WikiNewPage: FC<Props> = ({ user }) => {
|
||||
{/* 送信 */}
|
||||
<button onClick={handleSubmit}
|
||||
className="px-4 py-2 bg-blue-600 text-white rounded disabled:bg-gray-400">
|
||||
追加
|
||||
{label ('shared:actions.add')}
|
||||
</button>
|
||||
</div>
|
||||
</MainArea>)
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useEffect, useState } from 'react'
|
||||
import { Helmet } from 'react-helmet-async'
|
||||
|
||||
import { useLocale } from '@/lib/useLocale'
|
||||
import { useUITranslation } from '@/lib/useUITranslation'
|
||||
import PrefetchLink from '@/components/PrefetchLink'
|
||||
import FormField from '@/components/common/FormField'
|
||||
import PageTitle from '@/components/common/PageTitle'
|
||||
@@ -16,6 +17,7 @@ import type { WikiPage } from '@/types'
|
||||
|
||||
|
||||
const WikiSearchPage: FC = () => {
|
||||
const { label } = useUITranslation ('shared')
|
||||
const locale = useLocale ()
|
||||
const [title, setTitle] = useState ('')
|
||||
const [text, setText] = useState ('')
|
||||
@@ -46,7 +48,7 @@ const WikiSearchPage: FC = () => {
|
||||
<PageTitle>Wiki</PageTitle>
|
||||
<form onSubmit={handleSearch} className="space-y-2">
|
||||
{/* タイトル */}
|
||||
<FormField label="タイトル">
|
||||
<FormField label={label ('labels.title')}>
|
||||
{({ invalid }) => (
|
||||
<input type="text"
|
||||
value={title}
|
||||
@@ -55,7 +57,7 @@ const WikiSearchPage: FC = () => {
|
||||
</FormField>
|
||||
|
||||
{/* 内容 */}
|
||||
<FormField label="内容">
|
||||
<FormField label={label ('labels.content')}>
|
||||
{({ invalid }) => (
|
||||
<input type="text"
|
||||
value={text}
|
||||
@@ -67,7 +69,7 @@ const WikiSearchPage: FC = () => {
|
||||
<div className="py-3">
|
||||
<button type="submit"
|
||||
className="bg-blue-500 text-white px-4 py-2 rounded">
|
||||
検索
|
||||
{label ('common:search')}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -77,8 +79,8 @@ const WikiSearchPage: FC = () => {
|
||||
<table className="table-auto w-full border-collapse">
|
||||
<thead className="border-b-2 border-black dark:border-white">
|
||||
<tr>
|
||||
<th className="p-2 text-left">タイトル</th>
|
||||
<th className="p-2 text-left">最終更新</th>
|
||||
<th className="p-2 text-left">{label ('labels.title')}</th>
|
||||
<th className="p-2 text-left">{label ('labels.lastUpdated')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -89,7 +91,7 @@ const WikiSearchPage: FC = () => {
|
||||
<PrefetchLink to={`/wiki/${ encodeURIComponent (page.title) }`}>
|
||||
{page.title}
|
||||
</PrefetchLink>
|
||||
{page.deprecatedAt != null && <span>(廃止)</span>}
|
||||
{page.deprecatedAt != null && label ('labels.deprecatedSuffix')}
|
||||
</td>
|
||||
<td className="p-2">
|
||||
{dateString (page.updatedAt)}
|
||||
|
||||
新しいイシューから参照
ユーザーをブロックする