setValue('name', value)}
onBlur={() => trigger('name')}
/>
setValue('tel', value)}
onBlur={() => trigger('tel')}
/>
setValue('studentId', value)}
onBlur={() => trigger('studentId')}
/>
setValue('gradeId', Number(value))
@@ -233,7 +244,7 @@ const Carousel: FC = ({ isOpen, onClose }) => {
error={errors.gradeId?.message}
/>
setValue('departmentId', Number(value))
@@ -249,7 +260,7 @@ const Carousel: FC = ({ isOpen, onClose }) => {
- メールアドレス
+ {t('registerCarousel.review.email')}
@@ -261,7 +272,7 @@ const Carousel: FC
= ({ isOpen, onClose }) => {
- パスワード
+ {t('registerCarousel.review.password')}
@@ -272,7 +283,9 @@ const Carousel: FC = ({ isOpen, onClose }) => {
-
名前
+
+ {t('registerCarousel.review.name')}
+
@@ -283,7 +296,7 @@ const Carousel: FC
= ({ isOpen, onClose }) => {
- 電話番号
+ {t('registerCarousel.review.tel')}
@@ -295,7 +308,7 @@ const Carousel: FC
= ({ isOpen, onClose }) => {
- 学籍番号
+ {t('registerCarousel.review.studentId')}
@@ -306,7 +319,9 @@ const Carousel: FC = ({ isOpen, onClose }) => {
-
学年
+
+ {t('registerCarousel.review.grade')}
+
@@ -319,7 +334,9 @@ const Carousel: FC = ({ isOpen, onClose }) => {
-
学科
+
+ {t('registerCarousel.review.department')}
+
@@ -357,7 +374,7 @@ const Carousel: FC = ({ isOpen, onClose }) => {
icon="lessThan"
isDisable={isLoading}
>
- 修正
+ {t('registerCarousel.buttons.previous')}
)}
{stepIndex === 2 ? (
@@ -368,7 +385,7 @@ const Carousel: FC = ({ isOpen, onClose }) => {
onClick={handleRegisterClick}
isDisable={isLoading}
>
- 登録
+ {t('registerCarousel.buttons.submit')}
) : (
)}
diff --git a/user/src/components/RegisterCarousel/useRegistration.ts b/user/src/components/RegisterCarousel/useRegistration.ts
index 54260b2ee..6bdf1439b 100644
--- a/user/src/components/RegisterCarousel/useRegistration.ts
+++ b/user/src/components/RegisterCarousel/useRegistration.ts
@@ -1,31 +1,33 @@
import { useCallback, useState } from 'react';
import { useRouter } from 'next/router';
+import type { TFunction } from 'i18next';
import { signIn } from 'next-auth/react';
+import { useTranslation } from 'next-i18next';
import type { UseFormHandleSubmit } from 'react-hook-form';
import { toast } from 'react-toastify';
import type { RegisterFormSchema } from './schema';
type ApiErrors = Record
;
-const ERROR_MESSAGES: Record> = {
+const ERROR_MESSAGE_KEYS: Record> = {
email: {
- 'has already been taken': 'このメールアドレスは既に登録されています。',
- default: 'メールアドレスに誤りがあります。',
+ 'has already been taken': 'registerCarousel.errors.emailTaken',
+ default: 'registerCarousel.errors.emailDefault',
},
password: {
- 'is too short': 'パスワードは6文字以上である必要があります。',
- default: 'パスワードに誤りがあります。',
+ 'is too short': 'registerCarousel.errors.passwordShort',
+ default: 'registerCarousel.errors.passwordDefault',
},
password_confirmation: {
- "doesn't match Password": 'パスワードが一致しません。',
- default: 'パスワード確認に誤りがあります。',
+ "doesn't match Password": 'registerCarousel.errors.passwordConfirmMismatch',
+ default: 'registerCarousel.errors.passwordConfirmDefault',
},
user_details: {
- tel: '電話番号に誤りがあります。',
- student_id: '学籍番号に誤りがあります。',
- grade_id: '学年に誤りがあります。',
- department_id: '学科に誤りがあります。',
- default: 'ユーザー詳細情報に誤りがあります。',
+ tel: 'registerCarousel.errors.telInvalid',
+ student_id: 'registerCarousel.errors.studentIdInvalid',
+ grade_id: 'registerCarousel.errors.gradeInvalid',
+ department_id: 'registerCarousel.errors.departmentInvalid',
+ default: 'registerCarousel.errors.userDetailsDefault',
},
};
@@ -39,22 +41,23 @@ const STEP_FIELDS: Record = {
* @param errors APIから返されたエラー情報
* @returns 対応するエラーメッセージ(なければ空文字)
*/
-function mapErrorMessage(errors: ApiErrors = {}): string {
+function mapErrorMessage(
+ errors: ApiErrors = {},
+ t: TFunction<'common'>
+): string {
for (const [field, msgs] of Object.entries(errors)) {
- // 各フィールドに対応するエラーメッセージのマッピングを取得
- const mapping = ERROR_MESSAGES[field] || {};
- for (const key of Object.keys(mapping)) {
- // キーワードが一致するエラーメッセージを返す
+ const mapping = ERROR_MESSAGE_KEYS[field];
+ if (!mapping) continue;
+
+ for (const [key, translationKey] of Object.entries(mapping)) {
if (key !== 'default' && msgs.some((m) => m.includes(key))) {
- return mapping[key];
+ return t(translationKey);
}
}
- // デフォルトメッセージがあればそれを返す
if (mapping.default) {
- return mapping.default;
+ return t(mapping.default);
}
}
- // 該当するメッセージがない場合は空文字を返す
return '';
}
@@ -92,6 +95,7 @@ export const useRegistration = (
const [isLoading, setIsLoading] = useState(false); // ローディング状態
const [displayError, setDisplayError] = useState(); // 表示するエラーメッセージ
const router = useRouter(); // ルーターオブジェクト
+ const { t } = useTranslation('common');
/**
* APIエラー発生時に対応するステップに移動
@@ -149,8 +153,8 @@ export const useRegistration = (
if (result.status === 'success') {
// 登録成功時の処理
- toast.success('登録が完了しました。');
- toast.info('自動でログインします。そのままお待ちください。');
+ toast.success(t('registerCarousel.toasts.registrationSuccess'));
+ toast.info(t('registerCarousel.toasts.autoLogin'));
await signIn('credentials', {
redirect: false,
@@ -158,28 +162,28 @@ export const useRegistration = (
password: data.password,
})
.then(() => {
- toast.success('ログインしました。');
+ toast.success(t('registerCarousel.toasts.loginSuccess'));
router.push('/home'); // ホーム画面にリダイレクト
})
.catch((error) => {
console.error('Login error:', error); // ログインエラーをログ出力
- toast.error('ログインに失敗しました。');
- toast.info('再度ログインしてください。');
+ toast.error(t('registerCarousel.toasts.loginFailed'));
+ toast.info(t('registerCarousel.toasts.retryLogin'));
router.push('/'); // トップページにリダイレクト
});
return;
} else {
// エラー時の処理
const message =
- mapErrorMessage(result.errors) ||
+ mapErrorMessage(result.errors, t) ||
result.message ||
- '通信エラーが発生しました。';
+ t('registerCarousel.errors.requestError');
setDisplayError(message); // エラーメッセージを設定
navigateToStep(result.errors); // エラーが発生したステップに移動
}
} catch {
// 通信エラー時の処理
- setDisplayError('通信に失敗しました。時間をおいて再度お試しください。');
+ setDisplayError(t('registerCarousel.errors.requestFailed'));
} finally {
setIsLoading(false); // 最終的にローディング状態を終了
}
From 40ffa47417c58fec58cfdde82ae8ac520f3fe477 Mon Sep 17 00:00:00 2001
From: hikahana <22.h.hanada.nutfes@gmail.com>
Date: Tue, 30 Dec 2025 03:46:03 +0900
Subject: [PATCH 10/57] =?UTF-8?q?[feat]=20WelcomeBox=E3=82=B3=E3=83=B3?=
=?UTF-8?q?=E3=83=9D=E3=83=BC=E3=83=8D=E3=83=B3=E3=83=88=E3=81=ABi18next?=
=?UTF-8?q?=E3=82=92=E4=BD=BF=E7=94=A8=E3=81=97=E3=81=9F=E7=BF=BB=E8=A8=B3?=
=?UTF-8?q?=E6=A9=9F=E8=83=BD=E3=82=92=E8=BF=BD=E5=8A=A0=E3=81=97=E3=80=81?=
=?UTF-8?q?=E3=83=9C=E3=82=BF=E3=83=B3=E3=83=A9=E3=83=99=E3=83=AB=E3=82=84?=
=?UTF-8?q?=E8=AA=AC=E6=98=8E=E6=96=87=E3=82=92=E3=83=AD=E3=83=BC=E3=82=AB?=
=?UTF-8?q?=E3=83=A9=E3=82=A4=E3=82=BA?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
user/src/components/WelcomeBox/WelcomeBox.tsx | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/user/src/components/WelcomeBox/WelcomeBox.tsx b/user/src/components/WelcomeBox/WelcomeBox.tsx
index 5775d8a36..e3d869e12 100755
--- a/user/src/components/WelcomeBox/WelcomeBox.tsx
+++ b/user/src/components/WelcomeBox/WelcomeBox.tsx
@@ -1,4 +1,5 @@
import { FC } from 'react';
+import { useTranslation } from 'next-i18next';
import Button from '../Button';
type WelcomeBoxProps = {
@@ -10,6 +11,8 @@ const WelcomeBox: FC = ({
handleLoginClick,
handleRegisterClick,
}) => {
+ const { t } = useTranslation('common');
+
return (
@@ -19,18 +22,18 @@ const WelcomeBox: FC
= ({
color="main"
onClick={handleRegisterClick}
>
- 新規登録
+ {t('welcomeBox.register')}
- 初めての方はこちら
+ {t('welcomeBox.registerDescription')}
- すでにアカウントをお持ちの方はこちら
+ {t('welcomeBox.loginDescription')}
From b88864556b06127df5fbb838660351a066e14305 Mon Sep 17 00:00:00 2001
From: hikahana <22.h.hanada.nutfes@gmail.com>
Date: Tue, 30 Dec 2025 03:46:14 +0900
Subject: [PATCH 11/57] =?UTF-8?q?[feat]=20=E6=96=B0=E3=81=97=E3=81=84?=
=?UTF-8?q?=E3=83=AD=E3=83=BC=E3=82=AB=E3=83=A9=E3=82=A4=E3=82=BA=E8=A8=AD?=
=?UTF-8?q?=E5=AE=9A=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB=E3=80=8C.cursorru?=
=?UTF-8?q?les=E3=80=8D=E3=82=92=E8=BF=BD=E5=8A=A0=E3=81=97=E3=80=81?=
=?UTF-8?q?=E6=97=A5=E6=9C=AC=E8=AA=9E=E3=81=A7=E3=81=AE=E5=87=BA=E5=8A=9B?=
=?UTF-8?q?=E3=82=92=E6=8C=87=E5=AE=9A?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.cursorrules | 1 +
1 file changed, 1 insertion(+)
create mode 100644 .cursorrules
diff --git a/.cursorrules b/.cursorrules
new file mode 100644
index 000000000..49282b29d
--- /dev/null
+++ b/.cursorrules
@@ -0,0 +1 @@
+すべて日本語で出力してください
From 26fa7582edfdf8369ff8c5974689bd0aeb874a91 Mon Sep 17 00:00:00 2001
From: hikahana <22.h.hanada.nutfes@gmail.com>
Date: Thu, 1 Jan 2026 12:52:35 +0900
Subject: [PATCH 12/57] =?UTF-8?q?[feat]=20=E8=8B=B1=E8=AA=9E=E3=81=A8?=
=?UTF-8?q?=E6=97=A5=E6=9C=AC=E8=AA=9E=E3=81=AE=E3=83=AD=E3=83=BC=E3=82=AB?=
=?UTF-8?q?=E3=83=A9=E3=82=A4=E3=82=BA=E8=A8=AD=E5=AE=9A=E3=82=92=E6=9B=B4?=
=?UTF-8?q?=E6=96=B0=E3=81=97=E3=80=81=E4=B8=80=E8=88=AC=E7=9A=84=E3=81=AA?=
=?UTF-8?q?=E3=83=A1=E3=83=83=E3=82=BB=E3=83=BC=E3=82=B8=E3=80=81=E3=83=95?=
=?UTF-8?q?=E3=82=A9=E3=83=BC=E3=83=A0=E3=80=81=E3=82=B9=E3=83=86=E3=83=BC?=
=?UTF-8?q?=E3=82=BF=E3=82=B9=E3=80=81=E3=83=AA=E3=82=B9=E3=83=88=E3=80=81?=
=?UTF-8?q?=E3=83=A6=E3=83=BC=E3=82=B6=E3=83=BC=E7=B7=A8=E9=9B=86=E3=83=A2?=
=?UTF-8?q?=E3=83=BC=E3=83=80=E3=83=AB=E3=80=81=E7=94=B3=E8=AB=8B=E9=96=A2?=
=?UTF-8?q?=E9=80=A3=E3=81=AE=E7=BF=BB=E8=A8=B3=E3=82=92=E8=BF=BD=E5=8A=A0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
user/public/locales/en/common.json | 792 +++++++++++++++++++++++++++++
user/public/locales/ja/common.json | 792 +++++++++++++++++++++++++++++
2 files changed, 1584 insertions(+)
diff --git a/user/public/locales/en/common.json b/user/public/locales/en/common.json
index 72c2d500e..dfd89d033 100644
--- a/user/public/locales/en/common.json
+++ b/user/public/locales/en/common.json
@@ -4,6 +4,62 @@
"japanese": "Japanese",
"english": "English"
},
+ "general": {
+ "loading": "Loading...",
+ "errors": {
+ "fetch": "Failed to fetch data."
+ }
+ },
+ "form": {
+ "required": "Required",
+ "optional": "Optional",
+ "actions": {
+ "register": "Register",
+ "edit": "Edit",
+ "cancel": "Cancel",
+ "delete": "Delete",
+ "close": "Close",
+ "submit": "Submit",
+ "save": "Save",
+ "add": "Add",
+ "back": "Back",
+ "next": "Next",
+ "previous": "Previous",
+ "confirm": "Confirm",
+ "open": "Open",
+ "upload": "Upload"
+ },
+ "messages": {
+ "nonEditable": "* Cannot be edited",
+ "registerSuccess": "Saved successfully.",
+ "registerFailed": "Failed to save.",
+ "updateSuccess": "Updated successfully.",
+ "updateFailed": "Failed to update."
+ },
+ "validation": {
+ "required": "This field is required",
+ "duplicateChoice": "You selected the same choice more than once.",
+ "remarkRequired": "Please enter the location in the remarks.",
+ "inputError": "Please fix the highlighted errors.",
+ "select": "Please select an option"
+ }
+ },
+ "status": {
+ "reception": {
+ "open": "Open",
+ "deadline": "Closing soon",
+ "closed": "Closed"
+ },
+ "registration": {
+ "registered": "Registered",
+ "unregistered": "Not registered"
+ },
+ "progress": {
+ "notRequired": "Not required",
+ "completed": "Complete",
+ "pending": "Incomplete"
+ }
+ },
"news": {
"title": "News",
"none": "No news available.",
@@ -94,6 +150,742 @@
"retryLogin": "Please try signing in again."
}
},
+ "lists": {
+ "grade": {
+ "select": "Select",
+ "B1": "B1 (1st year undergraduate)",
+ "B2": "B2 (2nd year undergraduate)",
+ "B3": "B3 (3rd year undergraduate)",
+ "B4": "B4 (4th year undergraduate)",
+ "M1": "M1 (1st year master's)",
+ "M2": "M2 (2nd year master's)",
+ "D1": "D1 (1st year doctoral)",
+ "D2": "D2 (2nd year doctoral)",
+ "D3": "D3 (3rd year doctoral)",
+ "GD1": "GD1 (1st year innovation program)",
+ "GD2": "GD2 (2nd year innovation program)",
+ "GD3": "GD3 (3rd year innovation program)",
+ "GD4": "GD4 (4th year innovation program)",
+ "GD5": "GD5 (5th year innovation program)",
+ "faculty": "Faculty / Staff",
+ "other": "Other"
+ },
+ "department": {
+ "select": "Select",
+ "1": "Mechanical Engineering (Undergraduate Program)",
+ "2": "Electrical, Electronics and Information Engineering (Undergraduate Program)",
+ "3": "Materials Science and Biotechnology (Undergraduate Programs)",
+ "4": "Civil and Environmental Engineering (Undergraduate Program)",
+ "5": "Information and Management Systems Engineering (Undergraduate Program)",
+ "6": "Mechanical Engineering (Graduate Program)",
+ "7": "Electrical, Electronics and Information Engineering (Graduate Program)",
+ "8": "Materials Science and Biotechnology (Graduate Programs)",
+ "9": "Civil and Environmental Engineering (Graduate Program)",
+ "10": "Information and Management Systems Engineering (Graduate Program)",
+ "11": "Integrated Quantum and Nuclear Engineering",
+ "12": "System Safety Engineering",
+ "13": "GIGAKU Innovation Group",
+ "14": "Information Science and Control Engineering",
+ "15": "Materials Science",
+ "16": "Energy and Environmental Engineering",
+ "17": "Integrated Bioscience and Technology",
+ "18": "Other"
+ }
+ },
+ "userEditModal": {
+ "labels": {
+ "name": "Name",
+ "email": "Email address",
+ "tel": "Phone number",
+ "studentId": "Student ID",
+ "grade": "Grade",
+ "department": "Department"
+ },
+ "notes": {
+ "name": "e.g., Taro Nagaoka",
+ "email": "e.g., s123456@stn.nagaokaut.ac.jp",
+ "tel": "e.g., 09012345678",
+ "studentId": "e.g., 12345678"
+ },
+ "messages": {
+ "emailChangeConfirm": "If you change your email address, you must sign in again with the new address. Your password will remain the same."
+ },
+ "toasts": {
+ "cancelled": "Update was cancelled.",
+ "updateSuccess": "User information updated.",
+ "emailChanged": "Email updated. Please sign in again."
+ },
+ "errors": {
+ "duplicateEmail": "This email address is already in use.",
+ "updateFailed": "Failed to update."
+ },
+ "validation": {
+ "name": "Name is required.",
+ "studentId": "Enter an 8-digit student ID.",
+ "tel": "Enter a valid phone number (e.g., 09012345678).",
+ "telMin": "Phone number is too short.",
+ "telMax": "Phone number is too long.",
+ "email": "Enter a valid email address.",
+ "department": "Select a department.",
+ "grade": "Select a grade."
+ }
+ },
+ "applications": {
+ "group": {
+ "title": "Group Registration",
+ "loading": "Loading...",
+ "errors": {
+ "fetch": "Failed to fetch data."
+ },
+ "fields": {
+ "name": "Group name",
+ "projectName": "Project name",
+ "isInternational": "Is this an international group?",
+ "isExternal": "Is this an off-campus group?",
+ "groupCategory": "Participation type",
+ "activity": "Activity details"
+ },
+ "notes": {
+ "name": "e.g., NUT Festival Executive Committee",
+ "projectName": "e.g., Gidai Rangers",
+ "international": "Please confirm your registration status.",
+ "external": "Please confirm whether outside organizations are allowed.",
+ "groupCategory": "Select the format that best fits your activity.",
+ "activity": "e.g., Food sales, stage performance"
+ },
+ "boolean": {
+ "yes": "Yes",
+ "no": "No"
+ },
+ "options": {
+ "international": {
+ "no": "No, we are not an international (international-student) group.",
+ "yes": "Yes, we are an international (international-student) group."
+ },
+ "external": {
+ "no": "No, we are an on-campus group.",
+ "yes": "Yes, we are an off-campus group."
+ }
+ }
+ },
+ "venue": {
+ "title": "Venue Application",
+ "loading": "Loading...",
+ "fields": {
+ "firstChoice": "First choice",
+ "secondChoice": "Second choice",
+ "thirdChoice": "Third choice",
+ "remark": "Remarks"
+ }
+ },
+ "rentItems": {
+ "title": "Equipment rental application",
+ "loading": "Loading data...",
+ "errors": {
+ "fetchTitle": "Error:",
+ "fetchDescription": "Failed to load data. Please reload the page."
+ },
+ "radio": {
+ "question": "Will you submit a rental request?",
+ "options": {
+ "yes": "Yes",
+ "no": "No"
+ }
+ },
+ "summary": {
+ "noApplication": {
+ "label": "Rental request not required (saved)",
+ "description": "We will not borrow any equipment from the school."
+ },
+ "count": "{{value}} items"
+ },
+ "location": {
+ "displayLabel": "First choice:",
+ "radioQuestion": "Which venue type is your first choice?",
+ "options": {
+ "indoor": "Indoor",
+ "outdoor": "Outdoor"
+ },
+ "notes": {
+ "preApplication": "Please submit your venue application first.",
+ "foodOnlyOutdoor": "Food sales groups can only operate outdoors."
+ }
+ },
+ "fields": {
+ "section": "Item {{index}}",
+ "item": "Item name",
+ "count": "Quantity"
+ },
+ "notes": {
+ "minRequest": "Request only the minimum quantity you need.",
+ "contactLimit": "If you need 20 or more units, please email us.",
+ "contactEmail": "nutfes.soumu@gmail.com"
+ },
+ "buttons": {
+ "addItem": "Add item"
+ },
+ "messages": {
+ "registerNoItemsFailed": "Failed to save the response that no rental is needed.",
+ "deleteExistingError": "An error occurred while deleting existing rental data.",
+ "deleteExistingFailed": "Failed to delete the existing rental request.",
+ "registerNoItemsSuccess": "Saved that no rental request is needed.",
+ "unexpectedError": "An unexpected error occurred.",
+ "unexpectedErrorWithDetail": "An unexpected error occurred: {{message}}",
+ "unexpectedRetry": "An unexpected error occurred. Please try again.",
+ "submitError": "An error occurred while submitting. Please try again.",
+ "submitFailed": "Failed to submit the rental request.",
+ "updateSuccess": "Rental request updated.",
+ "createSuccess": "Rental request submitted.",
+ "editStartFailed": "Failed to start editing."
+ },
+ "validation": {
+ "selectItem": "Please select an item.",
+ "minCount": "Enter at least one.",
+ "selectLocation": "Please select a venue type.",
+ "addOneItem": "Add at least one item.",
+ "fillAllFields": "Complete all item fields.",
+ "noDuplicates": "You cannot add the same item more than once.",
+ "tentLimit": "You can request at most one tent.",
+ "partitionDisplayExclusive": "You can request either partitions or display boards, not both.",
+ "longTableLimit": "You can request at most one long table.",
+ "tableOutdoorLimit": "Outdoor groups can request up to 20 tables.",
+ "chairOutdoorLimit": "Outdoor groups can request up to 20 chairs."
+ }
+ },
+ "purchaseLists": {
+ "title": "Purchase application",
+ "loading": "Loading...",
+ "errors": {
+ "fetch": "Failed to fetch data."
+ },
+ "deadline": {
+ "title": "Submission deadline has passed",
+ "description": "The purchase application deadline is over, so you can no longer submit a new request."
+ },
+ "summary": {
+ "labels": {
+ "foodProduct": "Product name",
+ "items": "Ingredients / materials",
+ "type": "Product type",
+ "shop": "Shop",
+ "date": "Purchase date",
+ "remark": "Remarks",
+ "url": "URL"
+ }
+ },
+ "radio": {
+ "label": "Product type",
+ "options": {
+ "fresh": "Fresh food",
+ "processed": "Processed food"
+ }
+ },
+ "fields": {
+ "section": "Item {{index}}",
+ "foodProduct": "Product name",
+ "items": "Ingredients/materials used for the selected dish",
+ "type": "Product type",
+ "shop": "Shop",
+ "purchaseDate": "Purchase date",
+ "url": "URL",
+ "remark": "Remarks"
+ },
+ "notes": {
+ "foodProduct": "Selectable after you register your food product application.",
+ "shop": "If you choose online order, a URL is required.",
+ "purchaseDate": "Example: 2025/03/14",
+ "url": "Enter the URL of the e-commerce site you used.",
+ "remarkOther": "Enter the shop name, address, phone number, and opening hours.",
+ "remarkDefault": "Add any other details if needed."
+ },
+ "buttons": {
+ "addItem": "Add purchase item"
+ },
+ "messages": {
+ "itemDeleteSuccess": "Deleted the purchase item.",
+ "itemDeleteFailed": "Failed to delete the item.",
+ "bulkCreateSuccess": "Submitted multiple purchase applications.",
+ "updateSuccess": "Purchase application updated.",
+ "createSuccess": "Purchase application submitted.",
+ "submitFailed": "Failed to submit."
+ },
+ "validation": {
+ "foodProduct": "Select a product.",
+ "shop": "Select a shop.",
+ "items": "Enter the ingredients or materials.",
+ "purchaseDate": "Enter the purchase date.",
+ "invalidDate": "Enter a valid date.",
+ "invalidUrl": "Enter a valid URL.",
+ "urlRequired": "Enter a URL for online orders.",
+ "remarkRequired": "For “Other” shops, enter the name, address, phone number, and opening hours.",
+ "minItems": "Register at least one purchase item."
+ }
+ },
+ "foodProduct": {
+ "title": "Food product application",
+ "loading": "Loading...",
+ "errors": {
+ "fetch": "Failed to fetch data."
+ },
+ "deadline": {
+ "title": "Submission deadline has passed",
+ "description": "You can no longer submit new products because the deadline has passed."
+ },
+ "view": {
+ "summaryLabel": "Product list",
+ "registered": "{{count}} items registered",
+ "none": "Not registered",
+ "empty": "No products have been registered.",
+ "addButton": "Add product"
+ },
+ "summary": {
+ "labels": {
+ "name": "Product name",
+ "alcohol": "Is it alcoholic?",
+ "cooking": "Cooking required",
+ "day1": "Day 1 quantity",
+ "day2": "Day 2 quantity"
+ }
+ },
+ "radio": {
+ "alcohol": {
+ "label": "Is this an alcoholic beverage?",
+ "note": "Selecting “Yes” automatically sets cooking to “required.”",
+ "options": {
+ "yes": "Yes",
+ "no": "No"
+ }
+ },
+ "cooking": {
+ "label": "Cooking required",
+ "options": {
+ "yes": "Yes (e.g., alcohol, heated dishes)",
+ "no": "No (e.g., soft drinks)"
+ }
+ }
+ },
+ "fields": {
+ "name": "Product name",
+ "day1": "Planned quantity (Day 1)",
+ "day2": "Planned quantity (Day 2)"
+ },
+ "notes": {
+ "processing": "Processing...",
+ "quantity": "Use half-width numbers."
+ },
+ "buttons": {
+ "add": "Add product"
+ },
+ "messages": {
+ "updateSuccess": "Updated the product list.",
+ "updateFailed": "Failed to update the product list.",
+ "updateFailedDetail": "Failed to update the product list: {{message}}",
+ "createSuccess": "Submitted the product list.",
+ "createFailed": "Failed to submit the product list.",
+ "createFailedDetail": "Failed to submit the product list: {{message}}",
+ "deleteSuccess": "Deleted “{{name}}”.",
+ "deleteFailed": "Failed to delete the product.",
+ "deleteFailedDetail": "Failed to delete the product: {{message}}",
+ "deleteNotFound": "Could not find the product to delete.",
+ "authRequired": "Authentication is required. Please sign in."
+ },
+ "validation": {
+ "name": "Enter a product name.",
+ "isAlcohol": "Select whether it is alcoholic.",
+ "isCooking": "Select whether cooking is required.",
+ "day1": "Enter the planned quantity for Day 1.",
+ "day2": "Enter the planned quantity for Day 2.",
+ "number": "Use half-width numbers.",
+ "minValue": "Enter a value of 1 or more.",
+ "alcoholRequiresCooking": "Alcohol must be marked as requiring cooking.",
+ "minProducts": "Register at least one product."
+ }
+ },
+ "power": {
+ "title": "Power application",
+ "radio": {
+ "question": "Will you submit a power application?",
+ "options": {
+ "yes": "Yes",
+ "no": "No"
+ }
+ },
+ "summary": {
+ "noApplication": {
+ "label": "Power application not required (saved)",
+ "description": "We will not use any devices that require power."
+ },
+ "fields": {
+ "productName": "Product name",
+ "manufacturer": "Manufacturer",
+ "model": "Model number",
+ "url": "Product URL",
+ "maxPower": "Power consumption"
+ },
+ "powerValue": "{{value}} W"
+ },
+ "form": {
+ "fields": {
+ "productName": "Device name",
+ "manufacturer": "Device manufacturer",
+ "model": "Model number",
+ "url": "Product URL",
+ "maxPower": "Power (W)"
+ },
+ "notes": {
+ "url": "Enter the URL of the product introduction page.",
+ "totalPower": "Keep the total consumption of all devices within {{limit}} W.",
+ "emailWarning": "If you need more than {{limit}} W, contact us at the email below.",
+ "contactEmail": "nutfes.soumu@gmail.com"
+ },
+ "addDevice": "Add device",
+ "totalPowerWarning": "Total power exceeds {{limit}} W (current: {{value}} W)."
+ },
+ "errors": {
+ "submitTitle": "Error:"
+ },
+ "messages": {
+ "partialDeleteWarning": "Some devices could not be deleted, but the process will continue.",
+ "registerNegativeSuccess": "Saved your response that no power application is needed.",
+ "registerNegativeFailed": "Failed to save your response. Please try again.",
+ "processError": "Failed to process the application. Please try again.",
+ "missingGroup": "Could not retrieve the group ID.",
+ "unregisteredDeleteWarning": "There was an issue removing the previous response, but the process will continue.",
+ "updateSuccess": "Power application updated.",
+ "createSuccess": "Power application submitted.",
+ "submitFailed": "Failed to submit the application. Please try again.",
+ "submitUnexpectedError": "An error occurred while submitting the application.",
+ "deviceDeleteSuccess": "Deleted the device.",
+ "deviceDeleteFailed": "Failed to delete the device. Please try again.",
+ "deviceDeleteError": "An error occurred while deleting the device."
+ },
+ "validation": {
+ "productNameRequired": "Enter a product name.",
+ "manufacturerRequired": "Enter the manufacturer.",
+ "modelRequired": "Enter the model number.",
+ "invalidUrl": "Enter a valid URL.",
+ "invalidNumber": "Enter a number.",
+ "minPower": "Enter at least 1 W.",
+ "maxPower": "Enter 1500 W or less.",
+ "minDevices": "Register at least one device.",
+ "totalPowerLimit": "Keep the total power consumption within 1500 W."
+ }
+ },
+ "stage": {
+ "title": "Stage application",
+ "loading": "Loading stage data...",
+ "fields": {
+ "date": "Event date",
+ "sunnyFirst": "Sunny: first choice",
+ "sunnySecond": "Sunny: second choice",
+ "rainyFirst": "Rainy: first choice",
+ "rainySecond": "Rainy: second choice",
+ "prepTime": "Preparation time",
+ "performTime": "Performance time",
+ "cleanupTime": "Cleanup time"
+ },
+ "notes": {
+ "select": "Please select",
+ "unit": " (Unit: min)",
+ "prepTime": "Enter the minutes required to prepare on stage.",
+ "performTime": "The total of preparation, performance, and cleanup must be within 120 minutes.",
+ "cleanupTime": "Enter the minutes required to clean up on stage."
+ },
+ "minutes": "{{value}} min",
+ "messages": {
+ "missingGroup": "Group ID was not found.",
+ "submitError": "An error occurred while submitting. Please try again.",
+ "unexpectedError": "An unexpected error occurred. Please try again.",
+ "updateSuccess": "Stage preferences updated.",
+ "createSuccess": "Stage preferences submitted."
+ },
+ "errors": {
+ "fetchTitle": "Error:",
+ "fetchDescription": "Failed to fetch data. Please reload the page.",
+ "submitTitle": "Submission error:"
+ },
+ "validation": {
+ "sunnyFirst": "Select your first sunny-day preference.",
+ "sunnySecond": "Select your second sunny-day preference.",
+ "rainyFirst": "Select your first rainy-day preference.",
+ "rainySecond": "Select your second rainy-day preference.",
+ "prepTimeRequired": "Enter a preparation time.",
+ "performTimeRequired": "Enter a performance time.",
+ "cleanupTimeRequired": "Enter a cleanup time.",
+ "prepTimeInvalid": "Enter a valid preparation time.",
+ "performTimeInvalid": "Enter a valid performance time.",
+ "cleanupTimeInvalid": "Enter a valid cleanup time.",
+ "totalTime": "The total of preparation, performance, and cleanup must be 120 minutes or less.",
+ "sunnyChoiceDuplicate": "Choose a different stage than your first sunny-day choice.",
+ "rainyChoiceDuplicate": "Choose a different stage than your first rainy-day choice."
+ }
+ },
+ "stageOptions": {
+ "title": "Stage option application",
+ "fields": {
+ "ownEquipment": "Will you bring electrical equipment?",
+ "bgm": "Will you bring equipment that connects to the speakers?",
+ "cameraPermission": "Do you allow the committee to film your performance?",
+ "loudSound": "Will you produce loud sounds?"
+ },
+ "notes": {
+ "select": "Please select"
+ },
+ "options": {
+ "yes": "Yes",
+ "no": "No"
+ },
+ "messages": {
+ "submitSuccess": "Submitted successfully.",
+ "submitFailed": "Submission failed. Please try again later."
+ }
+ },
+ "publicRelations": {
+ "title": "Public relations submission",
+ "fields": {
+ "text": "PR text (used on the website, pamphlet, and announcements)",
+ "announce": "Would you like to make an announcement?",
+ "image": "PR image"
+ },
+ "notes": {
+ "text": "Japanese: 0–50 characters / English: 0–25 words",
+ "upload": [
+ "File types: png, jpeg",
+ "File size: under 10 MB",
+ "Image shape: square (ideally a photo of your food/product)"
+ ],
+ "existingImage": "If you don’t upload a new image, the current one will be kept."
+ },
+ "uploadStatus": "Uploaded: {{fileName}}",
+ "options": {
+ "announce": {
+ "yes": "Yes",
+ "no": "No"
+ }
+ },
+ "validation": {
+ "imageRequired": "Please upload an image.",
+ "imageSquare": "Please upload a square image.",
+ "imageLoadFailed": "Failed to load the selected image.",
+ "sizeLimit": "Image files must be under 10 MB.",
+ "format": "Only png or jpeg files are supported.",
+ "jpLimit": "Japanese text must be 50 characters or less.",
+ "enLimit": "English text must be 25 words or fewer."
+ },
+ "messages": {
+ "imgurMissing": "Imgur Client ID is not configured. Check your environment variables.",
+ "imgurUploadFailed": "Failed to upload the image.",
+ "submitSuccess": "Submitted successfully.",
+ "submitFailed": "Submission failed. Please try again later."
+ },
+ "state": {
+ "notSet": "Not set",
+ "missingText": "(No PR text submitted)"
+ }
+ },
+ "viceRepresentative": {
+ "title": "Vice Representative Application",
+ "note": "If you are participating alone, you do not need to submit this application.",
+ "summary": {
+ "individual": {
+ "label": "No vice representative required (saved)",
+ "description": "You are participating alone."
+ }
+ },
+ "fields": {
+ "isIndividual": "Are you participating alone?",
+ "name": "Name",
+ "studentId": "Student ID",
+ "gradeId": "Grade",
+ "departmentId": "Department",
+ "email": "Email address",
+ "tel": "Phone number"
+ },
+ "notes": {
+ "name": "e.g., Taro Nagaoka",
+ "studentId": "8-digit half-width numbers only (e.g., 12345678)",
+ "email": "e.g., 123456@stn.nagaokaut.ac.jp",
+ "tel": "e.g., 09012345678 (no hyphen)"
+ },
+ "radio": {
+ "options": {
+ "individual": "Yes (participating alone)",
+ "group": "No (participating as a group)"
+ }
+ },
+ "messages": {
+ "submitSuccess": "Submitted successfully.",
+ "submitFailed": "Failed to submit."
+ },
+ "validation": {
+ "name": "Enter the vice representative's name.",
+ "studentIdInteger": "Enter numbers only.",
+ "studentIdLength": "Student ID must be 8 digits.",
+ "gradeId": "Select a grade.",
+ "departmentId": "Select a department.",
+ "email": "Enter an email address.",
+ "emailFormat": "Enter a valid email address.",
+ "tel": "Enter a 10 or 11 digit phone number starting with 0."
+ }
+ },
+ "employees": {
+ "title": "Employee Application",
+ "deadline": {
+ "title": "The submission deadline has passed",
+ "description": "The deadline for the employee application has passed, so you can no longer submit a new application."
+ },
+ "summary": {
+ "noApplication": {
+ "label": "No employee application required (saved)",
+ "description": "Only the representative and vice representative will participate."
+ },
+ "headers": {
+ "name": "Employee name",
+ "studentId": "Student ID"
+ }
+ },
+ "radio": {
+ "label": "Will you register members other than the representative and vice representative?",
+ "options": {
+ "yes": "Yes",
+ "no": "No"
+ }
+ },
+ "form": {
+ "labels": {
+ "name": "Employee name",
+ "studentId": "Student ID"
+ },
+ "notes": {
+ "name": "e.g., Hanako NUT",
+ "studentId": "e.g., 12345678"
+ }
+ },
+ "buttons": {
+ "addEmployee": "Add employee"
+ },
+ "messages": {
+ "applicationSuccess": "Employee information saved.",
+ "applicationFailed": "Failed to save the employee information.",
+ "noApplicationSuccess": "Saved that no employee registration is required.",
+ "noApplicationFailed": "Failed to save the response that no employee registration is required.",
+ "deleteSuccess": "Employee deleted.",
+ "deleteFailed": "Failed to delete the employee.",
+ "registerUnregisteredFailed": "Failed to save.",
+ "deleteUnregisteredFailed": "Failed to delete."
+ },
+ "validation": {
+ "name": "Employee name is required.",
+ "studentId": "Enter an 8-digit student ID."
+ }
+ },
+ "cookingProcessOrder": {
+ "title": "Cooking Process Application",
+ "warning": "Please submit your food product application first.",
+ "summary": {
+ "labels": {
+ "foodProduct": "Product name",
+ "preOpen": "Kitchen use (before opening)",
+ "duringOpen": "Kitchen use (during business)",
+ "description": "Cooking details"
+ },
+ "status": {
+ "use": "Will use",
+ "notUse": "Will not use",
+ "notRegistered": "Not submitted"
+ }
+ },
+ "fields": {
+ "kitchenUsage": "Kitchen usage",
+ "preOpen": "(Before opening)",
+ "duringOpen": "(During business)",
+ "tent": "Cooking details",
+ "confirm": "Confirmation items"
+ },
+ "placeholders": {
+ "tent": "Example:\n1. Measure 15 g of coffee beans\n2. Place in filter\n3. Heat\n4. Plate and serve"
+ },
+ "notes": {
+ "confirm": "Check every confirmation item."
+ },
+ "options": {
+ "kitchenUsage": {
+ "use": "Will use",
+ "notUse": "Will not use"
+ }
+ },
+ "checkbox": {
+ "options": [
+ "I described the hygiene control process in as much detail as possible.",
+ "I confirmed whether the final product is heated before serving.",
+ "I also submitted the cooking process for alcoholic beverages."
+ ]
+ },
+ "buttons": {
+ "edit": "Edit"
+ },
+ "messages": {
+ "updateSuccess": "Cooking processes updated.",
+ "updateFailed": "Failed to update the cooking processes."
+ },
+ "validation": {
+ "tentRequired": "Enter the cooking details.",
+ "confirmAll": "Check all confirmation items."
+ }
+ },
+ "venueMap": {
+ "title": "Venue Layout",
+ "fields": {
+ "picture": "Venue layout image",
+ "checklist": "Layout checklist"
+ },
+ "summary": {
+ "notSet": "Not set"
+ },
+ "notes": {
+ "required": "*Required",
+ "existing": "If you do not upload a new image, the current image will be used.",
+ "currentImage": "Current image: {{name}}",
+ "unknownFile": "Unknown file name"
+ },
+ "upload": {
+ "note": [
+ "Show the placement of tables, chairs, and equipment.",
+ "File types: png, jpeg",
+ "File size: 20 MB max"
+ ],
+ "uploaded": "Uploaded: {{fileName}}"
+ },
+ "checklist": {
+ "note": "Check every item.",
+ "options": {
+ "trashPosition": "I included the location of the trash bins.",
+ "foodStorage": "I included where ingredients will be stored.",
+ "allItemsListed": "I added every requested item to the layout.",
+ "fireHazardousMaterials": "I specified where fire or electrical appliances will be used.",
+ "partitionPlacement": "I confirmed that partitions/boards stay outside the kitchen area and are placed on the side of the tent."
+ }
+ },
+ "messages": {
+ "imgurMissing": "Imgur Client ID is not configured. Check your environment variables.",
+ "imgurUploadFailed": "Failed to upload the image.",
+ "submitSuccess": "Submitted successfully.",
+ "submitFailed": "Submission failed. Please try again later."
+ },
+ "buttons": {
+ "submitting": "Submitting..."
+ },
+ "validation": {
+ "imageRequired": "Upload the venue layout image.",
+ "fileSize": "File size must be under 20 MB.",
+ "fileType": "Only png or jpeg files are supported.",
+ "checklist": "Confirm every item."
+ }
+ }
+ },
+ "auth": {
+ "logout": "Log out"
+ },
"footer": {
"copyright": "Copyright © {{year}} NUTMEG. All Rights Reserved."
}
diff --git a/user/public/locales/ja/common.json b/user/public/locales/ja/common.json
index d10e1afcc..f6ec27b2e 100644
--- a/user/public/locales/ja/common.json
+++ b/user/public/locales/ja/common.json
@@ -4,6 +4,62 @@
"japanese": "日本語",
"english": "English"
},
+ "general": {
+ "loading": "読み込み中...",
+ "errors": {
+ "fetch": "データの取得に失敗しました。"
+ }
+ },
+ "form": {
+ "required": "必須",
+ "optional": "任意",
+ "actions": {
+ "register": "登録",
+ "edit": "修正",
+ "cancel": "キャンセル",
+ "delete": "削除",
+ "close": "閉じる",
+ "submit": "送信",
+ "save": "保存",
+ "add": "追加",
+ "back": "戻る",
+ "next": "次へ",
+ "previous": "前へ",
+ "confirm": "確認",
+ "open": "開く",
+ "upload": "アップロード"
+ },
+ "messages": {
+ "nonEditable": "※変更できません",
+ "registerSuccess": "登録しました。",
+ "registerFailed": "登録に失敗しました。",
+ "updateSuccess": "更新しました。",
+ "updateFailed": "更新に失敗しました。"
+ },
+ "validation": {
+ "required": "入力してください",
+ "duplicateChoice": "希望が重複しています",
+ "remarkRequired": "備考に場所を入力してください",
+ "inputError": "入力エラーがあります。",
+ "select": "選択してください"
+ }
+ },
+ "status": {
+ "reception": {
+ "open": "受付中",
+ "deadline": "締切間近",
+ "closed": "受付終了"
+ },
+ "registration": {
+ "registered": "登録済",
+ "unregistered": "未登録"
+ },
+ "progress": {
+ "notRequired": "不要",
+ "completed": "完了",
+ "pending": "未完了"
+ }
+ },
"news": {
"title": "お知らせ",
"none": "お知らせはありません。",
@@ -94,6 +150,742 @@
"retryLogin": "再度ログインしてください。"
}
},
+ "lists": {
+ "grade": {
+ "select": "選択してください",
+ "B1": "B1(学部1年)",
+ "B2": "B2(学部2年)",
+ "B3": "B3(学部3年)",
+ "B4": "B4(学部4年)",
+ "M1": "M1(修士1年)",
+ "M2": "M2(修士2年)",
+ "D1": "D1(博士1年)",
+ "D2": "D2(博士2年)",
+ "D3": "D3(博士3年)",
+ "GD1": "GD1(イノベ1年)",
+ "GD2": "GD2(イノベ2年)",
+ "GD3": "GD3(イノベ3年)",
+ "GD4": "GD4(イノベ4年)",
+ "GD5": "GD5(イノベ5年)",
+ "faculty": "教員",
+ "other": "その他"
+ },
+ "department": {
+ "select": "選択してください",
+ "1": "機械工学分野 / 機械創造工学課程",
+ "2": "電気電子情報工学分野 / 電気電子情報工学課程",
+ "3": "物質生物工学分野 / 物質材料工学課程 / 生物機能工学課程",
+ "4": "環境社会基盤工学分野 / 環境社会基盤工学課程",
+ "5": "情報・経営システム工学分野 / 情報・経営システム工学課程",
+ "6": "機械工学分野 / 機械創造工学専攻",
+ "7": "電気電子情報工学分野 / 電気電子情報工学専攻",
+ "8": "物質生物工学分野 / 物質材料工学専攻 / 生物機能工学専攻",
+ "9": "環境社会基盤工学分野 / 環境社会基盤工学専攻",
+ "10": "情報・経営システム工学分野 / 情報・経営システム工学専攻",
+ "11": "量子・原子力統合工学分野 / 原子力システム安全工学専攻",
+ "12": "システム安全工学専攻",
+ "13": "技術科学イノベーション専攻",
+ "14": "情報・制御工学分野 / 情報・制御工学専攻",
+ "15": "材料工学分野 / 材料工学専攻",
+ "16": "エネルギー工学分野 / エネルギー・環境工学専攻",
+ "17": "社会環境・生物機能工学分野 / 生物統合工学専攻",
+ "18": "その他"
+ }
+ },
+ "userEditModal": {
+ "labels": {
+ "name": "名前",
+ "email": "メールアドレス",
+ "tel": "電話番号",
+ "studentId": "学籍番号",
+ "grade": "学年",
+ "department": "学科"
+ },
+ "notes": {
+ "name": "例:長岡 太郎",
+ "email": "例:s123456@stn.nagaokaut.ac.jp",
+ "tel": "例:09012345678",
+ "studentId": "例:12345678"
+ },
+ "messages": {
+ "emailChangeConfirm": "メールアドレスを変更する場合は、変更後のメールアドレスで再度ログインする必要があります。パスワードは以前のものと同じです。"
+ },
+ "toasts": {
+ "cancelled": "変更はキャンセルされました。",
+ "updateSuccess": "ユーザー情報を登録しました。",
+ "emailChanged": "メールアドレスを変更しました。再度ログインしてください。"
+ },
+ "errors": {
+ "duplicateEmail": "このメールアドレスはすでに使われています。",
+ "updateFailed": "更新に失敗しました。"
+ },
+ "validation": {
+ "name": "名前は必須です",
+ "studentId": "8桁の学籍番号を入力してください",
+ "tel": "有効な電話番号を入力してください(例: 09012345678)",
+ "telMin": "電話番号が短すぎます",
+ "telMax": "電話番号が長すぎます",
+ "email": "有効なメールアドレスを入力してください",
+ "department": "学科を選択してください",
+ "grade": "学年を選択してください"
+ }
+ },
+ "applications": {
+ "group": {
+ "title": "団体申請",
+ "loading": "読み込み中...",
+ "errors": {
+ "fetch": "データの取得に失敗しました。"
+ },
+ "fields": {
+ "name": "団体名",
+ "projectName": "企画名",
+ "isInternational": "国際団体ですか?",
+ "isExternal": "学外団体ですか?",
+ "groupCategory": "参加形式",
+ "activity": "企画内容"
+ },
+ "notes": {
+ "name": "例:技大祭実行委員会",
+ "projectName": "例:ギダイジャー",
+ "international": "注意書き",
+ "external": "注意書き",
+ "groupCategory": "注意書き",
+ "activity": "〇〇の販売、〇〇のパフォーマンスなど"
+ },
+ "boolean": {
+ "yes": "はい",
+ "no": "いいえ"
+ },
+ "options": {
+ "international": {
+ "no": "いいえ、国際団体(留学生団体)ではありません。",
+ "yes": "はい、国際団体(留学生団体)です。"
+ },
+ "external": {
+ "no": "いいえ、学内の団体です。",
+ "yes": "はい、学外の団体です。"
+ }
+ }
+ },
+ "venue": {
+ "title": "会場申請",
+ "loading": "読み込み中...",
+ "fields": {
+ "firstChoice": "第一希望",
+ "secondChoice": "第二希望",
+ "thirdChoice": "第三希望",
+ "remark": "備考"
+ }
+ },
+ "rentItems": {
+ "title": "物品申請",
+ "loading": "データを読み込み中です...",
+ "errors": {
+ "fetchTitle": "エラー:",
+ "fetchDescription": "データの取得に失敗しました。ページを再読込してください。"
+ },
+ "radio": {
+ "question": "物品申請を行いますか?",
+ "options": {
+ "yes": "はい",
+ "no": "いいえ"
+ }
+ },
+ "summary": {
+ "noApplication": {
+ "label": "物品申請は不要(登録済み)",
+ "description": "学校から借用する備品はありません。"
+ },
+ "count": "{{value}} 個"
+ },
+ "location": {
+ "displayLabel": "第一希望:",
+ "radioQuestion": "会場申請の第一希望はどちらですか?",
+ "options": {
+ "indoor": "屋内",
+ "outdoor": "屋外"
+ },
+ "notes": {
+ "preApplication": "会場申請を先に申請してください。",
+ "foodOnlyOutdoor": "※食品販売団体は屋外での出店のみとなります"
+ }
+ },
+ "fields": {
+ "section": "物品 {{index}}",
+ "item": "物品名",
+ "count": "個数"
+ },
+ "notes": {
+ "minRequest": "※必要最低限の数だけ申請してください",
+ "contactLimit": "使用する個数が20個以上の場合はメールをお送りください",
+ "contactEmail": "nutfes.soumu@gmail.com"
+ },
+ "buttons": {
+ "addItem": "物品の追加"
+ },
+ "messages": {
+ "registerNoItemsFailed": "物品申請の登録に失敗しました",
+ "deleteExistingError": "既存の申請データ削除中にエラーが発生しました",
+ "deleteExistingFailed": "既存の物品申請の削除に失敗しました",
+ "registerNoItemsSuccess": "物品申請を行わない設定を登録しました",
+ "unexpectedError": "予期せぬエラーが発生しました",
+ "unexpectedErrorWithDetail": "予期せぬエラーが発生しました: {{message}}",
+ "unexpectedRetry": "予期せぬエラーが発生しました。もう一度お試しください。",
+ "submitError": "送信中にエラーが発生しました。もう一度お試しください。",
+ "submitFailed": "物品申請の送信に失敗しました",
+ "updateSuccess": "物品申請を更新しました",
+ "createSuccess": "物品申請を登録しました",
+ "editStartFailed": "編集モードの開始に失敗しました"
+ },
+ "validation": {
+ "selectItem": "物品を選択してください",
+ "minCount": "1つ以上選択してください",
+ "selectLocation": "会場タイプを選択してください",
+ "addOneItem": "少なくとも1つの物品を追加してください",
+ "fillAllFields": "すべての物品情報を正しく入力してください",
+ "noDuplicates": "同じ物品を複数回追加することはできません",
+ "tentLimit": "テントは1個までしか申請できません",
+ "partitionDisplayExclusive": "パーテーションと掲示板はどちらか一方のみ申請できます",
+ "longTableLimit": "長机は1個までしか申請できません",
+ "tableOutdoorLimit": "屋外団体は机を20個までしか申請できません",
+ "chairOutdoorLimit": "屋外団体は椅子を20個までしか申請できません"
+ }
+ },
+ "purchaseLists": {
+ "title": "購入品申請",
+ "loading": "読み込み中...",
+ "errors": {
+ "fetch": "データの取得に失敗しました。"
+ },
+ "deadline": {
+ "title": "申請期限が過ぎています",
+ "description": "購入品申請の締切期限が過ぎているため、新規申請はできません。"
+ },
+ "summary": {
+ "labels": {
+ "foodProduct": "販売品名",
+ "items": "食材・材料",
+ "type": "商品の種類",
+ "shop": "購入場所",
+ "date": "購入日",
+ "remark": "備考",
+ "url": "URL"
+ }
+ },
+ "radio": {
+ "label": "商品の種類",
+ "options": {
+ "fresh": "生鮮品",
+ "processed": "加工品"
+ }
+ },
+ "fields": {
+ "section": "購入品 {{index}}",
+ "foodProduct": "販売品名",
+ "items": "選択した料理に使用した食材・使用する材料",
+ "type": "商品の種類",
+ "shop": "購入場所",
+ "purchaseDate": "購入日",
+ "url": "URL",
+ "remark": "備考"
+ },
+ "notes": {
+ "foodProduct": "販売品申請登録後に選択可能",
+ "shop": "ネット注文選択時はURL入力が必要です",
+ "purchaseDate": "例:2025/03/14",
+ "url": "購入したECサイトのURLなど",
+ "remarkOther": "店名・住所・電話番号・営業時間を入力してください",
+ "remarkDefault": "その他補足事項があれば入力してください"
+ },
+ "buttons": {
+ "addItem": "購入品を追加"
+ },
+ "messages": {
+ "itemDeleteSuccess": "購入品が削除されました",
+ "itemDeleteFailed": "削除に失敗しました。",
+ "bulkCreateSuccess": "複数の購入品申請が登録されました",
+ "updateSuccess": "購入品申請が更新されました",
+ "createSuccess": "購入品申請が登録されました",
+ "submitFailed": "登録に失敗しました"
+ },
+ "validation": {
+ "foodProduct": "販売品名を選択してください",
+ "shop": "購入場所を選択してください",
+ "items": "食材・材料を入力してください",
+ "purchaseDate": "購入日を入力してください",
+ "invalidDate": "日付を入力してください",
+ "invalidUrl": "有効なURLを入力してください",
+ "urlRequired": "ネット注文の場合はURLを入力してください",
+ "remarkRequired": "「その他」の場合は、店名・住所・電話番号・営業時間を記入してください",
+ "minItems": "少なくとも1つの購入品を登録してください"
+ }
+ },
+ "foodProduct": {
+ "title": "販売品申請",
+ "loading": "読み込み中...",
+ "errors": {
+ "fetch": "データの取得に失敗しました。"
+ },
+ "deadline": {
+ "title": "申請期限が過ぎています",
+ "description": "販売品申請の締切期限が過ぎているため、新規申請はできません。"
+ },
+ "view": {
+ "summaryLabel": "販売品一覧",
+ "registered": "{{count}}品目登録済み",
+ "none": "未登録",
+ "empty": "販売品が登録されていません",
+ "addButton": "販売品を追加"
+ },
+ "summary": {
+ "labels": {
+ "name": "販売品名",
+ "alcohol": "酒類ですか?",
+ "cooking": "調理の有無",
+ "day1": "1日目の販売予定数",
+ "day2": "2日目の販売予定数"
+ }
+ },
+ "radio": {
+ "alcohol": {
+ "label": "酒類ですか?",
+ "note": "「はい」を選択すると、自動的に「調理あり」になります。",
+ "options": {
+ "yes": "はい",
+ "no": "いいえ"
+ }
+ },
+ "cooking": {
+ "label": "調理の有無",
+ "options": {
+ "yes": "有り (例:酒類、加熱調理をするものなど)",
+ "no": "無し (例:ソフトドリンク)"
+ }
+ }
+ },
+ "fields": {
+ "name": "販売品名",
+ "day1": "1日目販売予定数",
+ "day2": "2日目販売予定数"
+ },
+ "notes": {
+ "processing": "処理中...",
+ "quantity": "半角数字"
+ },
+ "buttons": {
+ "add": "販売品の追加"
+ },
+ "messages": {
+ "updateSuccess": "販売品を更新しました。",
+ "updateFailed": "販売品の更新に失敗しました。",
+ "updateFailedDetail": "販売品の更新に失敗しました: {{message}}",
+ "createSuccess": "販売品申請を送信しました。",
+ "createFailed": "販売品の登録に失敗しました。",
+ "createFailedDetail": "販売品の登録に失敗しました: {{message}}",
+ "deleteSuccess": "「{{name}}」を削除しました。",
+ "deleteFailed": "販売品の削除に失敗しました。",
+ "deleteFailedDetail": "販売品の削除に失敗しました: {{message}}",
+ "deleteNotFound": "削除対象の商品が見つかりませんでした。",
+ "authRequired": "認証が必要です。ログインしてください。"
+ },
+ "validation": {
+ "name": "販売品名を入力してください",
+ "isAlcohol": "酒類かどうかを選択してください",
+ "isCooking": "調理の有無を選択してください",
+ "day1": "1日目の販売予定数を入力してください",
+ "day2": "2日目の販売予定数を入力してください",
+ "number": "半角数字で入力してください",
+ "minValue": "1以上の数値を入力してください",
+ "alcoholRequiresCooking": "酒類を販売する場合は調理の有無を「有り」にしてください",
+ "minProducts": "少なくとも1つの販売品を登録してください"
+ }
+ },
+ "power": {
+ "title": "電力申請",
+ "radio": {
+ "question": "電力申請を行いますか?",
+ "options": {
+ "yes": "はい",
+ "no": "いいえ"
+ }
+ },
+ "summary": {
+ "noApplication": {
+ "label": "電力申請は不要(登録済み)",
+ "description": "電力が必要な機器は使用しません。"
+ },
+ "fields": {
+ "productName": "製品名",
+ "manufacturer": "メーカー名",
+ "model": "型番",
+ "url": "製品URL",
+ "maxPower": "消費電力"
+ },
+ "powerValue": "{{value}}W"
+ },
+ "form": {
+ "fields": {
+ "productName": "機器の名称",
+ "manufacturer": "機器のメーカー名",
+ "model": "型番",
+ "url": "製品URL",
+ "maxPower": "電力量 (W)"
+ },
+ "notes": {
+ "url": "製品の紹介ページのサイトURLを入力してください",
+ "totalPower": "使用機器の消費電力の合計が{{limit}}W以内になるようにしてください",
+ "emailWarning": "{{limit}}Wを超える場合は以下のメールアドレスまでご連絡ください。",
+ "contactEmail": "nutfes.soumu@gmail.com"
+ },
+ "addDevice": "物品の追加",
+ "totalPowerWarning": "合計電力が{{limit}}Wを超えています(現在: {{value}}W)"
+ },
+ "errors": {
+ "submitTitle": "エラー:"
+ },
+ "messages": {
+ "partialDeleteWarning": "一部の機器情報の削除に失敗しましたが、処理を続行します。",
+ "registerNegativeSuccess": "電力申請を行わない登録が完了しました。",
+ "registerNegativeFailed": "申請の登録に失敗しました。もう一度お試しください。",
+ "processError": "申請の処理に失敗しました。もう一度お試しください。",
+ "missingGroup": "グループIDが取得できませんでした。",
+ "unregisteredDeleteWarning": "未登録データの削除に問題がありましたが、処理を続行します。",
+ "updateSuccess": "電力申請情報を更新しました。",
+ "createSuccess": "電力申請情報を登録しました。",
+ "submitFailed": "申請の送信に失敗しました。もう一度お試しください。",
+ "submitUnexpectedError": "申請の送信中にエラーが発生しました。",
+ "deviceDeleteSuccess": "機器情報を削除しました。",
+ "deviceDeleteFailed": "機器の削除に失敗しました。もう一度お試しください。",
+ "deviceDeleteError": "機器の削除中にエラーが発生しました。"
+ },
+ "validation": {
+ "productNameRequired": "製品名を入力してください",
+ "manufacturerRequired": "メーカー名を入力してください",
+ "modelRequired": "型番を入力してください",
+ "invalidUrl": "有効なURLを入力してください",
+ "invalidNumber": "数値を入力してください",
+ "minPower": "1W以上で入力してください",
+ "maxPower": "1500W以下で入力してください",
+ "minDevices": "少なくとも1つの機器を登録してください",
+ "totalPowerLimit": "合計消費電力は1500W以下にしてください"
+ }
+ },
+ "stage": {
+ "title": "ステージ申請",
+ "loading": "データを読み込み中です...",
+ "fields": {
+ "date": "開催日",
+ "sunnyFirst": "晴れの場合:第1希望",
+ "sunnySecond": "晴れの場合:第2希望",
+ "rainyFirst": "雨の場合:第1希望",
+ "rainySecond": "雨の場合:第2希望",
+ "prepTime": "準備時間",
+ "performTime": "本番時間",
+ "cleanupTime": "片付け時間"
+ },
+ "notes": {
+ "select": "選んでください",
+ "unit": "(単位:min)",
+ "prepTime": "ステージ上の準備にかかる時間を分単位で記入してください",
+ "performTime": "準備、本番、片付けの時間が120分以内になるようにしてください",
+ "cleanupTime": "ステージ上の片付けにかかる時間を分単位で記入してください"
+ },
+ "minutes": "{{value}}分",
+ "messages": {
+ "missingGroup": "グループIDが見つかりません",
+ "submitError": "送信中にエラーが発生しました。もう一度お試しください。",
+ "unexpectedError": "予期せぬエラーが発生しました。もう一度お試しください。",
+ "updateSuccess": "ステージ希望を更新しました。",
+ "createSuccess": "ステージ希望を登録しました。"
+ },
+ "errors": {
+ "fetchTitle": "エラー:",
+ "fetchDescription": "データの取得に失敗しました。ページを再読込してください。",
+ "submitTitle": "送信エラー:"
+ },
+ "validation": {
+ "sunnyFirst": "晴れの第1希望を選択してください",
+ "sunnySecond": "晴れの第2希望を選択してください",
+ "rainyFirst": "雨の第1希望を選択してください",
+ "rainySecond": "雨の第2希望を選択してください",
+ "prepTimeRequired": "準備時間を入力してください",
+ "performTimeRequired": "本番時間を入力してください",
+ "cleanupTimeRequired": "片付け時間を入力してください",
+ "prepTimeInvalid": "有効な準備時間を入力してください",
+ "performTimeInvalid": "有効な本番時間を入力してください",
+ "cleanupTimeInvalid": "有効な片付け時間を入力してください",
+ "totalTime": "準備、本番、片付けの合計時間が120分を超えています",
+ "sunnyChoiceDuplicate": "第1希望と異なるステージを選んでください",
+ "rainyChoiceDuplicate": "第1希望と異なるステージを選んでください"
+ }
+ },
+ "stageOptions": {
+ "title": "ステージオプション申請",
+ "fields": {
+ "ownEquipment": "電力を使用する機器を持ち込みますか",
+ "bgm": "スピーカーに繋ぐ機器を持ち込みますか",
+ "cameraPermission": "実行委員が撮影することを許可しますか",
+ "loudSound": "大きい音を出しますか"
+ },
+ "notes": {
+ "select": "選んでください"
+ },
+ "options": {
+ "yes": "はい",
+ "no": "いいえ"
+ },
+ "messages": {
+ "submitSuccess": "送信しました",
+ "submitFailed": "送信に失敗しました。時間を置いて再度お試しください"
+ }
+ },
+ "publicRelations": {
+ "title": "PR文申請",
+ "fields": {
+ "text": "PR文(HP,パンフレット,アナウンスに使用)",
+ "announce": "アナウンスを行いますか?",
+ "image": "PR画像"
+ },
+ "notes": {
+ "text": "日本語の場合:0~50文字、英語の場合:0~25words",
+ "upload": [
+ "ファイル形式:png、jpeg",
+ "ファイルサイズ:10MB未満",
+ "画像、イラストの形:正方形(できれば料理の写真)"
+ ],
+ "existingImage": "※新しい画像をアップロードしない場合、既存の画像がそのまま使用されます"
+ },
+ "uploadStatus": "アップロード済み: {{fileName}}",
+ "options": {
+ "announce": {
+ "yes": "はい",
+ "no": "いいえ"
+ }
+ },
+ "validation": {
+ "imageRequired": "画像をアップロードしてください",
+ "imageSquare": "画像は正方形にしてください",
+ "imageLoadFailed": "画像の読み込みに失敗しました",
+ "sizeLimit": "ファイルサイズは10MB未満にしてください",
+ "format": "ファイル形式はpngまたはjpegにしてください",
+ "jpLimit": "日本語は50文字以内で入力してください",
+ "enLimit": "英語は25単語以内で入力してください"
+ },
+ "messages": {
+ "imgurMissing": "Imgur Client IDが設定されていません。環境変数を確認してください。",
+ "imgurUploadFailed": "画像のアップロードに失敗しました",
+ "submitSuccess": "送信しました",
+ "submitFailed": "送信に失敗しました。時間を置いて再度お試しください"
+ },
+ "state": {
+ "notSet": "未設定",
+ "missingText": "(PR文が未入力です)"
+ }
+ },
+ "viceRepresentative": {
+ "title": "副代表申請",
+ "note": "一人での参加者の場合のみ、副代表申請は不要です。",
+ "summary": {
+ "individual": {
+ "label": "副代表申請は不要(登録済み)",
+ "description": "あなたは1人での参加です"
+ }
+ },
+ "fields": {
+ "isIndividual": "一人での参加ですか?",
+ "name": "名前",
+ "studentId": "学籍番号",
+ "gradeId": "課程・学年",
+ "departmentId": "学科・専攻",
+ "email": "メールアドレス",
+ "tel": "電話番号"
+ },
+ "notes": {
+ "name": "例:長岡 太郎",
+ "studentId": "半角数字のみ8桁(例:12345678)",
+ "email": "例:123456@stn.nagaokaut.ac.jp",
+ "tel": "例:09012345678(ハイフンなし)"
+ },
+ "radio": {
+ "options": {
+ "individual": "はい(個人で参加)",
+ "group": "いいえ(グループで参加)"
+ }
+ },
+ "messages": {
+ "submitSuccess": "送信しました。",
+ "submitFailed": "送信に失敗しました。"
+ },
+ "validation": {
+ "name": "名前を入力してください",
+ "studentIdInteger": "整数で入力してください",
+ "studentIdLength": "学籍番号は8桁で入力してください",
+ "gradeId": "課程・学年を選択してください",
+ "departmentId": "学科・専攻を選択してください",
+ "email": "メールアドレスを入力してください",
+ "emailFormat": "有効なメールアドレスを入力してください",
+ "tel": "電話番号は0から始まる10桁または11桁の半角数字のみで入力してください"
+ }
+ },
+ "employees": {
+ "title": "従業員申請",
+ "deadline": {
+ "title": "申請期限が過ぎています",
+ "description": "従業員申請の締切期限が過ぎているため、新規申請はできません。"
+ },
+ "summary": {
+ "noApplication": {
+ "label": "従業員申請は不要(登録済み)",
+ "description": "代表と副代表だけで活動します。"
+ },
+ "headers": {
+ "name": "従業員名",
+ "studentId": "学籍番号"
+ }
+ },
+ "radio": {
+ "label": "「代表」と「副代表」以外の従業員申請を行いますか?",
+ "options": {
+ "yes": "はい",
+ "no": "いいえ"
+ }
+ },
+ "form": {
+ "labels": {
+ "name": "従業員名",
+ "studentId": "学籍番号"
+ },
+ "notes": {
+ "name": "例:技大 花子",
+ "studentId": "例:12345678"
+ }
+ },
+ "buttons": {
+ "addEmployee": "従業員の追加"
+ },
+ "messages": {
+ "applicationSuccess": "従業員申請が完了しました。",
+ "applicationFailed": "従業員申請の登録に失敗しました。",
+ "noApplicationSuccess": "従業員申請を行わない登録が完了しました。",
+ "noApplicationFailed": "従業員申請を行わない登録に失敗しました。",
+ "deleteSuccess": "従業員を削除しました。",
+ "deleteFailed": "従業員の削除に失敗しました。",
+ "registerUnregisteredFailed": "登録に失敗しました。",
+ "deleteUnregisteredFailed": "削除に失敗しました。"
+ },
+ "validation": {
+ "name": "従業員名は必須です",
+ "studentId": "8桁の学籍番号を入力してください"
+ }
+ },
+ "cookingProcessOrder": {
+ "title": "調理工程申請",
+ "warning": "販売品申請を先に申請してください。",
+ "summary": {
+ "labels": {
+ "foodProduct": "販売品名",
+ "preOpen": "調理場の使用有無(営業前)",
+ "duringOpen": "調理場の使用有無(営業中)",
+ "description": "調理内容"
+ },
+ "status": {
+ "use": "使用する",
+ "notUse": "使用しない",
+ "notRegistered": "未登録"
+ }
+ },
+ "fields": {
+ "kitchenUsage": "調理場の使用有無",
+ "preOpen": "(営業前)",
+ "duringOpen": "(営業中)",
+ "tent": "調理内容",
+ "confirm": "調理工程確認事項"
+ },
+ "placeholders": {
+ "tent": "例)\n1. コーヒー豆を15g測る\n2. 入れる\n3. 温める\n4. 皿に乗せる"
+ },
+ "notes": {
+ "confirm": "確認事項にチェックを入れてください"
+ },
+ "options": {
+ "kitchenUsage": {
+ "use": "使用する",
+ "notUse": "使用しない"
+ }
+ },
+ "checkbox": {
+ "options": [
+ "衛生管理の工程をできるだけ詳しく記載しました。",
+ "最終的に加熱して提供するか確認しました。",
+ "お酒の調理工程も提出しました。"
+ ]
+ },
+ "buttons": {
+ "edit": "修正"
+ },
+ "messages": {
+ "updateSuccess": "調理工程を更新しました。",
+ "updateFailed": "調理工程の更新に失敗しました。"
+ },
+ "validation": {
+ "tentRequired": "調理内容を入力してください",
+ "confirmAll": "すべての確認事項にチェックを入れてください"
+ }
+ },
+ "venueMap": {
+ "title": "模擬店平面図",
+ "fields": {
+ "picture": "模擬店平面図画像",
+ "checklist": "平面図確認事項"
+ },
+ "summary": {
+ "notSet": "未設定"
+ },
+ "notes": {
+ "required": "※必須",
+ "existing": "※新しい画像をアップロードしない場合、既存の画像がそのまま使用されます。",
+ "currentImage": "現在の画像: {{name}}",
+ "unknownFile": "ファイル名不明"
+ },
+ "upload": {
+ "note": [
+ "机、椅子、使用機器などの配置が分かるように",
+ "ファイル形式:png、jpeg",
+ "ファイルサイズ:20MB"
+ ],
+ "uploaded": "アップロード済み: {{fileName}}"
+ },
+ "checklist": {
+ "note": "確認事項にチェックを入れてください",
+ "options": {
+ "trashPosition": "ゴミ箱の設置位置を記載しました。",
+ "foodStorage": "食材の保存場所を記載しました。",
+ "allItemsListed": "申請した物品をすべて平面図に記載しました。",
+ "fireHazardousMaterials": "火気・電化製品の使用場所を明記しました。",
+ "partitionPlacement": "パーテーション/掲示板が調理場内に入っておらず、テントの側面に設置してあることを確認しました。"
+ }
+ },
+ "messages": {
+ "imgurMissing": "Imgur Client IDが設定されていません。環境変数を確認してください。",
+ "imgurUploadFailed": "画像のアップロードに失敗しました。",
+ "submitSuccess": "送信しました。",
+ "submitFailed": "送信に失敗しました。時間を置いて再度お試しください。"
+ },
+ "buttons": {
+ "submitting": "送信中..."
+ },
+ "validation": {
+ "imageRequired": "模擬店平面図画像をアップロードしてください。",
+ "fileSize": "ファイルサイズは20MB未満にしてください",
+ "fileType": "ファイル形式はpngまたはjpegにしてください",
+ "checklist": "すべての項目を確認してください。"
+ }
+ }
+ },
+ "auth": {
+ "logout": "ログアウト"
+ },
"footer": {
"copyright": "Copyright © {{year}} NUTMEG. All Rights Reserved."
}
From dd0af2de27239ebd8f0cb90f750139d1ccdd0e2f Mon Sep 17 00:00:00 2001
From: hikahana <22.h.hanada.nutfes@gmail.com>
Date: Thu, 1 Jan 2026 12:53:31 +0900
Subject: [PATCH 13/57] =?UTF-8?q?[feat]=20AccordionMenu=E3=82=B3=E3=83=B3?=
=?UTF-8?q?=E3=83=9D=E3=83=BC=E3=83=8D=E3=83=B3=E3=83=88=E3=81=ABi18next?=
=?UTF-8?q?=E3=82=92=E4=BD=BF=E7=94=A8=E3=81=97=E3=81=9F=E7=BF=BB=E8=A8=B3?=
=?UTF-8?q?=E6=A9=9F=E8=83=BD=E3=82=92=E8=BF=BD=E5=8A=A0=E3=81=97=E3=80=81?=
=?UTF-8?q?=E5=BF=85=E9=A0=88=E3=83=BB=E4=BB=BB=E6=84=8F=E3=81=AE=E8=A1=A8?=
=?UTF-8?q?=E7=A4=BA=E3=82=92=E3=83=AD=E3=83=BC=E3=82=AB=E3=83=A9=E3=82=A4?=
=?UTF-8?q?=E3=82=BA?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
user/src/components/AccordionMenu/AccordionMenu.tsx | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/user/src/components/AccordionMenu/AccordionMenu.tsx b/user/src/components/AccordionMenu/AccordionMenu.tsx
index 7ac1c2d6e..3a1aa77e8 100755
--- a/user/src/components/AccordionMenu/AccordionMenu.tsx
+++ b/user/src/components/AccordionMenu/AccordionMenu.tsx
@@ -1,4 +1,5 @@
import React, { FC, useState } from 'react';
+import { useTranslation } from 'next-i18next';
import { RiArrowDownWideLine } from 'react-icons/ri';
import { Textfit } from 'react-textfitfix';
import Status from '@/components/Status';
@@ -22,6 +23,7 @@ const AccordionMenu: FC = ({
required,
note,
}) => {
+ const { t } = useTranslation('common');
const receptionStatus = isEdit ? 'open' : 'closed';
const registerStatus =
@@ -49,7 +51,7 @@ const AccordionMenu: FC = ({
- {required ? '必須' : '任意'}
+ {required ? t('form.required') : t('form.optional')}
From 19d165591651d4f38c9aeb8f21a488b3c7265f4d Mon Sep 17 00:00:00 2001
From: hikahana <22.h.hanada.nutfes@gmail.com>
Date: Thu, 1 Jan 2026 12:54:25 +0900
Subject: [PATCH 14/57] =?UTF-8?q?[feat]=20CookingProcessOrder=E3=82=B3?=
=?UTF-8?q?=E3=83=B3=E3=83=9D=E3=83=BC=E3=83=8D=E3=83=B3=E3=83=88=E3=81=8A?=
=?UTF-8?q?=E3=82=88=E3=81=B3=E9=96=A2=E9=80=A3=E3=83=95=E3=82=A9=E3=83=BC?=
=?UTF-8?q?=E3=83=A0=E3=81=ABi18next=E3=82=92=E4=BD=BF=E7=94=A8=E3=81=97?=
=?UTF-8?q?=E3=81=9F=E7=BF=BB=E8=A8=B3=E6=A9=9F=E8=83=BD=E3=82=92=E8=BF=BD?=
=?UTF-8?q?=E5=8A=A0=E3=81=97=E3=80=81=E8=A1=A8=E7=A4=BA=E3=83=86=E3=82=AD?=
=?UTF-8?q?=E3=82=B9=E3=83=88=E3=82=84=E3=83=90=E3=83=AA=E3=83=87=E3=83=BC?=
=?UTF-8?q?=E3=82=B7=E3=83=A7=E3=83=B3=E3=83=A1=E3=83=83=E3=82=BB=E3=83=BC?=
=?UTF-8?q?=E3=82=B8=E3=82=92=E3=83=AD=E3=83=BC=E3=82=AB=E3=83=A9=E3=82=A4?=
=?UTF-8?q?=E3=82=BA?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../CookingProcessOrder.tsx | 59 ++++++++++++++-----
.../CookingProcessOrderForm.tsx | 52 ++++++++--------
.../CookingProcessOrderForm/schema.ts | 9 ++-
.../Applications/CookingProcessOrder/hooks.ts | 8 ++-
4 files changed, 85 insertions(+), 43 deletions(-)
diff --git a/user/src/components/Applications/CookingProcessOrder/CookingProcessOrder.tsx b/user/src/components/Applications/CookingProcessOrder/CookingProcessOrder.tsx
index 885f49af8..f69529392 100644
--- a/user/src/components/Applications/CookingProcessOrder/CookingProcessOrder.tsx
+++ b/user/src/components/Applications/CookingProcessOrder/CookingProcessOrder.tsx
@@ -1,4 +1,5 @@
import { FC } from 'react';
+import { useTranslation } from 'next-i18next';
import { FormProvider } from 'react-hook-form';
import AccordionMenu from '@/components/AccordionMenu';
import Button from '@/components/Button';
@@ -17,6 +18,7 @@ const CookingProcessOrder: FC
= ({
groupId,
isDeadline,
}) => {
+ const { t } = useTranslation('common');
const {
methods,
fields,
@@ -30,12 +32,12 @@ const CookingProcessOrder: FC = ({
} = useCookingProcessOrder(groupId, isDeadline);
if (isLoading) {
- return Loading...
;
+ return {t('general.loading')}
;
}
return (
= ({
>
{shouldShowWarning ? (
- 販売品申請を先に申請してください
+ {t('applications.cookingProcessOrder.warning')}
) : (
@@ -70,7 +72,9 @@ const CookingProcessOrder: FC = ({
}
icon={isExist ? 'save' : 'send'}
>
- {isExist ? '更新' : '登録'}
+ {isExist
+ ? t('form.actions.save')
+ : t('form.actions.register')}
>
@@ -83,32 +87,57 @@ const CookingProcessOrder: FC
= ({
cookingProcessOrder
? [
{
- label: '販売品名',
+ label: t(
+ 'applications.cookingProcessOrder.summary.labels.foodProduct'
+ ),
content: foodProduct.name,
},
{
- label: '調理場の使用有無(営業前)',
+ label: t(
+ 'applications.cookingProcessOrder.summary.labels.preOpen'
+ ),
content: cookingProcessOrder.preOpenKitchen
- ? '使用する'
- : '使用しない',
+ ? t(
+ 'applications.cookingProcessOrder.summary.status.use'
+ )
+ : t(
+ 'applications.cookingProcessOrder.summary.status.notUse'
+ ),
},
{
- label: '調理場の使用有無(営業中)',
+ label: t(
+ 'applications.cookingProcessOrder.summary.labels.duringOpen'
+ ),
content: cookingProcessOrder.duringOpenKitchen
- ? '使用する'
- : '使用しない',
+ ? t(
+ 'applications.cookingProcessOrder.summary.status.use'
+ )
+ : t(
+ 'applications.cookingProcessOrder.summary.status.notUse'
+ ),
},
{
- label: '調理内容',
+ label: t(
+ 'applications.cookingProcessOrder.summary.labels.description'
+ ),
content: cookingProcessOrder.tent || '',
},
]
: [
{
- label: '販売品名',
+ label: t(
+ 'applications.cookingProcessOrder.summary.labels.foodProduct'
+ ),
content: foodProduct.name,
},
- { label: '調理工程', content: '未登録' },
+ {
+ label: t(
+ 'applications.cookingProcessOrder.title'
+ ),
+ content: t(
+ 'applications.cookingProcessOrder.summary.status.notRegistered'
+ ),
+ },
]
}
/>
@@ -125,7 +154,7 @@ const CookingProcessOrder: FC = ({
icon="pencil"
onClick={handleEditClick}
>
- 修正
+ {t('form.actions.edit')}
)}
diff --git a/user/src/components/Applications/CookingProcessOrder/CookingProcessOrderForm/CookingProcessOrderForm.tsx b/user/src/components/Applications/CookingProcessOrder/CookingProcessOrderForm/CookingProcessOrderForm.tsx
index 2ba4e9ac7..274bbe8cd 100755
--- a/user/src/components/Applications/CookingProcessOrder/CookingProcessOrderForm/CookingProcessOrderForm.tsx
+++ b/user/src/components/Applications/CookingProcessOrder/CookingProcessOrderForm/CookingProcessOrderForm.tsx
@@ -1,4 +1,5 @@
import { FC } from 'react';
+import { useTranslation } from 'next-i18next';
import { useFormContext } from 'react-hook-form';
import CheckBox from '../../../Form/CheckBox';
import Radio from '../../../Form/Radio';
@@ -16,6 +17,7 @@ const CookingProcessOrderForm: FC
= ({
foodProductName,
}) => {
const { setValue } = useFormContext();
+ const { t } = useTranslation('common');
const { values, getError } = useCookingProcessOrderForm(index);
// 調理場使用状況の定数
@@ -25,38 +27,42 @@ const CookingProcessOrderForm: FC = ({
} as const;
const option = [
- { id: KITCHEN_USAGE.USE, name: '使用する' },
- { id: KITCHEN_USAGE.NOT_USE, name: '使用しない' },
- ];
-
- const confirmCookingProcess = [
- {
- id: '1',
- name: '衛生管理の工程をできるだけ詳しく記載しました',
- },
{
- id: '2',
- name: '最終的に加熱して提供するか確認しました',
+ id: KITCHEN_USAGE.USE,
+ name: t('applications.cookingProcessOrder.options.kitchenUsage.use'),
},
{
- id: '3',
- name: 'お酒の調理工程も提出しました',
+ id: KITCHEN_USAGE.NOT_USE,
+ name: t('applications.cookingProcessOrder.options.kitchenUsage.notUse'),
},
];
+ const confirmCookingProcess = (
+ t('applications.cookingProcessOrder.checkbox.options', {
+ returnObjects: true,
+ }) as string[]
+ ).map((label, idx) => ({
+ id: String(idx + 1),
+ name: label,
+ }));
+
return (
-
販売品名
+
+ {t('applications.cookingProcessOrder.summary.labels.foodProduct')}
+
{foodProductName}
-
調理場の使用有無
-
※必須
+
+ {t('applications.cookingProcessOrder.fields.kitchenUsage')}
+
+
※{t('form.required')}
= ({
error={getError('preOpenKitchen')}
/>
= ({
error={getError('duringOpenKitchen')}
/>
diff --git a/user/src/components/Applications/CookingProcessOrder/CookingProcessOrderForm/schema.ts b/user/src/components/Applications/CookingProcessOrder/CookingProcessOrderForm/schema.ts
index 89cfa9af1..b190476e5 100644
--- a/user/src/components/Applications/CookingProcessOrder/CookingProcessOrderForm/schema.ts
+++ b/user/src/components/Applications/CookingProcessOrder/CookingProcessOrderForm/schema.ts
@@ -1,14 +1,19 @@
import { z } from 'zod';
+const VALIDATION_MESSAGES = {
+ TENT: 'applications.cookingProcessOrder.validation.tentRequired',
+ CONFIRM: 'applications.cookingProcessOrder.validation.confirmAll',
+} as const;
+
const singleCookingProcessOrderSchema = z.object({
id: z.number().optional(),
foodProductId: z.number(),
foodProductName: z.string(),
preOpenKitchen: z.boolean(),
duringOpenKitchen: z.boolean(),
- tent: z.string().min(1, { message: '調理内容を入力してください' }),
+ tent: z.string().min(1, { message: VALIDATION_MESSAGES.TENT }),
confirmCookingProcess: z.array(z.string()).refine((val) => val.length === 3, {
- message: 'すべての確認事項にチェックを入れてください',
+ message: VALIDATION_MESSAGES.CONFIRM,
}),
});
diff --git a/user/src/components/Applications/CookingProcessOrder/hooks.ts b/user/src/components/Applications/CookingProcessOrder/hooks.ts
index 5311f4c7e..850245ffc 100644
--- a/user/src/components/Applications/CookingProcessOrder/hooks.ts
+++ b/user/src/components/Applications/CookingProcessOrder/hooks.ts
@@ -5,6 +5,7 @@ import {
} from '@/api/cookingProcessOrderApi';
import { useGetFoodProducts } from '@/api/foodProductApi';
import { zodResolver } from '@hookform/resolvers/zod';
+import { useTranslation } from 'next-i18next';
import { useFieldArray, useForm } from 'react-hook-form';
import { toast } from 'react-toastify';
import {
@@ -17,6 +18,7 @@ export const useCookingProcessOrder = (
isDeadline: boolean
) => {
const [isEditing, setIsEditing] = useState(false);
+ const { t } = useTranslation('common');
const {
cookingProcessOrders,
@@ -112,11 +114,13 @@ export const useCookingProcessOrder = (
});
await mutateCookingProcessOrders();
- toast.success('調理工程を更新しました');
+ toast.success(
+ t('applications.cookingProcessOrder.messages.updateSuccess')
+ );
setIsEditing(false);
} catch (e) {
console.error(e);
- toast.error('調理工程の更新に失敗しました');
+ toast.error(t('applications.cookingProcessOrder.messages.updateFailed'));
}
});
From 7661196bd0052eddf4a820b93a1d9d29705f05c5 Mon Sep 17 00:00:00 2001
From: hikahana <22.h.hanada.nutfes@gmail.com>
Date: Thu, 1 Jan 2026 12:54:32 +0900
Subject: [PATCH 15/57] =?UTF-8?q?[feat]=20Employees=E3=82=B3=E3=83=B3?=
=?UTF-8?q?=E3=83=9D=E3=83=BC=E3=83=8D=E3=83=B3=E3=83=88=E3=81=8A=E3=82=88?=
=?UTF-8?q?=E3=81=B3=E9=96=A2=E9=80=A3=E3=83=95=E3=83=83=E3=82=AF=E3=81=AB?=
=?UTF-8?q?i18next=E3=82=92=E4=BD=BF=E7=94=A8=E3=81=97=E3=81=9F=E7=BF=BB?=
=?UTF-8?q?=E8=A8=B3=E6=A9=9F=E8=83=BD=E3=82=92=E8=BF=BD=E5=8A=A0=E3=81=97?=
=?UTF-8?q?=E3=80=81=E8=A1=A8=E7=A4=BA=E3=83=86=E3=82=AD=E3=82=B9=E3=83=88?=
=?UTF-8?q?=E3=82=84=E3=83=90=E3=83=AA=E3=83=87=E3=83=BC=E3=82=B7=E3=83=A7?=
=?UTF-8?q?=E3=83=B3=E3=83=A1=E3=83=83=E3=82=BB=E3=83=BC=E3=82=B8=E3=82=92?=
=?UTF-8?q?=E3=83=AD=E3=83=BC=E3=82=AB=E3=83=A9=E3=82=A4=E3=82=BA?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../Applications/Employees/Employees.tsx | 34 ++++++++++++-------
.../Employees/EmployeesFrom/EmployeesForm.tsx | 12 ++++---
.../Applications/Employees/hooks.ts | 31 ++++++++++++-----
.../Applications/Employees/schema.ts | 9 +++--
4 files changed, 58 insertions(+), 28 deletions(-)
diff --git a/user/src/components/Applications/Employees/Employees.tsx b/user/src/components/Applications/Employees/Employees.tsx
index e6294ef3a..9b83cdbc0 100644
--- a/user/src/components/Applications/Employees/Employees.tsx
+++ b/user/src/components/Applications/Employees/Employees.tsx
@@ -1,5 +1,6 @@
import { FC } from 'react';
import { NEED_APPLICATION, RADIO_VALUE } from '@/utils/constants';
+import { useTranslation } from 'next-i18next';
import { FormProvider } from 'react-hook-form';
import AccordionMenu from '@/components/AccordionMenu';
import Button from '@/components/Button';
@@ -24,9 +25,10 @@ export const Employees: FC = ({
groupId,
mutateCheckAllRegisteredGroups,
}) => {
+ const { t } = useTranslation('common');
return (
= ({
isDeadline,
mutateCheckAllRegisteredGroups,
}) => {
+ const { t } = useTranslation('common');
// すべてのロジックをhookに委譲
const logic = useEmployeesMainLogic(
groupId,
@@ -82,10 +85,10 @@ const Content: FC = ({
- 申請期限が過ぎています
+ {t('applications.employees.deadline.title')}
- 従業員申請の締切期限が過ぎているため、新規申請はできません。
+ {t('applications.employees.deadline.description')}
@@ -98,8 +101,10 @@ const Content: FC = ({
= ({
return (
= ({
@@ -200,7 +208,7 @@ const Content: FC = ({
type="button"
onClick={logic.handleNoApplicationClick}
>
- 登録
+ {t('form.actions.register')}
)}
diff --git a/user/src/components/Applications/Employees/EmployeesFrom/EmployeesForm.tsx b/user/src/components/Applications/Employees/EmployeesFrom/EmployeesForm.tsx
index a48cda25b..0d44816c9 100644
--- a/user/src/components/Applications/Employees/EmployeesFrom/EmployeesForm.tsx
+++ b/user/src/components/Applications/Employees/EmployeesFrom/EmployeesForm.tsx
@@ -1,4 +1,5 @@
import { FC } from 'react';
+import { useTranslation } from 'next-i18next';
import { Controller, useFormContext } from 'react-hook-form';
import Button from '@/components/Button';
import TextBox from '@/components/Form/TextBox';
@@ -11,6 +12,7 @@ type Props = {
export const EmployeeForm: FC
= ({ index, onDelete }) => {
const { control } = useFormContext();
+ const { t } = useTranslation('common');
return (
@@ -19,13 +21,13 @@ export const EmployeeForm: FC = ({ index, onDelete }) => {
name={`employees.${index}.name` as const}
render={({ field, fieldState }) => (
)}
/>
@@ -34,13 +36,13 @@ export const EmployeeForm: FC = ({ index, onDelete }) => {
name={`employees.${index}.studentId` as const}
render={({ field, fieldState }) => (
)}
/>
@@ -53,7 +55,7 @@ export const EmployeeForm: FC = ({ index, onDelete }) => {
variant
onClick={onDelete}
>
- 削除
+ {t('form.actions.delete')}
diff --git a/user/src/components/Applications/Employees/hooks.ts b/user/src/components/Applications/Employees/hooks.ts
index 3cd1f8c67..f373f7029 100644
--- a/user/src/components/Applications/Employees/hooks.ts
+++ b/user/src/components/Applications/Employees/hooks.ts
@@ -25,6 +25,7 @@ import {
useMutateUnregisteredGroup,
} from '@/api/unRegisteredGroupApi';
import { NEED_APPLICATION } from '@/utils/constants';
+import { useTranslation } from 'next-i18next';
import { toast } from 'react-toastify';
import {
useEmployeesForm,
@@ -180,6 +181,7 @@ export const useEmployeesBusinessLogic = (
onError?: (message: string) => void;
}
) => {
+ const { t } = useTranslation('common');
const {
getEmployeesData,
mutateEmployees,
@@ -213,10 +215,14 @@ export const useEmployeesBusinessLogic = (
await upsertEmployees(data.employees);
}
await mutateEmployees();
- callbacks.onSuccess?.('従業員申請が完了しました');
+ callbacks.onSuccess?.(
+ t('applications.employees.messages.applicationSuccess')
+ );
} catch (error) {
console.error('Error in employee application:', error);
- callbacks.onError?.('登録に失敗しました');
+ callbacks.onError?.(
+ t('applications.employees.messages.applicationFailed')
+ );
throw error;
}
};
@@ -236,10 +242,14 @@ export const useEmployeesBusinessLogic = (
}
await mutateEmployees();
}
- callbacks.onSuccess?.('従業員申請を行わない登録が完了しました');
+ callbacks.onSuccess?.(
+ t('applications.employees.messages.noApplicationSuccess')
+ );
} catch (error) {
console.error('Error in no application:', error);
- callbacks.onError?.('登録に失敗しました');
+ callbacks.onError?.(
+ t('applications.employees.messages.noApplicationFailed')
+ );
throw error;
}
};
@@ -251,11 +261,11 @@ export const useEmployeesBusinessLogic = (
const handleEmployeeDeleteWithToast = async (employeeId: number) => {
try {
await deleteEmployee(employeeId);
- callbacks.onSuccess?.('従業員を削除しました');
+ callbacks.onSuccess?.(t('applications.employees.messages.deleteSuccess'));
await mutateEmployees();
} catch (error) {
console.error('Error deleting employee:', error);
- callbacks.onError?.('削除に失敗しました');
+ callbacks.onError?.(t('applications.employees.messages.deleteFailed'));
throw error;
}
};
@@ -288,6 +298,7 @@ export const useUnregisteredGroupLogic = (
onError?: (message: string) => void;
}
) => {
+ const { t } = useTranslation('common');
// 未登録グループの操作API
const { registerUnregisteredGroup, deleteUnregisteredGroup } =
useMutateUnregisteredGroup(ORDER_TYPES.EMPLOYEE);
@@ -308,7 +319,9 @@ export const useUnregisteredGroupLogic = (
await mutateUnregisteredGroup(); // データ再取得
} catch (error) {
console.error('Error registering unregistered group:', error);
- callbacks.onError?.('登録に失敗しました');
+ callbacks.onError?.(
+ t('applications.employees.messages.registerUnregisteredFailed')
+ );
throw error;
}
};
@@ -325,7 +338,9 @@ export const useUnregisteredGroupLogic = (
}
} catch (error) {
console.error('Error deleting unregistered group:', error);
- callbacks.onError?.('削除に失敗しました');
+ callbacks.onError?.(
+ t('applications.employees.messages.deleteUnregisteredFailed')
+ );
throw error;
}
};
diff --git a/user/src/components/Applications/Employees/schema.ts b/user/src/components/Applications/Employees/schema.ts
index 4a21b1aa9..74caadc05 100644
--- a/user/src/components/Applications/Employees/schema.ts
+++ b/user/src/components/Applications/Employees/schema.ts
@@ -1,10 +1,15 @@
import { NEED_APPLICATION } from '@/utils/constants';
import { z } from 'zod';
+const VALIDATION_MESSAGES = {
+ NAME: 'applications.employees.validation.name',
+ STUDENT_ID: 'applications.employees.validation.studentId',
+} as const;
+
export const employeeFormItemSchema = z.object({
id: z.number().optional(),
- name: z.string().min(1, '従業員名は必須です'),
- studentId: z.string().regex(/^\d{8}$/, '8桁の学籍番号を入力してください'),
+ name: z.string().min(1, VALIDATION_MESSAGES.NAME),
+ studentId: z.string().regex(/^\d{8}$/, VALIDATION_MESSAGES.STUDENT_ID),
});
export const employeesFormSchema = z.object({
From c921e0469f534c8b5b1616774b747f6344823a89 Mon Sep 17 00:00:00 2001
From: hikahana <22.h.hanada.nutfes@gmail.com>
Date: Thu, 1 Jan 2026 12:54:45 +0900
Subject: [PATCH 16/57] =?UTF-8?q?[feat]=20FoodProduct=E3=82=B3=E3=83=B3?=
=?UTF-8?q?=E3=83=9D=E3=83=BC=E3=83=8D=E3=83=B3=E3=83=88=E3=81=8A=E3=82=88?=
=?UTF-8?q?=E3=81=B3=E9=96=A2=E9=80=A3=E3=83=95=E3=82=A9=E3=83=BC=E3=83=A0?=
=?UTF-8?q?=E3=81=ABi18next=E3=82=92=E4=BD=BF=E7=94=A8=E3=81=97=E3=81=9F?=
=?UTF-8?q?=E7=BF=BB=E8=A8=B3=E6=A9=9F=E8=83=BD=E3=82=92=E8=BF=BD=E5=8A=A0?=
=?UTF-8?q?=E3=81=97=E3=80=81=E8=A1=A8=E7=A4=BA=E3=83=86=E3=82=AD=E3=82=B9?=
=?UTF-8?q?=E3=83=88=E3=82=84=E3=83=90=E3=83=AA=E3=83=87=E3=83=BC=E3=82=B7?=
=?UTF-8?q?=E3=83=A7=E3=83=B3=E3=83=A1=E3=83=83=E3=82=BB=E3=83=BC=E3=82=B8?=
=?UTF-8?q?=E3=82=92=E3=83=AD=E3=83=BC=E3=82=AB=E3=83=A9=E3=82=A4=E3=82=BA?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../Applications/FoodProduct/FoodProduct.tsx | 14 ++--
.../FoodProductForm/FoodProductForm.tsx | 81 +++++++++++++------
.../FoodProduct/FoodProductForm/hooks.ts | 20 ++++-
.../FoodProduct/FoodProductForm/schema.ts | 41 ++++++----
.../Applications/FoodProduct/hooks.ts | 71 ++++++++++------
5 files changed, 156 insertions(+), 71 deletions(-)
diff --git a/user/src/components/Applications/FoodProduct/FoodProduct.tsx b/user/src/components/Applications/FoodProduct/FoodProduct.tsx
index 6c5075b74..4390c5501 100644
--- a/user/src/components/Applications/FoodProduct/FoodProduct.tsx
+++ b/user/src/components/Applications/FoodProduct/FoodProduct.tsx
@@ -1,4 +1,5 @@
import { FC } from 'react';
+import { useTranslation } from 'next-i18next';
import AccordionMenu from '@/components/AccordionMenu/AccordionMenu';
import FoodProductForm from '@/components/Applications/FoodProduct/FoodProductForm/FoodProductForm';
import {
@@ -42,11 +43,13 @@ const Content: FC
= ({
removeFoodProduct,
setFoodProductsData,
}) => {
+ const { t } = useTranslation('common');
+
if (isLoading) {
return (
-
Loading...
+
{t('applications.foodProduct.loading')}
);
}
@@ -54,7 +57,7 @@ const Content: FC = ({
if (hasError) {
return (
- データの取得に失敗しました。
+ {t('applications.foodProduct.errors.fetch')}
);
}
@@ -81,10 +84,10 @@ const Content: FC = ({
- 申請期限が過ぎています
+ {t('applications.foodProduct.deadline.title')}
- 販売品申請の締切期限が過ぎているため、新規申請はできません。
+ {t('applications.foodProduct.deadline.description')}
@@ -126,6 +129,7 @@ const FoodProduct: FC
= ({
isDeadline,
isRegistered,
}) => {
+ const { t } = useTranslation('common');
const {
formItem,
isEditing,
@@ -140,7 +144,7 @@ const FoodProduct: FC = ({
return (
= ({
isViewMode = false,
mutateCheckAllRegisteredGroups,
}) => {
+ const { t } = useTranslation('common');
const {
handleSubmit,
errors,
@@ -66,13 +68,31 @@ const FoodProductForm: FC = ({
addFoodProducts,
setFoodProductsData
);
+ const alcoholRadioOptions = useMemo(
+ () =>
+ alcoholOptions.map((option) => ({
+ id: option.id,
+ name: t(option.labelKey),
+ })),
+ [alcoholOptions, t]
+ );
+ const cookingRadioOptions = useMemo(
+ () =>
+ licenseOptions.map((option) => ({
+ id: option.id,
+ name: t(option.labelKey),
+ })),
+ [licenseOptions, t]
+ );
// ビューモード(登録済みデータをカード表示)
if (isViewMode) {
if (!foodProducts || foodProducts.length === 0) {
return (
-
販売品が登録されていません
+
+ {t('applications.foodProduct.view.empty')}
+
);
@@ -90,21 +110,28 @@ const FoodProductForm: FC = ({
{foodProducts.map((product) => {
const items: FormItem[] = [
- { label: '販売品名', content: product.name ?? '-' },
{
- label: '酒類ですか?',
- content: product.isAlcohol ? 'はい' : 'いいえ',
+ label: t('applications.foodProduct.summary.labels.name'),
+ content: product.name ?? '-',
+ },
+ {
+ label: t('applications.foodProduct.summary.labels.alcohol'),
+ content: product.isAlcohol
+ ? t('applications.foodProduct.radio.alcohol.options.yes')
+ : t('applications.foodProduct.radio.alcohol.options.no'),
},
{
- label: '調理の有無',
- content: product.isCooking ? '有り' : '無し',
+ label: t('applications.foodProduct.summary.labels.cooking'),
+ content: product.isCooking
+ ? t('applications.foodProduct.radio.cooking.options.yes')
+ : t('applications.foodProduct.radio.cooking.options.no'),
},
{
- label: '1日目の販売予定数',
+ label: t('applications.foodProduct.summary.labels.day1'),
content: product.day1Quantity || '0',
},
{
- label: '2日目の販売予定数',
+ label: t('applications.foodProduct.summary.labels.day2'),
content: product.day2Quantity || '0',
},
];
@@ -132,7 +159,7 @@ const FoodProductForm: FC = ({
icon="pencil"
onClick={toEdit}
>
- 修正
+ {t('form.actions.edit')}
@@ -145,7 +172,9 @@ const FoodProductForm: FC = ({
{isFetching || isMutating ? (
-
処理中...
+
+ {t('applications.foodProduct.notes.processing')}
+
) : (
diff --git a/user/src/components/Applications/FoodProduct/FoodProductForm/hooks.ts b/user/src/components/Applications/FoodProduct/FoodProductForm/hooks.ts
index a71b06f73..d891496b9 100644
--- a/user/src/components/Applications/FoodProduct/FoodProductForm/hooks.ts
+++ b/user/src/components/Applications/FoodProduct/FoodProductForm/hooks.ts
@@ -79,13 +79,25 @@ export const useFoodProductFormHooks = (
const products = values.products || [];
const alcoholOptions = [
- { id: FORM_VALUES.YES, name: 'はい' },
- { id: FORM_VALUES.NO, name: 'いいえ' },
+ {
+ id: FORM_VALUES.YES,
+ labelKey: 'applications.foodProduct.radio.alcohol.options.yes',
+ },
+ {
+ id: FORM_VALUES.NO,
+ labelKey: 'applications.foodProduct.radio.alcohol.options.no',
+ },
];
const licenseOptions = [
- { id: FORM_VALUES.YES, name: '有り (例:酒類、加熱調理をするものなど)' },
- { id: FORM_VALUES.NO, name: '無し (例:ソフトドリンク)' },
+ {
+ id: FORM_VALUES.YES,
+ labelKey: 'applications.foodProduct.radio.cooking.options.yes',
+ },
+ {
+ id: FORM_VALUES.NO,
+ labelKey: 'applications.foodProduct.radio.cooking.options.no',
+ },
];
const handleAlcoholChange = (index: number, value: string) => {
diff --git a/user/src/components/Applications/FoodProduct/FoodProductForm/schema.ts b/user/src/components/Applications/FoodProduct/FoodProductForm/schema.ts
index 84cd509f1..7ef2678f7 100644
--- a/user/src/components/Applications/FoodProduct/FoodProductForm/schema.ts
+++ b/user/src/components/Applications/FoodProduct/FoodProductForm/schema.ts
@@ -1,5 +1,18 @@
import { z } from 'zod';
+const VALIDATION_MESSAGES = {
+ NAME: 'applications.foodProduct.validation.name',
+ IS_ALCOHOL: 'applications.foodProduct.validation.isAlcohol',
+ IS_COOKING: 'applications.foodProduct.validation.isCooking',
+ DAY1: 'applications.foodProduct.validation.day1',
+ DAY2: 'applications.foodProduct.validation.day2',
+ NUMBER: 'applications.foodProduct.validation.number',
+ MIN_VALUE: 'applications.foodProduct.validation.minValue',
+ ALCOHOL_REQUIRES_COOKING:
+ 'applications.foodProduct.validation.alcoholRequiresCooking',
+ MIN_PRODUCTS: 'applications.foodProduct.validation.minProducts',
+};
+
// ベースとなる商品スキーマ
const baseProductSchema = z.object({
id: z.string().optional(),
@@ -16,32 +29,32 @@ const productSchema = z
id: z.string().optional(),
name: z
.string({
- required_error: '販売品名を入力してください',
+ required_error: VALIDATION_MESSAGES.NAME,
})
- .min(1, { message: '販売品名を入力してください' }),
+ .min(1, { message: VALIDATION_MESSAGES.NAME }),
isAlcohol: z.boolean({
- required_error: '酒類かどうかを選択してください',
+ required_error: VALIDATION_MESSAGES.IS_ALCOHOL,
}),
isCooking: z.boolean({
- required_error: '調理の有無を選択してください',
+ required_error: VALIDATION_MESSAGES.IS_COOKING,
}),
day1Quantity: z
.string({
- required_error: '1日目の販売予定数を入力してください',
+ required_error: VALIDATION_MESSAGES.DAY1,
})
- .min(1, { message: '1日目の販売予定数を入力してください' })
- .regex(/^\d+$/, { message: '半角数字で入力してください' })
+ .min(1, { message: VALIDATION_MESSAGES.DAY1 })
+ .regex(/^\d+$/, { message: VALIDATION_MESSAGES.NUMBER })
.refine((val) => parseInt(val) > 0, {
- message: '1以上の数値を入力してください',
+ message: VALIDATION_MESSAGES.MIN_VALUE,
}),
day2Quantity: z
.string({
- required_error: '2日目の販売予定数を入力してください',
+ required_error: VALIDATION_MESSAGES.DAY2,
})
- .min(1, { message: '2日目の販売予定数を入力してください' })
- .regex(/^\d+$/, { message: '半角数字で入力してください' })
+ .min(1, { message: VALIDATION_MESSAGES.DAY2 })
+ .regex(/^\d+$/, { message: VALIDATION_MESSAGES.NUMBER })
.refine((val) => parseInt(val) > 0, {
- message: '1以上の数値を入力してください',
+ message: VALIDATION_MESSAGES.MIN_VALUE,
}),
})
.refine(
@@ -49,7 +62,7 @@ const productSchema = z
return !(data.isAlcohol && !data.isCooking);
},
{
- message: '酒類を販売する場合は調理の有無を「有り」にしてください',
+ message: VALIDATION_MESSAGES.ALCOHOL_REQUIRES_COOKING,
path: ['isCooking'],
}
);
@@ -66,7 +79,7 @@ export const productInputSchema = baseProductSchema;
export const foodProductSchema = z.object({
products: z
.array(productSchema)
- .min(1, { message: '少なくとも1つの販売品を登録してください' }),
+ .min(1, { message: VALIDATION_MESSAGES.MIN_PRODUCTS }),
});
// 型の自動生成
diff --git a/user/src/components/Applications/FoodProduct/hooks.ts b/user/src/components/Applications/FoodProduct/hooks.ts
index ed967bc5b..793361bf8 100644
--- a/user/src/components/Applications/FoodProduct/hooks.ts
+++ b/user/src/components/Applications/FoodProduct/hooks.ts
@@ -4,6 +4,7 @@ import {
useGetFoodProducts,
useUpsertFoodProducts,
} from '@/api/foodProductApi';
+import { useTranslation } from 'next-i18next';
import { toast } from 'react-toastify';
import {
ProductInput,
@@ -17,6 +18,7 @@ const API_ENDPOINTS = {
} as const;
export const useFoodProductHooks = (groupId: number) => {
+ const { t } = useTranslation('common');
const [isEditing, setIsEditing] = useState(true);
const [hasInitialized, setHasInitialized] = useState(false);
@@ -51,10 +53,12 @@ export const useFoodProductHooks = (groupId: number) => {
const formItem: FormItem[] = [
{
- label: '販売品一覧',
+ label: t('applications.foodProduct.view.summaryLabel'),
content: foodProducts?.length
- ? `${foodProducts.length}品目登録済み`
- : '未登録',
+ ? t('applications.foodProduct.view.registered', {
+ count: foodProducts.length,
+ })
+ : t('applications.foodProduct.view.none'),
},
];
@@ -116,23 +120,28 @@ export const useFoodProductHooks = (groupId: number) => {
// 成功時のみビューモードに戻す
setIsEditing(false);
- toast.success('販売品を更新しました', {
+ toast.success(t('applications.foodProduct.messages.updateSuccess'), {
position: 'top-right',
autoClose: 3000,
});
} catch (error) {
console.error('販売品更新エラー:', error);
- let errorMessage = '販売品の更新に失敗しました';
+ let errorMessage = t('applications.foodProduct.messages.updateFailed');
if (error instanceof Error) {
if (
error.message.includes('認証が必要') ||
error.message.includes('User is not authenticated')
) {
- errorMessage = '認証が必要です。ログインしてください。';
+ errorMessage = t('applications.foodProduct.messages.authRequired');
} else {
- errorMessage = `販売品の更新に失敗しました: ${error.message}`;
+ errorMessage = t(
+ 'applications.foodProduct.messages.updateFailedDetail',
+ {
+ message: error.message,
+ }
+ );
}
}
@@ -172,23 +181,26 @@ export const useFoodProductHooks = (groupId: number) => {
// 成功時のみビューモードに戻す
setIsEditing(false);
- toast.success('販売品申請を送信しました', {
+ toast.success(t('applications.foodProduct.messages.createSuccess'), {
position: 'top-right',
autoClose: 3000,
});
} catch (error) {
console.error('販売品登録エラー:', error);
- let errorMessage = '販売品の登録に失敗しました';
+ let errorMessage = t('applications.foodProduct.messages.createFailed');
if (error instanceof Error) {
if (
error.message.includes('認証が必要') ||
error.message.includes('User is not authenticated')
) {
- errorMessage = '認証が必要です。ログインしてください。';
+ errorMessage = t('applications.foodProduct.messages.authRequired');
} else {
- errorMessage = `販売品の登録に失敗しました: ${error.message}`;
+ errorMessage = t(
+ 'applications.foodProduct.messages.createFailedDetail',
+ { message: error.message }
+ );
}
}
@@ -223,7 +235,10 @@ export const useFoodProductHooks = (groupId: number) => {
'success' in result &&
!result.success
) {
- throw new Error(result.error?.message || '削除に失敗しました');
+ throw new Error(
+ result.error?.message ||
+ t('applications.foodProduct.messages.deleteFailed')
+ );
}
// データを強制的に再取得
@@ -234,10 +249,15 @@ export const useFoodProductHooks = (groupId: number) => {
await mutateFoodProducts();
}, 500);
- toast.success(`「${productToRemove.name}」を削除しました`, {
- position: 'top-right',
- autoClose: 3000,
- });
+ toast.success(
+ t('applications.foodProduct.messages.deleteSuccess', {
+ name: productToRemove.name,
+ }),
+ {
+ position: 'top-right',
+ autoClose: 3000,
+ }
+ );
} catch (error) {
console.error('販売品削除エラー:', error);
@@ -247,23 +267,28 @@ export const useFoodProductHooks = (groupId: number) => {
error.message.includes('User is not authenticated') ||
error.message.includes('認証が必要')
) {
- toast.error('認証が必要です。ログインしてください。', {
+ toast.error(t('applications.foodProduct.messages.authRequired'), {
position: 'top-right',
autoClose: 5000,
});
} else if (error.message.includes('404')) {
- toast.error('削除対象の商品が見つかりませんでした。', {
+ toast.error(t('applications.foodProduct.messages.deleteNotFound'), {
position: 'top-right',
autoClose: 5000,
});
} else {
- toast.error(`販売品の削除に失敗しました: ${error.message}`, {
- position: 'top-right',
- autoClose: 5000,
- });
+ toast.error(
+ t('applications.foodProduct.messages.deleteFailedDetail', {
+ message: error.message,
+ }),
+ {
+ position: 'top-right',
+ autoClose: 5000,
+ }
+ );
}
} else {
- toast.error('販売品の削除に失敗しました。', {
+ toast.error(t('applications.foodProduct.messages.deleteFailed'), {
position: 'top-right',
autoClose: 5000,
});
From ce4f3460a55c3949178fe7f3ebd5c917b8e4cb16 Mon Sep 17 00:00:00 2001
From: hikahana <22.h.hanada.nutfes@gmail.com>
Date: Thu, 1 Jan 2026 12:54:55 +0900
Subject: [PATCH 17/57] =?UTF-8?q?[feat]=20Group=E3=82=B3=E3=83=B3=E3=83=9D?=
=?UTF-8?q?=E3=83=BC=E3=83=8D=E3=83=B3=E3=83=88=E3=81=8A=E3=82=88=E3=81=B3?=
=?UTF-8?q?=E9=96=A2=E9=80=A3=E3=83=95=E3=82=A9=E3=83=BC=E3=83=A0=E3=81=AB?=
=?UTF-8?q?i18next=E3=82=92=E4=BD=BF=E7=94=A8=E3=81=97=E3=81=9F=E7=BF=BB?=
=?UTF-8?q?=E8=A8=B3=E6=A9=9F=E8=83=BD=E3=82=92=E8=BF=BD=E5=8A=A0=E3=81=97?=
=?UTF-8?q?=E3=80=81=E8=A1=A8=E7=A4=BA=E3=83=86=E3=82=AD=E3=82=B9=E3=83=88?=
=?UTF-8?q?=E3=82=84=E3=83=90=E3=83=AA=E3=83=87=E3=83=BC=E3=82=B7=E3=83=A7?=
=?UTF-8?q?=E3=83=B3=E3=83=A1=E3=83=83=E3=82=BB=E3=83=BC=E3=82=B8=E3=82=92?=
=?UTF-8?q?=E3=83=AD=E3=83=BC=E3=82=AB=E3=83=A9=E3=82=A4=E3=82=BA?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../components/Applications/Group/Group.tsx | 9 ++--
.../Group/GroupForm/GroupForm.tsx | 43 +++++++++++--------
.../Applications/Group/GroupForm/hooks.ts | 10 +++--
.../Applications/Group/GroupForm/schema.ts | 12 +++---
.../components/Applications/Group/hooks.ts | 22 ++++++----
5 files changed, 56 insertions(+), 40 deletions(-)
diff --git a/user/src/components/Applications/Group/Group.tsx b/user/src/components/Applications/Group/Group.tsx
index d00b85784..83feea859 100644
--- a/user/src/components/Applications/Group/Group.tsx
+++ b/user/src/components/Applications/Group/Group.tsx
@@ -1,5 +1,6 @@
import { FC } from 'react';
import { GroupCategoryResponse, GroupResponse } from '@/api/groupApi';
+import { useTranslation } from 'next-i18next';
import FormList from '@/components/FormList';
import { FormItem } from '@/components/FormList/type';
import AccordionMenu from '../../AccordionMenu';
@@ -45,15 +46,16 @@ const Content: FC
= ({
mutateCheckAllRegisteredGroups,
mutateGroupByUserId,
}) => {
+ const { t } = useTranslation('common');
// データ取得中など,ロード中に表示する画面
if (isLoading) {
- return Loading...
;
+ return {t('applications.group.loading')}
;
}
// データ取得に失敗した場合に表示する画面
if (hasError) {
return (
- データの取得に失敗しました。
+ {t('applications.group.errors.fetch')}
);
}
@@ -88,6 +90,7 @@ const Group: FC = ({
mutateCheckAllRegisteredGroups,
mutateGroupByUserId,
}) => {
+ const { t } = useTranslation('common');
const {
formItem,
isEditing,
@@ -100,7 +103,7 @@ const Group: FC = ({
} = useGroupHooks(groupId);
return (
= ({
mutateCheckAllRegisteredGroups,
mutateGroupByUserId,
}) => {
+ const { t } = useTranslation('common');
const {
handleSubmit,
errors,
@@ -49,7 +51,7 @@ const GroupForm: FC = ({
);
if (createError || updateError) {
- toast.error('送信に失敗しました。時間を置いて再度お試しください');
+ toast.error(t('form.messages.registerFailed'));
}
return (
@@ -60,51 +62,54 @@ const GroupForm: FC = ({
>
setValue('name', value)}
- note={'例:技大祭実行委員会'}
+ note={t('applications.group.notes.name')}
required={true}
error={errors.name?.message}
>
setValue('projectName', value)}
- note={'例:ギダイジャー'}
+ note={t('applications.group.notes.projectName')}
required={true}
error={errors.projectName?.message}
>
setValue('isInternational', value === '1')}
required={true}
- note={'注意書き'}
+ note={t('applications.group.notes.international')}
error={errors.isInternational?.message}
options={[
- { id: 0, name: 'いいえ、国際団体(留学生団体)ではありません。' },
- { id: 1, name: 'はい、国際団体(留学生団体)です。' },
+ { id: 0, name: t('applications.group.options.international.no') },
+ {
+ id: 1,
+ name: t('applications.group.options.international.yes'),
+ },
]}
>
setValue('isExternal', value === '1')}
required={true}
- note={'注意書き'}
+ note={t('applications.group.notes.external')}
error={errors.isExternal?.message}
options={[
- { id: 0, name: 'いいえ、学内の団体です。' },
- { id: 1, name: 'はい、学外の団体です。' },
+ { id: 0, name: t('applications.group.options.external.no') },
+ { id: 1, name: t('applications.group.options.external.yes') },
]}
>
setValue('groupCategoryId', parseInt(value))}
required={true}
- note={'注意書き'}
+ note={t('applications.group.notes.groupCategory')}
error={errors.groupCategoryId?.message}
options={
groupCategories?.map((category) => ({
@@ -114,11 +119,11 @@ const GroupForm: FC = ({
}
>
@@ -132,7 +137,7 @@ const GroupForm: FC = ({
type="button"
onClick={toEdit}
>
- キャンセル
+ {t('form.actions.cancel')}
)}
@@ -142,7 +147,7 @@ const GroupForm: FC