アイヌ語追加 (#428) (#433)

Reviewed-on: #433
Co-authored-by: miteruzo <miteruzo@naver.com>
このコミットはプルリクエスト #433 でマージされました。
このコミットが含まれているのは:
2026-10-20 20:44:14 +09:00
committed by みてるぞ
コミット 084a090cab
294個のファイルの変更、13978行の追加、3461行の削除
+66 -57
ファイルの表示
@@ -1,19 +1,22 @@
import { AnimatePresence, motion } from 'framer-motion'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Helmet } from 'react-helmet-async'
import { ChevronRight } from 'lucide-react'
import { useLocation, useNavigate } from 'react-router-dom'
import { useLocation } from 'react-router-dom'
import { useLocale, useLocaleNavigate } from '@/lib/useLocale'
import PageTitle from '@/components/common/PageTitle'
import MainArea from '@/components/layout/MainArea'
import PrefetchLink from '@/components/PrefetchLink'
import PostThumbnailPreview from '@/components/posts/PostThumbnailPreview'
import PostImportRowForm from '@/components/posts/import/PostImportRowForm'
import PostImportRowSummary from '@/components/posts/import/PostImportRowSummary'
import { renderPostImportMessage } from '@/components/posts/import/postImportRowStatus'
import { Button } from '@/components/ui/button'
import { toast } from '@/components/ui/use-toast'
import { SITE_TITLE } from '@/config'
import PageHead from '@/components/common/PageHead'
import { apiGet, apiPost, isApiError } from '@/lib/api'
import { translateUIMessage } from '@/lib/i18n'
import { useUITranslation } from '@/lib/useUITranslation'
import useDialogue from '@/lib/dialogues/useDialogue'
import { parsePostNewReviewUrls } from '@/lib/postNewQueryState'
import { validateImportSource } from '@/lib/postImportSourceValidation'
@@ -99,8 +102,9 @@ type ExistingSkippedRowsProps = {
id: string
rows: PostImportRow[] }
const DUPLICATE_URL_MESSAGE = 'URL が重複しています.'
const BULK_PROCESSING_FAILED_MESSAGE = '登録中にエラーが発生しました.'
const DUPLICATE_URL_MESSAGE = translateUIMessage ('validation.duplicateUrl', 'imports', 'ja').text
const BULK_PROCESSING_FAILED_MESSAGE =
translateUIMessage ('validation.processingFailed', 'imports', 'ja').text
const emptyAttributes = () => ({
title: '',
@@ -595,10 +599,12 @@ const editableRowDirty = (row: PostImportRow): boolean =>
const PostImportReviewPage: FC<Props> = ({ user }) => {
const locale = useLocale ()
const { message, label } = useUITranslation ('imports')
const editable = canEditContent (user)
const dialogue = useDialogue ()
const location = useLocation ()
const navigate = useNavigate ()
const navigate = useLocaleNavigate ()
const behaviourSettings = useClientBehaviourSettings ()
const animationMode = behaviourSettings.animation ?? 'normal'
const existingRowsTransition =
@@ -608,7 +614,8 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
? { duration: .08, ease: 'linear' as const }
: { duration: .2, ease: 'easeOut' as const }
const [rows, setRows] = useState<PostImportRow[] | null> (null)
const [rows, setRows] = useState<PostImportRow[] | null> (
() => buildInitialRows (parsePostNewReviewUrls (location.search)))
const [metadataLoading, setMetadataLoading] = useState (false)
const [submitting, setSubmitting] = useState (false)
const [busyRowIds, setBusyRowIds] = useState<Set<number>> (new Set ())
@@ -649,9 +656,10 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
const finishImport = useCallback (() => {
allowNextNavigation ()
clearPostImportSourceDraft (message =>
toast ({ title: '入力内容を削除できませんでした', description: message }))
toast ({ title: label ('review.clearFailed'),
description: renderPostImportMessage (message, locale) }))
navigate ('/posts')
}, [allowNextNavigation, navigate])
}, [allowNextNavigation, navigate, label, locale])
useEffect (() => {
const urls = parsePostNewReviewUrls (location.search)
@@ -696,7 +704,7 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
try
{
const preview = await apiGet<PostMetadataResponse> ('/posts/metadata', {
const preview = await apiGet<PostMetadataResponse> ('/posts/metadata', { locale,
params: { url: currentRow.url },
signal: controller.signal })
if (!(active) || metadataSequenceRef.current !== sequence)
@@ -770,7 +778,7 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
active = false
controllers.forEach (controller => controller.abort ())
}
}, [commitRows, currentRowBySource, location.search, updateRows])
}, [commitRows, currentRowBySource, location.search, updateRows, locale])
useEffect (() => {
if (editingRow == null || resultRepairMode (rowsRef.current) !== 'failed')
@@ -871,12 +879,12 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
urlChanged
? mergePreviewRow (
nextRow,
await apiGet<PostMetadataResponse> ('/posts/metadata', {
await apiGet<PostMetadataResponse> ('/posts/metadata', { locale,
params: { url: nextRow.url } }))
: nextRow
const dryRun = await apiPost<PostMetadataResponse> (
'/posts?dry=1',
buildDryRunFormData (candidateRow))
buildDryRunFormData (candidateRow), { locale })
const mergedRow = mergeDryRunRow (candidateRow, dryRun)
const mergedRows = updateRows (currentRows =>
replaceImportRow (currentRows, mergedRow))
@@ -910,7 +918,7 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
return { saved: false, row: applyThumbnailWarning (errorRow) }
}
toast ({ title: '行の再検証に失敗しました' })
toast ({ title: label ('review.validateFailed') })
return { saved: false, row: null }
}
}
@@ -924,8 +932,8 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
saveDraft (row, { draft, resetRequested })
await dialogue.form ({
title: '投稿を編輯',
cancelText: '取消',
title: label ('review.editPost'),
cancelText: label ('shared:actions.cancel'),
size: 'large',
body: controls => (
<PostImportRowForm
@@ -974,11 +982,11 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
const result = await apiPost<{ results: BulkApiRow[] }>(
'/posts/bulk',
buildBulkFormData ([target]))
buildBulkFormData ([target]), { locale })
if (result.results.length !== 1)
{
updateRows (currentRows => replaceImportRow (currentRows, originalRow))
toast ({ title: '登録結果が不完全でした' })
toast ({ title: label ('review.incomplete') })
return
}
@@ -993,7 +1001,7 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
catch
{
updateRows (currentRows => replaceImportRow (currentRows, originalRow))
toast ({ title: '再試行に失敗しました' })
toast ({ title: label ('review.retryFailed') })
}
finally
{
@@ -1018,10 +1026,10 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
{
const result = await apiPost<{ results: BulkApiRow[] }>(
'/posts/bulk',
buildBulkFormData (latestProcessingRows))
buildBulkFormData (latestProcessingRows), { locale })
if (result.results.length !== latestProcessingRows.length)
{
toast ({ title: '登録結果が不完全でした' })
toast ({ title: label ('review.incomplete') })
return
}
@@ -1035,7 +1043,7 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
}
catch
{
toast ({ title: '登録に失敗しました' })
toast ({ title: label ('review.submitFailed') })
}
finally
{
@@ -1051,13 +1059,11 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
return (
<>
<Helmet>
<title>{`追加内容確認 | ${ SITE_TITLE }`}</title>
</Helmet>
<PageHead title={message ('review.title')}/>
<MainArea className="pb-40">
<div className="mx-auto max-w-6xl space-y-6">
<PageTitle>追加内容確認</PageTitle>
<PageTitle>{label ('review.title')}</PageTitle>
{existingRows.length > 0 && (
<div className="rounded-lg border p-4">
@@ -1076,7 +1082,7 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
? ''
: 'transition-transform')}/>
</span>
既存投稿による自動スキップ {counts.existingSkipped}件
{label ('review.existingSkipped', { count: counts.existingSkipped })}
</button>
<AnimatePresence initial={false}>
{showExistingRows && (
@@ -1154,36 +1160,39 @@ const PostImportFooter = (
pendingOrErrorCount: number
onBack: () => void
onSubmit: () => void },
) => (
<div
className="shrink-0 border-t bg-white/95 p-4 backdrop-blur
dark:border-neutral-700 dark:bg-neutral-950/95">
<div className="mx-auto flex max-w-6xl flex-col gap-3 md:flex-row
md:items-center md:justify-between">
<div className="flex flex-wrap items-center gap-2 text-sm">
<span>作成対象 {creatableCount}件</span>
<span>手動スキップ {manualSkippedCount}件</span>
<span>既存投稿による自動スキップ {existingSkippedCount}件</span>
<span>未処理 {pendingOrErrorCount}件</span>
) => {
const { label } = useUITranslation ('imports')
return (
<div
className="shrink-0 border-t bg-white/95 p-4 backdrop-blur
dark:border-neutral-700 dark:bg-neutral-950/95">
<div className="mx-auto flex max-w-6xl flex-col gap-3 md:flex-row
md:items-center md:justify-between">
<div className="flex flex-wrap items-center gap-2 text-sm">
<span>{label ('review.creatable', { count: creatableCount })}</span>
<span>{label ('review.manualSkipped', { count: manualSkippedCount })}</span>
<span>{label ('review.existingSkipped', { count: existingSkippedCount })}</span>
<span>{label ('review.pending', { count: pendingOrErrorCount })}</span>
</div>
<div className="flex flex-col gap-2 md:flex-row">
<Button
type="button"
variant="outline"
className="w-full md:w-auto"
onClick={onBack}
disabled={submitting}>
{label ('review.back')}
</Button>
<Button
type="button"
className="w-full md:w-auto"
onClick={onSubmit}
disabled={metadataLoading || submitting || busyRowsPresent || !(canSubmit)}>
{creatableCount > 1 ? label ('review.addAll') : label ('shared:actions.add')}
</Button>
</div>
</div>
<div className="flex flex-col gap-2 md:flex-row">
<Button
type="button"
variant="outline"
className="w-full md:w-auto"
onClick={onBack}
disabled={submitting}>
戻る
</Button>
<Button
type="button"
className="w-full md:w-auto"
onClick={onSubmit}
disabled={metadataLoading || submitting || busyRowsPresent || !(canSubmit)}>
{creatableCount > 1 ? '一括追加' : '追加'}
</Button>
</div>
</div>
</div>)
</div>)
}
export default PostImportReviewPage