summaryrefslogtreecommitdiff
path: root/lib/vendor-document-list/ship
diff options
context:
space:
mode:
Diffstat (limited to 'lib/vendor-document-list/ship')
-rw-r--r--lib/vendor-document-list/ship/import-from-dolce-button.tsx152
-rw-r--r--lib/vendor-document-list/ship/send-to-shi-button.tsx127
2 files changed, 152 insertions, 127 deletions
diff --git a/lib/vendor-document-list/ship/import-from-dolce-button.tsx b/lib/vendor-document-list/ship/import-from-dolce-button.tsx
index 90796d8e..1ffe466d 100644
--- a/lib/vendor-document-list/ship/import-from-dolce-button.tsx
+++ b/lib/vendor-document-list/ship/import-from-dolce-button.tsx
@@ -25,6 +25,8 @@ import { SimplifiedDocumentsView } from "@/db/schema"
import { ImportStatus } from "../import-service"
import { useSession } from "next-auth/react"
import { getProjectIdsByVendor } from "../service"
+import { useParams } from "next/navigation"
+import { useTranslation } from "@/i18n/client"
// ๐Ÿ”ฅ API ์‘๋‹ต ์บ์‹œ (์ปดํฌ๋„ŒํŠธ ์™ธ๋ถ€์— ์„ ์–ธํ•˜์—ฌ ์ธ์Šคํ„ด์Šค ๊ฐ„ ๊ณต์œ )
const statusCache = new Map<string, { data: ImportStatus; timestamp: number }>()
@@ -69,6 +71,10 @@ export function ImportFromDOLCEButton({
const { data: session } = useSession()
const vendorId = session?.user.companyId
+ const params = useParams()
+ const lng = (params?.lng as string) || "ko"
+ const { t } = useTranslation(lng, "engineering")
+
// ๐Ÿ”ฅ allDocuments์—์„œ projectIds ์ถ”์ถœ (props๋กœ ์ „๋‹ฌ๋ฐ›์€ ๊ฒฝ์šฐ ์‚ฌ์šฉ)
const documentsProjectIds = React.useMemo(() => {
if (propProjectIds) return propProjectIds // props๋กœ ๋ฐ›์€ ๊ฒฝ์šฐ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉ
@@ -182,44 +188,34 @@ export function ImportFromDOLCEButton({
} catch (error) {
console.error('Failed to fetch import statuses:', error)
- toast.error('Unable to check status. Please verify project settings.')
+ toast.error(t('dolceImport.messages.statusCheckError'))
} finally {
setStatusLoading(false)
}
- }, [debouncedProjectIds, fetchImportStatusCached])
+ }, [debouncedProjectIds, fetchImportStatusCached, t])
// ๐Ÿ”ฅ vendorId๋กœ projects ๊ฐ€์ ธ์˜ค๊ธฐ (์ตœ์ ํ™”)
React.useEffect(() => {
- let isCancelled = false
-
- const fetchVendorProjects = async () => {
- if (allDocuments.length === 0 && vendorId && !loadingVendorProjects) {
- setLoadingVendorProjects(true)
- try {
- const projectIds = await getProjectIdsByVendor(vendorId)
- if (!isCancelled) {
- setVendorProjectIds(projectIds)
- }
- } catch (error) {
- console.error('Failed to fetch vendor projects:', error)
- if (!isCancelled) {
- toast.error('Failed to fetch project information.')
- }
- } finally {
- if (!isCancelled) {
- setLoadingVendorProjects(false)
- }
- }
- }
- }
-
- fetchVendorProjects()
-
- return () => {
- isCancelled = true
- }
- }, [allDocuments.length, vendorId, loadingVendorProjects])
-
+ let isCancelled = false;
+
+ if (allDocuments.length !== 0 || !vendorId) return;
+
+ setLoadingVendorProjects(true);
+
+ getProjectIdsByVendor(vendorId)
+ .then((projectIds) => {
+ if (!isCancelled) setVendorProjectIds(projectIds);
+ })
+ .catch((error) => {
+ if (!isCancelled) toast.error(t('dolceImport.messages.projectFetchError'));
+ })
+ .finally(() => {
+ if (!isCancelled) setLoadingVendorProjects(false);
+ });
+
+ return () => { isCancelled = true; };
+ }, [allDocuments, vendorId, t]);
+
// ๐Ÿ”ฅ ์ปดํฌ๋„ŒํŠธ ๋งˆ์šดํŠธ ์‹œ ์ƒํƒœ ์กฐํšŒ (๋””๋ฐ”์šด์‹ฑ ์ ์šฉ)
React.useEffect(() => {
if (debouncedProjectIds.length > 0) {
@@ -314,16 +310,21 @@ export function ImportFromDOLCEButton({
if (totalResult.success) {
toast.success(
- `DOLCE import completed`,
+ t('dolceImport.messages.importSuccess'),
{
- description: `New ${totalResult.newCount}, Updated ${totalResult.updatedCount}, Skipped ${totalResult.skippedCount} (${projectIds.length} projects)`
+ description: t('dolceImport.messages.importSuccessDescription', {
+ newCount: totalResult.newCount,
+ updatedCount: totalResult.updatedCount,
+ skippedCount: totalResult.skippedCount,
+ projectCount: projectIds.length
+ })
}
)
} else {
toast.error(
- `DOLCE import partially failed`,
+ t('dolceImport.messages.importPartiallyFailed'),
{
- description: 'Some projects failed to import.'
+ description: t('dolceImport.messages.importPartiallyFailedDescription')
}
)
}
@@ -338,35 +339,35 @@ export function ImportFromDOLCEButton({
setImportProgress(0)
setIsImporting(false)
- toast.error('DOLCE import failed', {
- description: error instanceof Error ? error.message : 'An unknown error occurred.'
+ toast.error(t('dolceImport.messages.importFailed'), {
+ description: error instanceof Error ? error.message : t('dolceImport.messages.unknownError')
})
}
- }, [projectIds, fetchAllImportStatus, onImportComplete])
+ }, [projectIds, fetchAllImportStatus, onImportComplete, t])
// ๐Ÿ”ฅ ์ƒํƒœ ๋ฑƒ์ง€ ๋ฉ”๋ชจ์ด์ œ์ด์…˜
const statusBadge = React.useMemo(() => {
if (loadingVendorProjects) {
- return <Badge variant="secondary">Loading project information...</Badge>
+ return <Badge variant="secondary">{t('dolceImport.status.loadingProjectInfo')}</Badge>
}
if (statusLoading) {
- return <Badge variant="secondary">Checking DOLCE connection...</Badge>
+ return <Badge variant="secondary">{t('dolceImport.status.checkingConnection')}</Badge>
}
if (importStatusMap.size === 0) {
- return <Badge variant="destructive">DOLCE Connection Error</Badge>
+ return <Badge variant="destructive">{t('dolceImport.status.connectionError')}</Badge>
}
if (!totalStats.importEnabled) {
- return <Badge variant="secondary">DOLCE Import Disabled</Badge>
+ return <Badge variant="secondary">{t('dolceImport.status.importDisabled')}</Badge>
}
if (totalStats.newDocuments > 0 || totalStats.updatedDocuments > 0) {
return (
<Badge variant="samsung" className="gap-1">
<AlertTriangle className="w-3 h-3" />
- Updates Available ({projectIds.length} projects)
+ {t('dolceImport.status.updatesAvailable', { projectCount: projectIds.length })}
</Badge>
)
}
@@ -374,10 +375,10 @@ export function ImportFromDOLCEButton({
return (
<Badge variant="default" className="gap-1 bg-green-500 hover:bg-green-600">
<CheckCircle className="w-3 h-3" />
- Synchronized with DOLCE
+ {t('dolceImport.status.synchronized')}
</Badge>
)
- }, [loadingVendorProjects, statusLoading, importStatusMap.size, totalStats, projectIds.length])
+ }, [loadingVendorProjects, statusLoading, importStatusMap.size, totalStats, projectIds.length, t])
const canImport = totalStats.importEnabled &&
(totalStats.newDocuments > 0 || totalStats.updatedDocuments > 0)
@@ -389,7 +390,7 @@ export function ImportFromDOLCEButton({
}, [fetchAllImportStatus])
// ๋กœ๋”ฉ ์ค‘์ด๊ฑฐ๋‚˜ projectIds๊ฐ€ ์—†์œผ๋ฉด ๋ฒ„ํŠผ์„ ํ‘œ์‹œํ•˜์ง€ ์•Š์Œ
- if (loadingVendorProjects || projectIds.length === 0) {
+ if (projectIds.length === 0) {
return null
}
@@ -409,7 +410,7 @@ export function ImportFromDOLCEButton({
) : (
<Download className="w-4 h-4" />
)}
- <span className="hidden sm:inline">Get List</span>
+ <span className="hidden sm:inline">{t('dolceImport.buttons.getList')}</span>
{totalStats.newDocuments + totalStats.updatedDocuments > 0 && (
<Badge
variant="samsung"
@@ -425,9 +426,9 @@ export function ImportFromDOLCEButton({
<PopoverContent className="w-96">
<div className="space-y-4">
<div className="space-y-2">
- <h4 className="font-medium">DOLCE Import Status</h4>
+ <h4 className="font-medium">{t('dolceImport.labels.importStatus')}</h4>
<div className="flex items-center justify-between">
- <span className="text-sm text-muted-foreground">Current Status</span>
+ <span className="text-sm text-muted-foreground">{t('dolceImport.labels.currentStatus')}</span>
{statusBadge}
</div>
</div>
@@ -435,17 +436,17 @@ export function ImportFromDOLCEButton({
{/* ํ”„๋กœ์ ํŠธ ์†Œ์Šค ํ‘œ์‹œ */}
{allDocuments.length === 0 && vendorProjectIds.length > 0 && (
<div className="text-xs text-blue-600 bg-blue-50 p-2 rounded">
- No documents found, importing from all projects.
+ {t('dolceImport.descriptions.noDocumentsImportAll')}
</div>
)}
{/* ๋‹ค์ค‘ ํ”„๋กœ์ ํŠธ ์ •๋ณด ํ‘œ์‹œ */}
{projectIds.length > 1 && (
<div className="text-sm">
- <div className="text-muted-foreground">Target Projects</div>
- <div className="font-medium">{projectIds.length} projects</div>
+ <div className="text-muted-foreground">{t('dolceImport.labels.targetProjects')}</div>
+ <div className="font-medium">{t('dolceImport.labels.projectCount', { count: projectIds.length })}</div>
<div className="text-xs text-muted-foreground">
- Project IDs: {projectIds.join(', ')}
+ {t('dolceImport.labels.projectIds')}: {projectIds.join(', ')}
</div>
</div>
)}
@@ -456,17 +457,17 @@ export function ImportFromDOLCEButton({
<div className="grid grid-cols-2 gap-4 text-sm">
<div>
- <div className="text-muted-foreground">New Documents</div>
+ <div className="text-muted-foreground">{t('dolceImport.labels.newDocuments')}</div>
<div className="font-medium">{totalStats.newDocuments || 0}</div>
</div>
<div>
- <div className="text-muted-foreground">Updates</div>
+ <div className="text-muted-foreground">{t('dolceImport.labels.updates')}</div>
<div className="font-medium">{totalStats.updatedDocuments || 0}</div>
</div>
</div>
<div className="text-sm">
- <div className="text-muted-foreground">Total Documents (B3/B4/B5)</div>
+ <div className="text-muted-foreground">{t('dolceImport.labels.totalDocuments')}</div>
<div className="font-medium">{totalStats.availableDocuments || 0}</div>
</div>
@@ -474,20 +475,23 @@ export function ImportFromDOLCEButton({
{projectIds.length > 1 && (
<details className="text-sm">
<summary className="cursor-pointer text-muted-foreground hover:text-foreground">
- Details by Project
+ {t('dolceImport.labels.detailsByProject')}
</summary>
<div className="mt-2 space-y-2 pl-2 border-l-2 border-muted">
{projectIds.map(projectId => {
const status = importStatusMap.get(projectId)
return (
<div key={projectId} className="text-xs">
- <div className="font-medium">Project {projectId}</div>
+ <div className="font-medium">{t('dolceImport.labels.projectLabel', { projectId })}</div>
{status ? (
<div className="text-muted-foreground">
- New {status.newDocuments}, Updates {status.updatedDocuments}
+ {t('dolceImport.descriptions.projectDetails', {
+ newDocuments: status.newDocuments,
+ updatedDocuments: status.updatedDocuments
+ })}
</div>
) : (
- <div className="text-destructive">Status check failed</div>
+ <div className="text-destructive">{t('dolceImport.status.statusCheckFailed')}</div>
)}
</div>
)
@@ -510,12 +514,12 @@ export function ImportFromDOLCEButton({
{isImporting ? (
<>
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
- Importing...
+ {t('dolceImport.buttons.importing')}
</>
) : (
<>
<Download className="w-4 h-4 mr-2" />
- Import Now
+ {t('dolceImport.buttons.importNow')}
</>
)}
</Button>
@@ -541,10 +545,10 @@ export function ImportFromDOLCEButton({
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
<DialogContent className="sm:max-w-md">
<DialogHeader>
- <DialogTitle>Import Document List from DOLCE</DialogTitle>
+ <DialogTitle>{t('dolceImport.dialog.title')}</DialogTitle>
<DialogDescription>
- Import the latest document list from Samsung Heavy Industries DOLCE system.
- {projectIds.length > 1 && ` (${projectIds.length} projects targeted)`}
+ {t('dolceImport.dialog.description')}
+ {projectIds.length > 1 && ` (${t('dolceImport.dialog.multipleProjects', { count: projectIds.length })})`}
</DialogDescription>
</DialogHeader>
@@ -552,20 +556,20 @@ export function ImportFromDOLCEButton({
{totalStats && (
<div className="rounded-lg border p-4 space-y-3">
<div className="flex items-center justify-between text-sm">
- <span>Items to Import</span>
+ <span>{t('dolceImport.labels.itemsToImport')}</span>
<span className="font-medium">
{totalStats.newDocuments + totalStats.updatedDocuments}
</span>
</div>
<div className="text-xs text-muted-foreground">
- Includes new and updated documents (B3, B4, B5).
+ {t('dolceImport.descriptions.includesNewAndUpdated')}
<br />
- For B4 documents, GTTPreDwg and GTTWorkingDwg issue stages will be auto-generated.
+ {t('dolceImport.descriptions.b4DocumentsNote')}
{projectIds.length > 1 && (
<>
<br />
- Will import sequentially from {projectIds.length} projects.
+ {t('dolceImport.descriptions.sequentialImport', { count: projectIds.length })}
</>
)}
</div>
@@ -573,7 +577,7 @@ export function ImportFromDOLCEButton({
{isImporting && (
<div className="space-y-2">
<div className="flex items-center justify-between text-sm">
- <span>Progress</span>
+ <span>{t('dolceImport.labels.progress')}</span>
<span>{importProgress}%</span>
</div>
<Progress value={importProgress} className="h-2" />
@@ -588,7 +592,7 @@ export function ImportFromDOLCEButton({
onClick={() => setIsDialogOpen(false)}
disabled={isImporting}
>
- Cancel
+ {t('buttons.cancel')}
</Button>
<Button
onClick={handleImport}
@@ -597,12 +601,12 @@ export function ImportFromDOLCEButton({
{isImporting ? (
<>
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
- Importing...
+ {t('dolceImport.buttons.importing')}
</>
) : (
<>
<Download className="w-4 h-4 mr-2" />
- Start Import
+ {t('dolceImport.buttons.startImport')}
</>
)}
</Button>
diff --git a/lib/vendor-document-list/ship/send-to-shi-button.tsx b/lib/vendor-document-list/ship/send-to-shi-button.tsx
index 7236dfde..447b461b 100644
--- a/lib/vendor-document-list/ship/send-to-shi-button.tsx
+++ b/lib/vendor-document-list/ship/send-to-shi-button.tsx
@@ -26,6 +26,8 @@ import { Alert, AlertDescription } from "@/components/ui/alert"
// โœ… ์—…๋ฐ์ดํŠธ๋œ Hook import
import { useClientSyncStatus, useTriggerSync, syncUtils } from "@/hooks/use-sync-status"
import type { EnhancedDocument } from "@/types/enhanced-documents"
+import { useParams } from "next/navigation"
+import { useTranslation } from "@/i18n/client"
interface SendToSHIButtonProps {
documents?: EnhancedDocument[]
@@ -42,6 +44,10 @@ export function SendToSHIButton({
const [syncProgress, setSyncProgress] = React.useState(0)
const [currentSyncingContract, setCurrentSyncingContract] = React.useState<number | null>(null)
+ const params = useParams()
+ const lng = (params?.lng as string) || "ko"
+ const { t } = useTranslation(lng, "engineering")
+
const targetSystem = projectType === 'ship' ? "DOLCE" : "SWP"
// ๋ฌธ์„œ์—์„œ ์œ ํšจํ•œ ๊ณ„์•ฝ ID ๋ชฉ๋ก ์ถ”์ถœ (projectId ์‚ฌ์šฉ)
@@ -80,7 +86,7 @@ export function SendToSHIButton({
// ๋™๊ธฐํ™” ์‹คํ–‰ ํ•จ์ˆ˜
const handleSync = async () => {
if (documentsContractIds.length === 0) {
- toast.info('๋™๊ธฐํ™”ํ•  ๊ณ„์•ฝ์ด ์—†์Šต๋‹ˆ๋‹ค.')
+ toast.info(t('shiSync.messages.noContractsToSync'))
return
}
@@ -105,7 +111,7 @@ export function SendToSHIButton({
})
if (contractsToSync.length === 0) {
- toast.info('๋™๊ธฐํ™”ํ•  ๋ณ€๊ฒฝ์‚ฌํ•ญ์ด ์—†์Šต๋‹ˆ๋‹ค.')
+ toast.info(t('shiSync.messages.noPendingChanges'))
setIsDialogOpen(false)
return
}
@@ -132,13 +138,13 @@ export function SendToSHIButton({
failedSyncs++
totalFailureCount += result?.failureCount || 0
const errorMsg = result?.errors?.[0] || result?.message || 'Unknown sync error'
- errors.push(`Contract ${projectId}: ${errorMsg}`)
+ errors.push(t('shiSync.messages.contractError', { projectId, error: errorMsg }))
console.error(`Contract ${projectId} sync failed:`, result)
}
} catch (error) {
failedSyncs++
- const errorMessage = error instanceof Error ? error.message : '์•Œ ์ˆ˜ ์—†๋Š” ์˜ค๋ฅ˜'
- errors.push(`Contract ${projectId}: ${errorMessage}`)
+ const errorMessage = error instanceof Error ? error.message : t('shiSync.messages.unknownError')
+ errors.push(t('shiSync.messages.contractError', { projectId, error: errorMessage }))
console.error(`Contract ${projectId} sync exception:`, error)
}
@@ -155,23 +161,30 @@ export function SendToSHIButton({
if (failedSyncs === 0) {
toast.success(
- `๋ชจ๋“  ๊ณ„์•ฝ ๋™๊ธฐํ™” ์™„๋ฃŒ: ${totalSuccessCount}๊ฑด ์„ฑ๊ณต`,
+ t('shiSync.messages.allSyncCompleted', { successCount: totalSuccessCount }),
{
- description: `${successfulSyncs}๊ฐœ ๊ณ„์•ฝ์—์„œ ${totalSuccessCount}๊ฐœ ํ•ญ๋ชฉ์ด SHI ์‹œ์Šคํ…œ์œผ๋กœ ์ „์†ก๋˜์—ˆ์Šต๋‹ˆ๋‹ค.`
+ description: t('shiSync.messages.allSyncCompletedDescription', {
+ contractCount: successfulSyncs,
+ itemCount: totalSuccessCount
+ })
}
)
} else if (successfulSyncs > 0) {
toast.warning(
- `๋ถ€๋ถ„ ๋™๊ธฐํ™” ์™„๋ฃŒ: ${successfulSyncs}๊ฐœ ์„ฑ๊ณต, ${failedSyncs}๊ฐœ ์‹คํŒจ`,
+ t('shiSync.messages.partialSyncCompleted', {
+ successfulCount: successfulSyncs,
+ failedCount: failedSyncs
+ }),
{
- description: errors.slice(0, 3).join(', ') + (errors.length > 3 ? ' ์™ธ ๋”๋ณด๊ธฐ...' : '')
+ description: errors.slice(0, 3).join(', ') +
+ (errors.length > 3 ? t('shiSync.messages.andMore') : '')
}
)
} else {
toast.error(
- `๋™๊ธฐํ™” ์‹คํŒจ: ${failedSyncs}๊ฐœ ๊ณ„์•ฝ ๋ชจ๋‘ ์‹คํŒจ`,
+ t('shiSync.messages.allSyncFailed', { failedCount: failedSyncs }),
{
- description: errors[0] || '๋ชจ๋“  ๊ณ„์•ฝ ๋™๊ธฐํ™”์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.'
+ description: errors[0] || t('shiSync.messages.allContractsSyncFailed')
}
)
}
@@ -186,7 +199,7 @@ export function SendToSHIButton({
setCurrentSyncingContract(null)
const errorMessage = syncUtils.formatError(error as Error)
- toast.error('๋™๊ธฐํ™” ์‹คํŒจ', {
+ toast.error(t('shiSync.messages.syncFailed'), {
description: errorMessage
})
console.error('Sync process failed:', error)
@@ -199,7 +212,7 @@ export function SendToSHIButton({
return (
<Badge variant="secondary" className="gap-1">
<Loader2 className="w-3 h-3 animate-spin" />
- ํ™•์ธ ์ค‘...
+ {t('shiSync.status.checking')}
</Badge>
)
}
@@ -208,20 +221,20 @@ export function SendToSHIButton({
return (
<Badge variant="destructive" className="gap-1">
<AlertTriangle className="w-3 h-3" />
- ์—ฐ๊ฒฐ ์˜ค๋ฅ˜
+ {t('shiSync.status.connectionError')}
</Badge>
)
}
if (documentsContractIds.length === 0) {
- return <Badge variant="secondary">๊ณ„์•ฝ ์—†์Œ</Badge>
+ return <Badge variant="secondary">{t('shiSync.status.noContracts')}</Badge>
}
if (totalStats.totalPending > 0) {
return (
<Badge variant="destructive" className="gap-1">
<AlertTriangle className="w-3 h-3" />
- {totalStats.totalPending}๊ฑด ๋Œ€๊ธฐ
+ {t('shiSync.status.pendingItems', { count: totalStats.totalPending })}
</Badge>
)
}
@@ -230,12 +243,12 @@ export function SendToSHIButton({
return (
<Badge variant="default" className="gap-1 bg-green-500 hover:bg-green-600">
<CheckCircle className="w-3 h-3" />
- ๋™๊ธฐํ™”๋จ
+ {t('shiSync.status.synchronized')}
</Badge>
)
}
- return <Badge variant="secondary">๋ณ€๊ฒฝ์‚ฌํ•ญ ์—†์Œ</Badge>
+ return <Badge variant="secondary">{t('shiSync.status.noChanges')}</Badge>
}
return (
@@ -254,7 +267,7 @@ export function SendToSHIButton({
) : (
<Send className="w-4 h-4" />
)}
- <span className="hidden sm:inline">Send to SHI</span>
+ <span className="hidden sm:inline">{t('shiSync.buttons.sendToSHI')}</span>
{totalStats.totalPending > 0 && (
<Badge
variant="destructive"
@@ -271,7 +284,7 @@ export function SendToSHIButton({
<div className="space-y-4">
<div className="space-y-2">
<div className="flex items-center justify-between">
- <h4 className="font-medium">SHI ๋™๊ธฐํ™” ์ƒํƒœ</h4>
+ <h4 className="font-medium">{t('shiSync.labels.syncStatus')}</h4>
<Button
variant="ghost"
size="sm"
@@ -288,12 +301,15 @@ export function SendToSHIButton({
</div>
<div className="flex items-center justify-between">
- <span className="text-sm text-muted-foreground">์ „์ฒด ์ƒํƒœ</span>
+ <span className="text-sm text-muted-foreground">{t('shiSync.labels.overallStatus')}</span>
{getSyncStatusBadge()}
</div>
<div className="text-xs text-muted-foreground">
- {documentsContractIds.length}๊ฐœ ๊ณ„์•ฝ ๋Œ€์ƒ โ€ข {targetSystem} ์‹œ์Šคํ…œ
+ {t('shiSync.descriptions.targetInfo', {
+ contractCount: documentsContractIds.length,
+ targetSystem
+ })}
</div>
</div>
@@ -302,10 +318,12 @@ export function SendToSHIButton({
<Alert variant="destructive">
<AlertTriangle className="h-4 w-4" />
<AlertDescription>
- ์ผ๋ถ€ ๊ณ„์•ฝ์˜ ๋™๊ธฐํ™” ์ƒํƒœ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๋„คํŠธ์›Œํฌ ์—ฐ๊ฒฐ์„ ํ™•์ธํ•ด์ฃผ์„ธ์š”.
+ {t('shiSync.descriptions.statusCheckError')}
{process.env.NODE_ENV === 'development' && (
<div className="text-xs mt-1 font-mono">
- Debug: {contractStatuses.filter(({ error }) => error).length}๊ฐœ ๊ณ„์•ฝ์—์„œ ์˜ค๋ฅ˜
+ Debug: {t('shiSync.descriptions.contractsWithError', {
+ count: contractStatuses.filter(({ error }) => error).length
+ })}
</div>
)}
</AlertDescription>
@@ -319,46 +337,46 @@ export function SendToSHIButton({
<div className="grid grid-cols-3 gap-4 text-sm">
<div className="text-center">
- <div className="text-muted-foreground">๋Œ€๊ธฐ ์ค‘</div>
- <div className="font-medium text-orange-600">{totalStats.totalPending}๊ฑด</div>
+ <div className="text-muted-foreground">{t('shiSync.labels.pending')}</div>
+ <div className="font-medium text-orange-600">{t('shiSync.labels.itemCount', { count: totalStats.totalPending })}</div>
</div>
<div className="text-center">
- <div className="text-muted-foreground">๋™๊ธฐํ™”๋จ</div>
- <div className="font-medium text-green-600">{totalStats.totalSynced}๊ฑด</div>
+ <div className="text-muted-foreground">{t('shiSync.labels.synced')}</div>
+ <div className="font-medium text-green-600">{t('shiSync.labels.itemCount', { count: totalStats.totalSynced })}</div>
</div>
<div className="text-center">
- <div className="text-muted-foreground">์‹คํŒจ</div>
- <div className="font-medium text-red-600">{totalStats.totalFailed}๊ฑด</div>
+ <div className="text-muted-foreground">{t('shiSync.labels.failed')}</div>
+ <div className="font-medium text-red-600">{t('shiSync.labels.itemCount', { count: totalStats.totalFailed })}</div>
</div>
</div>
{/* ๊ณ„์•ฝ๋ณ„ ์ƒ์„ธ ์ƒํƒœ */}
{contractStatuses.length > 1 && (
<div className="space-y-2">
- <div className="text-sm font-medium">๊ณ„์•ฝ๋ณ„ ์ƒํƒœ</div>
+ <div className="text-sm font-medium">{t('shiSync.labels.statusByContract')}</div>
<ScrollArea className="h-32">
<div className="space-y-2">
{contractStatuses.map(({ projectId, syncStatus, isLoading, error }) => (
<div key={projectId} className="flex items-center justify-between text-xs p-2 rounded border">
- <span className="font-medium">Contract {projectId}</span>
+ <span className="font-medium">{t('shiSync.labels.contractLabel', { projectId })}</span>
{isLoading ? (
<Badge variant="secondary" className="text-xs">
<Loader2 className="w-3 h-3 mr-1 animate-spin" />
- ๋กœ๋”ฉ...
+ {t('shiSync.status.loading')}
</Badge>
) : error ? (
<Badge variant="destructive" className="text-xs">
<AlertTriangle className="w-3 h-3 mr-1" />
- ์˜ค๋ฅ˜
+ {t('shiSync.status.error')}
</Badge>
) : syncStatus && syncStatus.pendingChanges > 0 ? (
<Badge variant="destructive" className="text-xs">
- {syncStatus.pendingChanges}๊ฑด ๋Œ€๊ธฐ
+ {t('shiSync.status.pendingCount', { count: syncStatus.pendingChanges })}
</Badge>
) : (
<Badge variant="secondary" className="text-xs">
<CheckCircle className="w-3 h-3 mr-1" />
- ์ตœ์‹ 
+ {t('shiSync.status.upToDate')}
</Badge>
)}
</div>
@@ -374,7 +392,7 @@ export function SendToSHIButton({
{documentsContractIds.length === 0 && (
<Alert>
<AlertDescription>
- ๋™๊ธฐํ™”ํ•  ๋ฌธ์„œ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ๋ฌธ์„œ๋ฅผ ์„ ํƒํ•ด์ฃผ์„ธ์š”.
+ {t('shiSync.descriptions.noDocumentsToSync')}
</AlertDescription>
</Alert>
)}
@@ -392,12 +410,12 @@ export function SendToSHIButton({
{isSyncing ? (
<>
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
- ๋™๊ธฐํ™” ์ค‘...
+ {t('shiSync.buttons.syncing')}
</>
) : (
<>
<Send className="w-4 h-4 mr-2" />
- ์ง€๊ธˆ ๋™๊ธฐํ™”
+ {t('shiSync.buttons.syncNow')}
</>
)}
</Button>
@@ -426,10 +444,13 @@ export function SendToSHIButton({
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<Send className="w-5 h-5" />
- SHI ์‹œ์Šคํ…œ์œผ๋กœ ๋™๊ธฐํ™”
+ {t('shiSync.dialog.title')}
</DialogTitle>
<DialogDescription>
- {documentsContractIds.length}๊ฐœ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ๋œ ๋ฌธ์„œ ๋ฐ์ดํ„ฐ๋ฅผ {targetSystem} ์‹œ์Šคํ…œ์œผ๋กœ ์ „์†กํ•ฉ๋‹ˆ๋‹ค.
+ {t('shiSync.dialog.description', {
+ contractCount: documentsContractIds.length,
+ targetSystem
+ })}
</DialogDescription>
</DialogHeader>
@@ -437,30 +458,30 @@ export function SendToSHIButton({
{!totalStats.hasError && documentsContractIds.length > 0 && (
<div className="rounded-lg border p-4 space-y-3">
<div className="flex items-center justify-between text-sm">
- <span>์ „์†ก ๋Œ€์ƒ</span>
- <span className="font-medium">{totalStats.totalPending}๊ฑด</span>
+ <span>{t('shiSync.labels.syncTarget')}</span>
+ <span className="font-medium">{t('shiSync.labels.itemCount', { count: totalStats.totalPending })}</span>
</div>
<div className="flex items-center justify-between text-sm">
- <span>๋Œ€์ƒ ๊ณ„์•ฝ</span>
- <span className="font-medium">{documentsContractIds.length}๊ฐœ</span>
+ <span>{t('shiSync.labels.targetContracts')}</span>
+ <span className="font-medium">{t('shiSync.labels.contractCount', { count: documentsContractIds.length })}</span>
</div>
<div className="text-xs text-muted-foreground">
- ๋ฌธ์„œ, ๋ฆฌ๋น„์ „, ์ฒจ๋ถ€ํŒŒ์ผ์˜ ๋ณ€๊ฒฝ์‚ฌํ•ญ์ด ํฌํ•จ๋ฉ๋‹ˆ๋‹ค.
+ {t('shiSync.descriptions.includesChanges')}
</div>
{isSyncing && (
<div className="space-y-2">
<div className="flex items-center justify-between text-sm">
- <span>์ง„ํ–‰๋ฅ </span>
+ <span>{t('shiSync.labels.progress')}</span>
<span>{Math.round(syncProgress)}%</span>
</div>
<Progress value={syncProgress} className="h-2" />
{currentSyncingContract && (
<div className="text-xs text-muted-foreground flex items-center gap-1">
<Loader2 className="w-3 h-3 animate-spin" />
- ํ˜„์žฌ ์ฒ˜๋ฆฌ ์ค‘: Contract {currentSyncingContract}
+ {t('shiSync.descriptions.currentlyProcessing', { contractId: currentSyncingContract })}
</div>
)}
</div>
@@ -472,7 +493,7 @@ export function SendToSHIButton({
<Alert variant="destructive">
<AlertTriangle className="h-4 w-4" />
<AlertDescription>
- ์ผ๋ถ€ ๊ณ„์•ฝ์˜ ๋™๊ธฐํ™” ์ƒํƒœ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๋„คํŠธ์›Œํฌ ์—ฐ๊ฒฐ์„ ํ™•์ธํ•˜๊ณ  ๋‹ค์‹œ ์‹œ๋„ํ•ด์ฃผ์„ธ์š”.
+ {t('shiSync.descriptions.dialogStatusCheckError')}
</AlertDescription>
</Alert>
)}
@@ -480,7 +501,7 @@ export function SendToSHIButton({
{documentsContractIds.length === 0 && (
<Alert>
<AlertDescription>
- ๋™๊ธฐํ™”ํ•  ๊ณ„์•ฝ์ด ์—†์Šต๋‹ˆ๋‹ค. ๋ฌธ์„œ๋ฅผ ์„ ํƒํ•ด์ฃผ์„ธ์š”.
+ {t('shiSync.descriptions.noContractsToSync')}
</AlertDescription>
</Alert>
)}
@@ -491,7 +512,7 @@ export function SendToSHIButton({
onClick={() => setIsDialogOpen(false)}
disabled={isSyncing}
>
- ์ทจ์†Œ
+ {t('buttons.cancel')}
</Button>
<Button
onClick={handleSync}
@@ -500,12 +521,12 @@ export function SendToSHIButton({
{isSyncing ? (
<>
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
- ๋™๊ธฐํ™” ์ค‘...
+ {t('shiSync.buttons.syncing')}
</>
) : (
<>
<Send className="w-4 h-4 mr-2" />
- ๋™๊ธฐํ™” ์‹œ์ž‘
+ {t('shiSync.buttons.startSync')}
</>
)}
</Button>