export type SystemWordingLeaf = string; export type SystemWordingTree = { [key: string]: SystemWordingLeaf | SystemWordingTree }; export type SystemWordingMessages = Record; export const defaultLocale = 'en'; export const systemWordingMessages = { en: { common: { add: 'Add', admin: 'Admin', all: 'All', back: 'Back', backToList: 'Back to list', cancel: 'Cancel', close: 'Close', create: 'Create', delete: 'Delete', edit: 'Edit', details: 'Details', filters: 'Filters', loading: 'Loading', name: 'Name', new: 'New', none: 'None', save: 'Save', saving: 'Saving', search: 'Search', select: 'Select', selected: 'Selected', system: 'System', noRecords: 'No records', fieldForLanguage: '{field} ({language})', searchOrSelect: 'Search or select', noMatches: 'No matches', createNamed: 'Add "{name}"', creating: 'Adding', inDev: 'In-Dev', removeNamed: 'Remove {name}', quantity: 'Quantity', eventItem: 'Event item', required: 'Required' }, nav: { pokemon: 'Pokemon', habitats: 'Habitats', items: 'Items', recipes: 'Recipes', automation: 'Automation', dish: 'Dish', events: 'Events', actions: 'Actions', dreamIsland: 'Dream Island', clothes: 'Clothes', checklist: 'CheckList', life: 'Life', admin: 'Admin', main: 'Main navigation', openMenu: 'Open navigation', closeMenu: 'Close navigation', language: 'Language', profile: 'Profile', login: 'Log in', logout: 'Log out', register: 'Register' }, seo: { siteDescription: 'Browse Pokopia Wiki for Pokemon, habitats, items, recipes, daily tasks, and Life community posts for Pokemon Pokopia.', pokemonDetailDescription: 'Read {name} details in Pokopia Wiki, including habitat, types, specialities, favourites, stats, related items, discussions, and edit history.', itemDetailDescription: 'Browse {name} item details in Pokopia Wiki, including category, usage, acquisition methods, customization, related recipes, habitats, and Pokemon drops.', habitatDetailDescription: 'View {name} habitat details in Pokopia Wiki, including recipes, possible Pokemon, maps, time, weather, discussions, and edit history.', recipeDetailDescription: 'View the {name} recipe in Pokopia Wiki, including the result item, acquisition methods, materials, discussions, and edit history.' }, auth: { accountAccess: 'Trainer Pass', email: 'Email', password: 'Password', currentPassword: 'Current password', newPassword: 'New password', confirmPassword: 'Confirm password', displayName: 'Display name', referralCode: 'Referral code', referralCodePlaceholder: 'Optional code', referralCodeHint: 'Use an invite code from another trainer.', loginTitle: 'Log in', loginSubtitle: 'Use a verified email to enter Pokopia Wiki.', loggingIn: 'Logging in', loginFailed: 'Login failed', rememberMe: 'Remember me', forgotPassword: 'Forgot password?', noAccount: 'No account yet?', registerTitle: 'Register', registerSubtitle: 'Verify your email after creating an account.', registerFailed: 'Registration failed', sending: 'Sending', sendVerification: 'Send verification email', hasAccount: 'Already have an account?', requestResetTitle: 'Reset password', requestResetSubtitle: 'Send a password reset link to your account email.', sendResetLink: 'Send reset link', requestResetFailed: 'Password reset request failed', resetTitle: 'Choose a new password', resetSubtitle: 'Use the reset link from your email to update your password.', resetPassword: 'Reset password', resetting: 'Resetting', resetFailed: 'Password reset failed', passwordMismatch: 'Passwords do not match', invalidPasswordReset: 'The password reset link is invalid or expired.', verifyTitle: 'Email verification', verifySubtitle: 'You can log in after verification is complete.', verifyingEmail: 'Verifying email', invalidVerification: 'The verification link is invalid or expired.', verifyFailed: 'Email verification failed', goLogin: 'Go to login' }, errors: { requestFailed: 'Request failed ({status})', operationFailed: 'Operation failed', loadFailed: 'Load failed', addFailed: 'Add failed', saveFailed: 'Save failed', completeEmailVerification: 'Please complete email verification first.', permissionDenied: 'You do not have permission to use this action.' }, pages: { profile: { title: 'User profile', subtitle: 'Manage your account details, referral, and password.', loading: 'Loading profile', accountSummary: 'Account summary', profileDetails: 'Profile details', displayNameHint: 'Display name is shown on edits, discussions, and Life posts.', displayNameRequired: 'Display name is required.', emailVerified: 'Email verified', emailUnverified: 'Email unverified', saved: 'Profile saved', saveFailed: 'Profile save failed', referralTitle: 'Referral', referralCode: 'Referral code', referralUrl: 'Invite link', referralHint: 'Share this link with new editors. Invites count after email verification.', verifiedReferralCount: 'Verified invites', copyReferralLink: 'Copy link', referralCopied: 'Referral link copied', referralCopyFailed: 'Referral link copy failed', referralLoadFailed: 'Referral details failed to load', publicSubtitle: 'Review this member\'s Life posts, reactions, comments, and Wiki contributions.', publicKicker: 'Member Profile', tabsLabel: 'Profile sections', tabFeeds: 'Feeds', tabContributions: 'Contributions', tabReactions: 'Reactions', tabComments: 'Comments', tabAccount: 'Account', contributionFiltersLabel: 'Contribution categories', contributionConfig: 'Config', contributionsFilterEmpty: 'No contributions in this category', reactionFiltersLabel: 'Reaction categories', reactionsFilterEmpty: 'No reactions in this category', commentFiltersLabel: 'Comment categories', commentsFilterEmpty: 'No comments in this category', lifeCommentCategory: 'Life', discussionCommentCategory: 'Wiki', passwordTitle: 'Change password', passwordHint: 'Use at least 8 characters.', passwordSaved: 'Password updated', passwordSaveFailed: 'Password update failed', savePassword: 'Save password', joinedAt: 'Joined {date}', lifePosts: 'Life posts', lifeComments: 'Life comments', lifeReactions: 'Reactions', discussionComments: 'Wiki comments', commentsMade: 'Comments', wikiEdits: 'Wiki edits', wikiCreates: 'Creates', wikiUpdates: 'Updates', wikiDeletes: 'Deletes', imageUploads: 'Images', wikiContributionStats: 'Wiki contribution stats', communityStats: 'Community stats', contributionBreakdown: 'Contribution breakdown', total: 'Total', otherContributions: 'Other', feedsEmpty: 'No Life posts yet', contributionsEmpty: 'No Wiki contributions yet', reactionsEmpty: 'No reactions yet', commentsEmpty: 'No comments yet', loadMore: 'Load more', lifeComment: 'Life comment', discussionComment: 'Wiki discussion', lifePostTarget: 'Life post', lifePostBy: 'Life post by {name}' }, pokemon: { title: 'Pokemon', subtitle: 'Search Pokemon and filter by specialities, ideal habitat, and favourites.', detailKicker: 'Pokédex Detail', editKicker: 'Pokédex Edit', editSubtitle: 'Maintain Pokemon profile, details, types, stats, specialities, and favourites.', editSections: 'Pokemon edit sections', editTabBasic: 'Basic', editTabAdvance: 'Advance', newTitle: 'New Pokemon', editTitle: 'Edit #{id} {name}', id: 'Pokemon ID', fetchData: 'Fetch data', fetchingData: 'Fetching', fetchIdentifier: 'Data identifier', fetchIdentifierPlaceholder: 'bulbasaur or 1', fetchIdentifierRequired: 'Enter a Pokemon identifier', fetchFailed: 'Pokemon data fetch failed', fetchIdMismatch: 'Fetched Pokemon ID #{id} does not match this editor.', fetchResults: 'Pokemon data results', fetchSearching: 'Searching data', fetchNoMatches: 'No matching Pokemon data', fetchSearchFailed: 'Pokemon data search failed', image: 'Image', fetchImages: 'Fetch images', fetchingImages: 'Fetching', imageFetchFailed: 'Pokemon image fetch failed', imageNoMatches: 'No available Pokemon images', loadingImages: 'Loading Pokemon images', selectedImage: 'Selected Pokemon image', imageOptions: 'Pokemon image options', clearImage: 'Clear image', imageEmpty: 'No Pokemon image selected', imageAlt: '{name} {variant} image', eventItem: 'Event item', loadingList: 'Loading Pokemon list', loadingDetail: 'Loading Pokemon detail', loadingEdit: 'Loading Pokemon editor', environmentPrefix: 'Ideal Habitat: {name}', details: 'Details', genus: 'Genus', height: 'Height', heightInput: 'Height (in)', heightImperial: 'ft / in', heightMetric: 'm', feet: 'ft', inches: 'in', meters: 'm', weight: 'Weight', weightInput: 'Weight (lb)', pounds: 'lb', kilograms: 'kg', measurements: 'Height & Weight', types: 'Types', typeOne: 'Type 1', typeTwo: 'Type 2', typesAndStats: 'Types & Base stats', statsTitle: 'Base stats', stats: { hp: 'HP', attack: 'Attack', defense: 'Defense', specialAttack: 'Special Attack', specialDefense: 'Special Defense', speed: 'Speed' }, environment: 'Ideal Habitat', skills: 'Specialities', skillMatchMode: 'Speciality match mode', any: 'Any', all: 'All', favoriteThings: 'Favourites', favoriteThingMatchMode: 'Favourites match mode', skillDrops: 'Speciality drops', skillDrop: '{name} drop', dropItem: 'Drop item', searchPokemon: 'Search Pokemon', relatedPokemon: 'Related Pokemon', relatedHabitat: 'Related Pokemon habitat', relatedItems: 'Related items', relatedItemCategory: 'Related item category', habitats: 'Habitats', namePlaceholder: 'Name', searchTypes: 'Search types', searchEnvironment: 'Search ideal habitats', searchSkills: 'Search specialities', searchFavoriteThings: 'Search favourites', searchItems: 'Search items' }, habitats: { title: 'Habitats', subtitle: 'View recipes and Pokemon that may appear.', detailKicker: 'Habitat Detail', detailSubtitle: 'Habitat detail', editSubtitle: 'Maintain habitat recipes and possible Pokemon appearances.', newTitle: 'New habitat', editTitle: 'Edit {name}', fallbackName: 'Habitat', loadingList: 'Loading habitat list', loadingDetail: 'Loading habitat detail', loadingEdit: 'Loading habitat editor', eventItem: 'Event item', recipe: 'Recipe', recipeList: 'Recipe list', possiblePokemon: 'Possible Pokemon', addItem: 'Add item', addPokemon: 'Add Pokemon', maps: 'Maps', searchMaps: 'Search maps' }, items: { title: 'Items', subtitle: 'Browse items by category, usage, and tags.', detailKicker: 'Item Detail', detailSubtitle: 'Item detail', editKicker: 'Item Edit', editSubtitle: 'Maintain item category, usage, acquisition methods, customization, and tags.', newTitle: 'New item', editTitle: 'Edit {name}', fallbackName: 'Item', loadingList: 'Loading item list', loadingDetail: 'Loading item detail', loadingEdit: 'Loading item editor', category: 'Category', usage: 'Usage', tags: 'Tags', acquisitionMethods: 'Acquisition methods', customization: 'Customization', dyeable: 'Dyeable', dualDyeable: 'Dual dyeable', patternEditable: 'Pattern editable', noRecipe: 'No recipe', eventItem: 'Event item', recipeInfo: 'Recipe info', relatedRecipes: 'Related recipes', relatedHabitats: 'Related habitats', pokemonDrops: 'Pokemon drops', createRecipe: 'Create recipe', searchCategory: 'Search categories', searchUsage: 'Search usages', searchMethods: 'Search acquisition methods', searchTags: 'Search tags' }, recipes: { title: 'Recipes', subtitle: 'Browse recipes by category, usage, and tags.', detailKicker: 'Recipe Detail', detailSubtitle: 'Recipe detail', editKicker: 'Recipe Edit', editSubtitle: 'Maintain result item, acquisition methods, and materials.', newTitle: 'New recipe', editTitle: 'Edit {name}', fallbackName: 'Recipe', loadingList: 'Loading recipe list', loadingDetail: 'Loading recipe detail', loadingEdit: 'Loading recipe editor', item: 'Item', materials: 'Materials', addMaterial: 'Add material' }, comingSoon: { status: 'In development', heading: 'This wiki section is being prepared.', previewLabel: 'Section preview', sections: { automation: { kicker: 'Automation', title: 'Automation', subtitle: 'Factory and automation base guides will be shared here.', body: 'Automation pages will help players compare production setups, material outputs, required Pokemon, production order, and shared favourites.', preview: { one: 'Factory guides will focus on the base layout and production goal.', two: 'Material output, Pokemon needs, and production order will stay easy to scan.', three: 'Shared favourite items can help players plan compatible teams and workflows.' } }, dish: { kicker: 'Dish', title: 'Dish', subtitle: 'A future home for cooked dishes and food discoveries.', body: 'Dish pages are being shaped for clear browsing, source notes, and useful ingredient links.', preview: { one: 'Dish records will focus on names, effects, and discovery context.', two: 'Ingredient relationships will connect back to items and recipes where useful.', three: 'The page will stay browse-first so community edits can grow naturally.' } }, events: { kicker: 'Events', title: 'Events', subtitle: 'Seasonal and limited-time game activity records are coming later.', body: 'Events will collect timing, rewards, and participation details once the section is ready.', preview: { one: 'Event cards will make dates and active windows easy to scan.', two: 'Rewards and related items will sit close to the event summary.', three: 'Archived activities will remain readable after they end.' } }, actions: { kicker: 'Actions', title: 'Actions', subtitle: 'Game shortcut actions such as waving and dancing will be documented here.', body: 'Actions are being prepared as a quick reference for expressive in-game gestures and shortcuts.', preview: { one: 'Each action will describe the gesture or shortcut in player-facing language.', two: 'Common examples include waving, dancing, and other social actions.', three: 'Related unlock or usage details can be linked when the data model is ready.' } }, dreamIsland: { kicker: 'Dream Island', title: 'Dream Island', subtitle: 'Dream Island information is being organized for future browsing.', body: 'This area will present island details with a calm, destination-style layout when content is ready.', preview: { one: 'Island notes will prioritize location, availability, and notable discoveries.', two: 'Related Pokemon, items, or activities can be connected from the page.', three: 'The layout will support browsing without adding another management flow yet.' } }, clothes: { kicker: 'Clothes', title: 'Clothes', subtitle: 'Outfit and clothing references are being prepared.', body: 'Clothes pages will make it easy to compare appearance, acquisition, and customization details.', preview: { one: 'Clothing entries will focus on display names and visual categories.', two: 'Acquisition and customization details can be connected when available.', three: 'The page will keep item-like details readable without mixing them into the item list.' } } } }, checklist: { title: 'Daily checklist', subtitle: 'See what can be completed each day.', sectionTitle: 'Daily tasks', empty: 'No daily checklist', loading: 'Loading daily checklist', task: 'Task', newTask: 'New task', editTask: 'Edit task' }, life: { title: 'Life', subtitle: 'Share favourite thoughts, tips, and community finds.', kicker: 'Community Feed', composerTitle: 'Share something', composerPrompt: 'What would you like to share?', bodyLabel: 'Post', bodyPlaceholder: 'Share a thought, tip, or discovery...', newPost: 'New Post', tags: 'Tags', languages: 'Languages', allLanguages: 'All languages', allTags: 'All', tagPlaceholder: 'Select tags', searchTags: 'Search tags', search: 'Search Life', searchPlaceholder: 'Search post content...', clearSearch: 'Clear search', searchEmpty: 'No posts match your search', searchEmptyHint: 'Try another keyword or clear the search.', comments: 'Comments', commentsCount: '{count} comments', comment: 'Comment', hideComments: 'Hide comments', react: 'Like', reactions: 'Reactions', reactionsCount: '{count} reactions', reactionCountLabel: '{reaction}: {count}', reactionLike: 'Like', reactionHelpful: 'Helpful', reactionFun: 'Fun', reactionThanks: 'Thanks', chooseReaction: 'Choose reaction', reactionMenu: 'Reaction menu', removeReaction: 'Remove reaction', reactionFailed: 'Reaction failed', commentPlaceholder: 'Write a comment...', commentReplyPlaceholder: 'Write a reply...', postComment: 'Post comment', postingComment: 'Posting comment', reply: 'Reply', postReply: 'Post reply', postingReply: 'Posting reply', cancelReply: 'Cancel reply', noComments: 'No comments yet', loadingComments: 'Loading comments', loadMoreComments: 'Load more comments', deleteComment: 'Delete comment', deleteCommentConfirm: 'Delete this comment?', commentDeleted: 'Comment deleted', commentRequired: 'Please enter a comment.', commentFailed: 'Comment failed', replyFailed: 'Reply failed', deleteCommentFailed: 'Delete comment failed', publish: 'Post', publishing: 'Posting', update: 'Update', updating: 'Updating', cancelEdit: 'Cancel edit', empty: 'No posts yet', emptyHint: 'Verified members can start the first Life post.', loading: 'Loading Life feed', retryFeed: 'Retry loading', loginPrompt: 'Log in with a verified email to post.', verifyPrompt: 'Complete email verification to post.', editPost: 'Edit post', deletePost: 'Delete post', saveEdit: 'Save edit', postFailed: 'Post failed', saveFailed: 'Save failed', deleteFailed: 'Delete failed', bodyRequired: 'Please enter a post.', tagRequired: 'Please select at least one tag.', byUnknown: 'Community member', edited: 'Edited', deleteConfirm: 'Delete this post?', moderationUnreviewed: 'Not reviewed', moderationReviewing: 'Reviewing', moderationApproved: 'Approved', moderationRejected: 'Rejected', moderationFailed: 'Review failed', moderationRetry: 'Retry review', moderationRetrying: 'Retrying', moderationRetryFailed: 'Review retry failed', charactersLeft: '{count} characters left' }, admin: { title: 'Admin', subtitle: 'Manage Wiki content, configuration, localization, and access.', modules: 'Admin modules', contentGroup: 'Content', configurationGroup: 'Configuration', localizationGroup: 'Localization', accessGroup: 'Access', loading: 'Loading admin list', users: 'Users', roles: 'Roles', permissions: 'Permissions', config: 'System config', configType: 'System config type', checklist: 'CheckList', pokemonList: 'Pokemon list', itemList: 'Item list', recipeList: 'Recipe list', habitatList: 'Habitat list', languages: 'Languages', newConfig: 'New {name}', editConfig: 'Edit {name}', hasItemDrop: 'Has item drop', dragSort: 'Drag to reorder: {name}', dragSortTitle: 'Drag to reorder', languageCode: 'Code', languageName: 'Language name', enabled: 'Enabled', defaultLanguage: 'Default language', sortOrder: 'Sort order', newLanguage: 'New language', editLanguage: 'Edit language', wordings: 'System wordings', aiModeration: 'AI moderation', aiModerationEnabled: 'Enabled', aiModerationFormat: 'API format', aiModerationFormatGemini: 'Gemini generateContent', aiModerationFormatOpenAi: 'OpenAI-compatible chat completions', aiModerationAuthMode: 'Auth mode', aiModerationAuthQueryKey: 'Query key', aiModerationAuthBearer: 'Bearer token', aiModerationEndpoint: 'End Point', aiModerationModel: 'Model', aiModerationRpm: 'Requests per minute', aiModerationApiKey: 'API Key', aiModerationApiKeyConfigured: 'API Key configured', aiModerationApiKeyMissing: 'API Key missing', aiModerationClearApiKey: 'Clear saved API Key', aiModerationSettings: 'AI moderation settings', wordingLocale: 'Locale', wordingModule: 'Module', wordingSurface: 'Surface', wordingMissingOnly: 'Missing only', wordingKey: 'Key', wordingValue: 'Wording', defaultValue: 'Default wording', placeholders: 'Placeholders', missingTranslation: 'Missing translation', allModules: 'All modules', allSurfaces: 'All surfaces', surfaceFrontend: 'Frontend', surfaceBackend: 'Backend', surfaceEmail: 'Email', editWording: 'Edit wording', userRoles: 'User roles', noRoles: 'No roles', newRole: 'New role', editRole: 'Edit role', roleKey: 'Role key', roleName: 'Role name', description: 'Description', level: 'Level', disabled: 'Disabled', systemRole: 'System role', roleLevel: 'Level {level}', permissionCount: '{count} permissions', rolePermissions: 'Role permissions', newPermission: 'New permission', editPermission: 'Edit permission', permissionKey: 'Permission key', permissionName: 'Permission name', category: 'Category', systemPermission: 'System permission' } }, config: { pokemonTypes: 'Pokemon Types', skills: 'Specialities', environments: 'Ideal Habitats', favoriteThings: 'Favourites / tags', itemCategories: 'Item categories', itemUsages: 'Item usages', acquisitionMethods: 'Acquisition methods', maps: 'Maps', lifeTags: 'Life tags' }, appearance: { time: 'Time', weather: 'Weather', rarity: 'Rarity', map: 'Map', maps: 'Maps', morning: 'Morning', noon: 'Noon', evening: 'Evening', night: 'Night', sunny: 'Sunny', cloudy: 'Cloudy', rainy: 'Rainy', stars: '{count} stars' }, history: { title: 'Contribution records', createdBy: 'Created by', lastEdited: 'Last edited', editHistory: 'Edit history', before: 'Before', after: 'After', author: 'Author', time: 'Time', action: 'Action', create: 'Create', update: 'Edit', delete: 'Delete', empty: 'No edit history' }, media: { image: 'Image', imageAlt: '{name} image', uploadImage: 'Upload image', uploading: 'Uploading', uploadedImage: 'Uploaded image', selectedImage: 'Selected image', imageHistory: 'Image history', imageOptions: 'Image options', clearImage: 'Clear image', imageEmpty: 'No image selected', imageHistoryEmpty: 'No uploaded images', uploadFailed: 'Image upload failed', selectImage: 'Select image' }, discussion: { title: 'Discussion', count: '{count} comments', comment: 'Comment', commentPlaceholder: 'Write a comment...', replyPlaceholder: 'Write a reply...', postComment: 'Post comment', postingComment: 'Posting comment', reply: 'Reply', postReply: 'Post reply', postingReply: 'Posting reply', cancelReply: 'Cancel reply', deleteComment: 'Delete comment', deleteConfirm: 'Delete this comment?', deletedComment: 'Comment deleted', commentRequired: 'Please enter a comment.', commentFailed: 'Comment failed', replyFailed: 'Reply failed', deleteFailed: 'Delete failed', languages: 'Languages', allLanguages: 'All languages', moderationUnreviewed: 'Not reviewed', moderationReviewing: 'Reviewing', moderationApproved: 'Approved', moderationRejected: 'Rejected', moderationFailed: 'Review failed', moderationRetry: 'Retry review', moderationRetrying: 'Retrying', moderationRetryFailed: 'Review retry failed', loading: 'Loading discussion', loadMore: 'Load more comments', empty: 'No discussion yet', emptyHint: 'Start a new discussion now.', loginPrompt: 'Log in with a verified email to comment.', verifyPrompt: 'Complete email verification to comment.', byUnknown: 'Community member', charactersLeft: '{count} characters left' }, server: { errors: { foreignKey: 'Referenced data does not exist or the record is currently in use', duplicate: 'A record with the same name or ID already exists', invalidField: 'Field value is invalid', serverError: 'Server error', loginRequired: 'Please log in first', verifyEmailFirst: 'Please complete email verification first', permissionDenied: 'Permission denied', notFound: 'Not found', rateLimited: 'Too many requests. Please try again later.' }, auth: { emailRequired: 'Email is required', invalidEmail: 'Email format is invalid', displayNameRequired: 'Display name is required', displayNameLength: 'Display name must be 1 to 40 characters', passwordLength: 'Password must be at least 8 characters', invalidToken: 'The verification link is invalid or expired', emailAlreadyRegistered: 'This email is already registered', checkVerificationEmail: 'Please check your verification email', emailVerified: 'Email verified', checkPasswordResetEmail: 'If an account uses this email, a password reset link will be sent.', passwordResetComplete: 'Password updated. You can log in with the new password.', passwordChanged: 'Password updated.', invalidCredentials: 'Email or password is incorrect', verifyEmailFirst: 'Please complete email verification first', invalidResetToken: 'The password reset link is invalid or expired', currentPasswordInvalid: 'Current password is incorrect', invalidReferralCode: 'Referral code is invalid' }, validation: { nameRequired: 'Name is required', recordMissing: 'Record does not exist', languageCodeInvalid: 'Language code is invalid', languageNameRequired: 'Language name is required', defaultLanguageMustBeEnglish: 'Default language must be English', defaultLanguageMustBeEnabled: 'Default language must be enabled', languageNotFound: 'Language not found', defaultLanguageRequired: 'A default language is required', defaultLanguageCannotBeDeleted: 'Default language cannot be deleted', selectLanguage: 'Please select a language', languageDoesNotExist: 'Language does not exist', pokemonIdentifierRequired: 'Pokemon identifier is required', pokemonTypeDataUnavailable: 'Pokemon type data is unavailable', pokemonDataNotFound: 'Pokemon data was not found', pokemonImagePathInvalid: 'Pokemon image path is invalid', imagePathInvalid: 'Image path is invalid', imageUploadRequired: 'Please select an image', imageUploadTypeInvalid: 'Image type is not supported', imageUploadContentInvalid: 'Image file is invalid', imageUploadEntityNameRequired: 'Please enter a name before uploading an image', imageUploadFailed: 'Image upload failed', taskRequired: 'Please enter a task', selectTask: 'Please select a task', taskDoesNotExist: 'Task does not exist', postRequired: 'Please enter a post', postTooLong: 'Post is too long', lifeTagRequired: 'Please select at least one tag', commentRequired: 'Please enter a comment', commentTooLong: 'Comment is too long', reactionInvalid: 'Reaction is invalid', cursorInvalid: 'Cursor is invalid', tagInvalid: 'Tag is invalid', entityTypeInvalid: 'Entity type is invalid', recordInvalid: 'Record is invalid', commentInvalid: 'Comment is invalid', selectRecord: 'Please select a record', typeMin: 'Choose at least 1 type', typeMax: 'Choose at most 2 types', skillMax: 'Choose at most 2 specialities', favoriteMax: 'Choose at most 6 favourites', dropItemSelectedSkill: 'Drop items must be linked to selected specialities', pokemonIdRequired: 'Pokemon ID is required', pokemonNameRequired: 'Pokemon name is required', heightNonNegative: 'Height must be a non-negative number', weightNonNegative: 'Weight must be a non-negative number', environmentRequired: 'Ideal Habitat is required', skillNoDrop: 'This speciality cannot have a drop item', habitatNameRequired: 'Habitat name is required', usageRequired: 'Usage is required', itemNameRequired: 'Item name is required', categoryRequired: 'Category is required', recipeFreeWithRecipe: 'An item with a recipe cannot be marked as recipe-free', itemRequired: 'Item is required', recipeFreeItem: 'This item is marked as recipe-free', statNonNegative: 'Base stat must be a non-negative integer', pokemonDataFileEmpty: 'Pokemon data file is empty', pokemonDataFileUnavailable: 'Pokemon data file is unavailable' }, wordings: { keyNotFound: 'System wording key was not found', localeRequired: 'Locale is required', valueRequired: 'Wording is required', placeholderMismatch: 'Placeholders must match the default wording' }, permissions: { nameRequired: 'Name is required', valueTooLong: 'Value is too long', invalidSelection: 'Selection is invalid', roleKeyInvalid: 'Role key is invalid', roleNotFound: 'Role not found', ownerRequired: 'At least one Owner is required', ownerRoleLocked: 'Owner role permissions cannot be edited', ownerRoleOperationDenied: 'Only Owners with Owner assignment permission can assign or remove the Owner role', roleLevelOperationDenied: 'You can only assign or remove roles below your highest role level', permissionKeyInvalid: 'Permission key is invalid', permissionNotFound: 'Permission not found', criticalPermissionRequired: 'Critical administration permissions must remain enabled', permissionManagerRequired: 'At least one verified user must be able to manage permissions', userNotFound: 'User not found' } }, email: { auth: { verificationSubject: 'Verify your Pokopia Wiki email', verificationHtml: '

Open the link below to verify your email:

Verify email

The link expires in {hours} hours.

', verificationText: 'Open this link to verify your Pokopia Wiki email: {url}\nThe link expires in {hours} hours.', passwordResetSubject: 'Reset your Pokopia Wiki password', passwordResetHtml: '

Open the link below to reset your password:

Reset password

The link expires in {hours} hours.

', passwordResetText: 'Open this link to reset your Pokopia Wiki password: {url}\nThe link expires in {hours} hours.' } }, }, 'zh-CN': { common: { add: '添加', admin: '管理', all: '全部', back: '返回', backToList: '返回列表', cancel: '取消', close: '关闭', create: '创建', delete: '删除', edit: '编辑', details: '详情', filters: '筛选', loading: '加载中', name: '名称', new: '新建', none: '无', save: '保存', saving: '保存中', search: '搜索', select: '请选择', selected: '已选', system: '系统', noRecords: '暂无记录', fieldForLanguage: '{field}({language})', searchOrSelect: '搜索或选择', noMatches: '没有匹配项', createNamed: '添加「{name}」', creating: '添加中', inDev: '开发中', removeNamed: '移除{name}', quantity: '数量', eventItem: '活动物品', required: '必填' }, nav: { pokemon: 'Pokemon', habitats: '栖息地', items: '物品', recipes: '材料单', automation: '自动化', dish: '料理', events: '活动', actions: '动作', dreamIsland: 'Dream Island', clothes: '服装', checklist: 'CheckList', life: 'Life', admin: '管理', main: '主导航', openMenu: '打开导航', closeMenu: '关闭导航', language: '语言', profile: '个人资料', login: '登录', logout: '退出', register: '注册' }, seo: { siteDescription: '浏览 Pokopia Wiki 的 Pokemon、栖息地、物品、材料单、每日清单和 Life 社区动态。', pokemonDetailDescription: '查看 {name} 在 Pokopia Wiki 中的栖息地、属性、特长、喜欢的东西、六维、相关物品、讨论和编辑历史。', itemDetailDescription: '查看 {name} 在 Pokopia Wiki 中的分类、用途、入手方式、自定义、相关材料单、栖息地和 Pokemon 掉落。', habitatDetailDescription: '查看 {name} 在 Pokopia Wiki 中的配方、可能出现的 Pokemon、地图、时间、天气、讨论和编辑历史。', recipeDetailDescription: '查看 {name} 材料单在 Pokopia Wiki 中的结果物品、入手方式、需要材料、讨论和编辑历史。' }, auth: { accountAccess: 'Trainer Pass', email: '邮箱', password: '密码', currentPassword: '当前密码', newPassword: '新密码', confirmPassword: '确认密码', displayName: '显示名', referralCode: '邀请码', referralCodePlaceholder: '可选邀请码', referralCodeHint: '可填写其他训练师分享的邀请码。', loginTitle: '登录', loginSubtitle: '使用已验证邮箱进入 Pokopia Wiki', loggingIn: '登录中', loginFailed: '登录失败', rememberMe: '记住我', forgotPassword: '忘记密码?', noAccount: '还没有账号?', registerTitle: '注册', registerSubtitle: '创建账号后需要完成邮箱验证', registerFailed: '注册失败', sending: '发送中', sendVerification: '发送验证邮件', hasAccount: '已有账号?', requestResetTitle: '重置密码', requestResetSubtitle: '向账号邮箱发送密码重置链接。', sendResetLink: '发送重置链接', requestResetFailed: '密码重置请求失败', resetTitle: '设置新密码', resetSubtitle: '使用邮件中的重置链接更新密码。', resetPassword: '重置密码', resetting: '重置中', resetFailed: '密码重置失败', passwordMismatch: '两次输入的密码不一致', invalidPasswordReset: '密码重置链接无效或已过期', verifyTitle: '邮箱验证', verifySubtitle: '完成验证后即可登录', verifyingEmail: '正在验证邮箱', invalidVerification: '验证链接无效或已过期', verifyFailed: '邮箱验证失败', goLogin: '去登录' }, errors: { requestFailed: '请求失败({status})', operationFailed: '操作失败', loadFailed: '加载失败', addFailed: '添加失败', saveFailed: '保存失败', completeEmailVerification: '请先完成邮箱验证', permissionDenied: '你没有权限执行这个操作' }, pages: { profile: { title: '个人资料', subtitle: '管理账号资料、邀请信息和密码。', loading: '正在加载个人资料', accountSummary: '账号概览', profileDetails: '资料详情', displayNameHint: '显示名会用于编辑署名、讨论和 Life 动态。', displayNameRequired: '请输入显示名。', emailVerified: '邮箱已验证', emailUnverified: '邮箱未验证', saved: '个人资料已保存', saveFailed: '个人资料保存失败', referralTitle: '邀请', referralCode: '邀请码', referralUrl: '邀请链接', referralHint: '分享给新编辑者,对方完成邮箱验证后会计入有效邀请。', verifiedReferralCount: '有效邀请', copyReferralLink: '复制链接', referralCopied: '邀请链接已复制', referralCopyFailed: '邀请链接复制失败', referralLoadFailed: '邀请信息加载失败', publicSubtitle: '查看该成员的 Life 动态、互动、评论和 Wiki 贡献。', publicKicker: '成员主页', tabsLabel: '个人主页分区', tabFeeds: 'Feeds', tabContributions: '贡献', tabReactions: '互动', tabComments: '评论', tabAccount: '账号', contributionFiltersLabel: '贡献分类', contributionConfig: '配置', contributionsFilterEmpty: '该分类暂无贡献', reactionFiltersLabel: '互动分类', reactionsFilterEmpty: '该分类暂无互动', commentFiltersLabel: '评论分类', commentsFilterEmpty: '该分类暂无评论', lifeCommentCategory: 'Life', discussionCommentCategory: 'Wiki', passwordTitle: '修改密码', passwordHint: '至少使用 8 个字符。', passwordSaved: '密码已更新', passwordSaveFailed: '密码更新失败', savePassword: '保存密码', joinedAt: '加入于 {date}', lifePosts: 'Life 动态', lifeComments: 'Life 评论', lifeReactions: '互动', discussionComments: 'Wiki 评论', commentsMade: '评论', wikiEdits: 'Wiki 编辑', wikiCreates: '新增', wikiUpdates: '更新', wikiDeletes: '删除', imageUploads: '图片', wikiContributionStats: 'Wiki 贡献统计', communityStats: '社区统计', contributionBreakdown: '贡献分布', total: '总计', otherContributions: '其他', feedsEmpty: '暂无 Life 动态', contributionsEmpty: '暂无 Wiki 贡献', reactionsEmpty: '暂无互动', commentsEmpty: '暂无评论', loadMore: '加载更多', lifeComment: 'Life 评论', discussionComment: 'Wiki 讨论', lifePostTarget: 'Life 动态', lifePostBy: '{name} 的 Life 动态' }, pokemon: { title: 'Pokemon', subtitle: '搜索宝可梦,并按特长、环境、喜欢的东西筛选。', detailKicker: 'Pokédex Detail', editKicker: 'Pokédex Edit', editSubtitle: '维护 Pokemon 介绍、属性、六维、特长和喜欢的东西。', editSections: 'Pokemon 编辑分区', editTabBasic: '基础', editTabAdvance: '进阶', newTitle: '新增 Pokemon', editTitle: '编辑 #{id} {name}', id: 'Pokemon ID', fetchData: '获取数据', fetchingData: '正在获取', fetchIdentifier: '数据标识', fetchIdentifierPlaceholder: 'bulbasaur 或 1', fetchIdentifierRequired: '请输入 Pokemon 数据标识', fetchFailed: 'Pokemon 数据获取失败', fetchIdMismatch: '获取到的 Pokemon ID #{id} 与当前编辑内容不一致。', fetchResults: 'Pokemon 数据结果', fetchSearching: '正在搜索数据', fetchNoMatches: '没有匹配的 Pokemon 数据', fetchSearchFailed: 'Pokemon 数据搜索失败', image: '图片', fetchImages: '获取图片', fetchingImages: '正在获取', imageFetchFailed: 'Pokemon 图片获取失败', imageNoMatches: '没有可用的 Pokemon 图片', loadingImages: '正在加载 Pokemon 图片', selectedImage: '已选择的 Pokemon 图片', imageOptions: 'Pokemon 图片选项', clearImage: '清除图片', imageEmpty: '尚未选择 Pokemon 图片', imageAlt: '{name} {variant} 图片', eventItem: '活动物品', loadingList: '正在加载 Pokemon 列表', loadingDetail: '正在加载 Pokemon 详情', loadingEdit: '正在加载 Pokemon 编辑内容', environmentPrefix: '喜欢的环境:{name}', details: '介绍', genus: '分类', height: '身高', heightInput: '身高(in)', heightImperial: 'ft / in', heightMetric: 'm', feet: 'ft', inches: 'in', meters: 'm', weight: '体重', weightInput: '体重(lb)', pounds: 'lb', kilograms: 'kg', measurements: '身高与体重', types: '属性', typeOne: '属性 1', typeTwo: '属性 2', typesAndStats: '属性与六维', statsTitle: '六维', stats: { hp: 'HP', attack: '攻击', defense: '防御', specialAttack: '特攻', specialDefense: '特防', speed: '速度' }, environment: '喜欢的环境', skills: '特长', skillMatchMode: '特长匹配方式', any: '任意', all: '全部', favoriteThings: '喜欢的东西', favoriteThingMatchMode: '喜欢的东西匹配方式', skillDrops: '特长掉落物', skillDrop: '{name}掉落物', dropItem: '掉落物', searchPokemon: '搜索 Pokemon', relatedPokemon: '相关 Pokemon', relatedHabitat: '相关 Pokemon 栖息地', relatedItems: '关联物品', relatedItemCategory: '关联物品分类', habitats: '栖息地', namePlaceholder: '名字', searchTypes: '搜索属性', searchEnvironment: '搜索喜欢的环境', searchSkills: '搜索特长', searchFavoriteThings: '搜索喜欢的东西', searchItems: '搜索物品' }, habitats: { title: '栖息地', subtitle: '查看配方和可能出现的宝可梦。', detailKicker: 'Habitat Detail', detailSubtitle: '栖息地详情', editSubtitle: '维护栖息地配方和可能出现的 Pokemon。', newTitle: '新增栖息地', editTitle: '编辑 {name}', fallbackName: '栖息地', loadingList: '正在加载栖息地列表', loadingDetail: '正在加载栖息地详情', loadingEdit: '正在加载栖息地编辑内容', eventItem: '活动物品', recipe: '配方', recipeList: '配方列表', possiblePokemon: '可能出现的宝可梦', addItem: '添加物品', addPokemon: '添加 Pokemon', maps: '地图', searchMaps: '搜索地图' }, items: { title: '物品', subtitle: '按分类、用途、标签查看物品。', detailKicker: 'Item Detail', detailSubtitle: '物品详情', editKicker: 'Item Edit', editSubtitle: '维护物品分类、用途、入手方式、自定义和标签。', newTitle: '新增物品', editTitle: '编辑 {name}', fallbackName: '物品', loadingList: '正在加载列表', loadingDetail: '正在加载物品详情', loadingEdit: '正在加载物品编辑内容', category: '分类', usage: '用途', tags: '标签', acquisitionMethods: '入手方式', customization: '自定义', dyeable: '可染色', dualDyeable: '可双区染色', patternEditable: '可改花纹', noRecipe: '无材料单', eventItem: '活动物品', recipeInfo: '材料单信息', relatedRecipes: '相关材料单', relatedHabitats: '相关栖息地', pokemonDrops: 'Pokemon 掉落', createRecipe: '创建材料单', searchCategory: '搜索分类', searchUsage: '搜索用途', searchMethods: '搜索入手方式', searchTags: '搜索标签' }, recipes: { title: '材料单', subtitle: '按分类、用途、标签查看材料单。', detailKicker: 'Recipe Detail', detailSubtitle: '材料单详情', editKicker: 'Recipe Edit', editSubtitle: '维护材料单结果物品、入手方式和需要材料。', newTitle: '新增材料单', editTitle: '编辑 {name}', fallbackName: '材料单', loadingList: '正在加载材料单列表', loadingDetail: '正在加载材料单详情', loadingEdit: '正在加载材料单编辑内容', item: '物品', materials: '需要材料', addMaterial: '添加材料' }, comingSoon: { status: '正在开发中', heading: '这个 Wiki 分区正在准备中。', previewLabel: '分区预览', sections: { automation: { kicker: 'Automation', title: '自动化', subtitle: '自动化基地和工厂方案会在这里分享。', body: '自动化分区会帮助玩家对比生产配置、材料产出、所需 Pokemon、生产顺序和共同喜好物品。', preview: { one: '工厂方案会围绕基地布局和生产目标整理。', two: '材料产出、Pokemon 需求和生产顺序会保持易读。', three: '共同喜好物品可用于规划更适合协作的队伍和流程。' } }, dish: { kicker: 'Dish', title: '料理', subtitle: '未来会用于整理料理和食物相关发现。', body: '料理页面会围绕清晰浏览、来源记录和材料关联来设计。', preview: { one: '料理记录会优先呈现名称、效果和发现方式。', two: '需要时会把材料关系连接回物品和材料单。', three: '页面会先保持浏览友好,后续再自然承接社区编辑内容。' } }, events: { kicker: 'Events', title: '活动', subtitle: '季节活动和限时内容资料会在这里整理。', body: '活动分区会在准备好后集中展示时间、奖励和参与信息。', preview: { one: '活动卡片会让日期和开放时间更容易浏览。', two: '奖励与关联物品会靠近活动摘要展示。', three: '活动结束后,历史记录也会保持可读。' } }, actions: { kicker: 'Actions', title: '动作', subtitle: '挥手、跳舞等游戏内快捷动作会记录在这里。', body: '动作分区会作为游戏内表情、社交动作和快捷动作的快速参考。', preview: { one: '每个动作会用面向玩家的语言说明动作或快捷方式。', two: '常见内容包括挥手、跳舞和其他社交动作。', three: '后续可在数据模型准备好后补充解锁或使用条件。' } }, dreamIsland: { kicker: 'Dream Island', title: 'Dream Island', subtitle: 'Dream Island 相关资料正在整理。', body: '这个区域未来会用更像目的地资料页的方式展示岛屿信息。', preview: { one: '岛屿记录会优先整理地点、开放状态和重要发现。', two: '可关联的 Pokemon、物品或活动会从页面中连接出来。', three: '目前先保持公开浏览入口,不额外增加管理流程。' } }, clothes: { kicker: 'Clothes', title: '服装', subtitle: '外观和服装资料正在准备。', body: '服装页面会用于对比外观、入手方式和自定义信息。', preview: { one: '服装条目会优先整理展示名称和视觉分类。', two: '入手方式与自定义信息会在资料可用后接入。', three: '页面会保持服装资料清晰,不和普通物品列表混在一起。' } } } }, checklist: { title: '每日清单', subtitle: '查看每天可以完成的事项。', sectionTitle: '每日做什么', empty: '暂无每日清单', loading: '正在加载每日清单', task: 'Task', newTask: '新增 Task', editTask: '编辑 Task' }, life: { title: 'Life', subtitle: '分享喜欢的心得、想法和社区发现。', kicker: '社区动态', composerTitle: '分享动态', composerPrompt: '想分享什么?', bodyLabel: '动态内容', bodyPlaceholder: '分享一段想法、心得或发现……', newPost: 'New Post', tags: '标签', languages: '语言区', allLanguages: '全部语言', allTags: '全部', tagPlaceholder: '选择标签', searchTags: '搜索标签', search: '搜索动态', searchPlaceholder: '搜索动态内容……', clearSearch: '清除搜索', searchEmpty: '没有匹配的动态', searchEmptyHint: '换个关键词或清除搜索。', comments: '评论', commentsCount: '{count} 条评论', comment: '评论', hideComments: '收起评论', react: '点赞', reactions: '互动', reactionsCount: '{count} 次互动', reactionCountLabel: '{reaction}:{count}', reactionLike: '喜欢', reactionHelpful: '有帮助', reactionFun: '有趣', reactionThanks: '感谢', chooseReaction: '选择互动', reactionMenu: '互动菜单', removeReaction: '取消互动', reactionFailed: '互动失败', commentPlaceholder: '写下评论……', commentReplyPlaceholder: '写下回复……', postComment: '发表评论', postingComment: '评论中', reply: '回复', postReply: '发布回复', postingReply: '回复中', cancelReply: '取消回复', noComments: '暂无评论', loadingComments: '正在加载评论', loadMoreComments: '加载更多评论', deleteComment: '删除评论', deleteCommentConfirm: '确认删除这条评论?', commentDeleted: '评论已删除', commentRequired: '请输入评论内容。', commentFailed: '评论失败', replyFailed: '回复失败', deleteCommentFailed: '删除评论失败', publish: '发布', publishing: '发布中', update: '更新', updating: '更新中', cancelEdit: '取消编辑', empty: '暂无动态', emptyHint: '已验证成员可以发布第一条 Life 动态。', loading: '正在加载 Life 动态', retryFeed: '重试加载', loginPrompt: '使用已验证邮箱登录后即可发布。', verifyPrompt: '完成邮箱验证后即可发布。', editPost: '编辑动态', deletePost: '删除动态', saveEdit: '保存编辑', postFailed: '发布失败', saveFailed: '保存失败', deleteFailed: '删除失败', bodyRequired: '请输入动态内容。', tagRequired: '请至少选择 1 个标签。', byUnknown: '社区成员', edited: '已编辑', deleteConfirm: '确认删除这条动态?', moderationUnreviewed: '未审核', moderationReviewing: '审核中', moderationApproved: '审核通过', moderationRejected: '审核不通过', moderationFailed: '审核失败', moderationRetry: '重新审核', moderationRetrying: '重审中', moderationRetryFailed: '重新审核失败', charactersLeft: '还可以输入 {count} 个字符' }, admin: { title: '管理', subtitle: '管理 Wiki 内容、配置、本地化和访问权限。', modules: '管理模块', contentGroup: '内容', configurationGroup: '配置', localizationGroup: '本地化', accessGroup: '访问权限', loading: '正在加载管理列表', users: '用户', roles: '角色', permissions: '权限', config: '系统配置', configType: '系统配置类型', checklist: 'CheckList', pokemonList: 'Pokemon 列表', itemList: '物品列表', recipeList: '材料单列表', habitatList: '栖息地列表', languages: '语言', newConfig: '新增{name}', editConfig: '编辑{name}', hasItemDrop: '有掉落物', dragSort: '拖曳排序:{name}', dragSortTitle: '拖曳排序', languageCode: 'Code', languageName: '语言名称', enabled: '启用', defaultLanguage: '默认语言', sortOrder: '排序', newLanguage: '新增语言', editLanguage: '编辑语言', wordings: '系统文案', aiModeration: 'AI 审核', aiModerationEnabled: '启用', aiModerationFormat: 'API 格式', aiModerationFormatGemini: 'Gemini generateContent', aiModerationFormatOpenAi: 'OpenAI-compatible chat completions', aiModerationAuthMode: '鉴权方式', aiModerationAuthQueryKey: 'Query key', aiModerationAuthBearer: 'Bearer token', aiModerationEndpoint: 'End Point', aiModerationModel: '模型', aiModerationRpm: '每分钟请求数', aiModerationApiKey: 'API Key', aiModerationApiKeyConfigured: 'API Key 已配置', aiModerationApiKeyMissing: 'API Key 未配置', aiModerationClearApiKey: '清除已保存 API Key', aiModerationSettings: 'AI 审核设置', wordingLocale: '语言', wordingModule: '模块', wordingSurface: '端', wordingMissingOnly: '只看缺失', wordingKey: 'Key', wordingValue: '文案', defaultValue: '默认文案', placeholders: '占位符', missingTranslation: '缺少翻译', allModules: '全部模块', allSurfaces: '全部端', surfaceFrontend: '前端', surfaceBackend: '后端', surfaceEmail: '邮件', editWording: '编辑文案', userRoles: '用户角色', noRoles: '无角色', newRole: '新增角色', editRole: '编辑角色', roleKey: '角色 Key', roleName: '角色名称', description: '说明', level: '层级', disabled: '停用', systemRole: '系统角色', roleLevel: '层级 {level}', permissionCount: '{count} 个权限', rolePermissions: '角色权限', newPermission: '新增权限', editPermission: '编辑权限', permissionKey: '权限 Key', permissionName: '权限名称', category: '分类', systemPermission: '系统权限' } }, config: { pokemonTypes: 'Pokemon 属性', skills: '特长', environments: '喜欢的环境', favoriteThings: '喜欢的东西 / 标签', itemCategories: '物品分类', itemUsages: '物品用途', acquisitionMethods: '入手方式', maps: '地图', lifeTags: 'Life 标签' }, appearance: { time: '时段', weather: '天气', rarity: '稀有度', map: '地图', maps: '出现地图', morning: '早晨', noon: '中午', evening: '傍晚', night: '晚上', sunny: '晴天', cloudy: '阴天', rainy: '雨天', stars: '{count} 星' }, history: { title: '贡献记录', createdBy: '由谁创建', lastEdited: '最后编辑', editHistory: '编辑历史', before: '修改前', after: '修改后', author: '作者', time: '时间', action: '动作', create: '创建', update: '编辑', delete: '删除', empty: '暂无编辑历史' }, media: { image: '图片', imageAlt: '{name}图片', uploadImage: '上传图片', uploading: '上传中', uploadedImage: '上传图片', selectedImage: '已选择图片', imageHistory: '图片历史', imageOptions: '图片选项', clearImage: '清除图片', imageEmpty: '尚未选择图片', imageHistoryEmpty: '暂无上传图片', uploadFailed: '图片上传失败', selectImage: '选择图片' }, discussion: { title: '讨论', count: '{count} 条评论', comment: '评论', commentPlaceholder: '写下评论……', replyPlaceholder: '写下回复……', postComment: '发表评论', postingComment: '评论中', reply: '回复', postReply: '发布回复', postingReply: '回复中', cancelReply: '取消回复', deleteComment: '删除评论', deleteConfirm: '确认删除这条评论?', deletedComment: '评论已删除', commentRequired: '请输入评论内容。', commentFailed: '评论失败', replyFailed: '回复失败', deleteFailed: '删除失败', languages: '语言区', allLanguages: '全部语言', moderationUnreviewed: '未审核', moderationReviewing: '审核中', moderationApproved: '审核通过', moderationRejected: '审核不通过', moderationFailed: '审核失败', moderationRetry: '重新审核', moderationRetrying: '重审中', moderationRetryFailed: '重新审核失败', loading: '正在加载讨论', loadMore: '加载更多评论', empty: '暂无讨论', emptyHint: '现在发起新的讨论。', loginPrompt: '使用已验证邮箱登录后即可评论。', verifyPrompt: '完成邮箱验证后即可评论。', byUnknown: '社区成员', charactersLeft: '还可以输入 {count} 个字符' }, server: { errors: { foreignKey: '引用的数据不存在,或当前记录正在被使用', duplicate: '同名或相同 ID 的记录已存在', invalidField: '字段值不合法', serverError: '服务器错误', loginRequired: '请先登录', verifyEmailFirst: '请先完成邮箱验证', permissionDenied: '权限不足', notFound: '未找到记录', rateLimited: '请求过于频繁,请稍后再试。' }, auth: { emailRequired: '请输入邮箱', invalidEmail: '邮箱格式不正确', displayNameRequired: '请输入显示名', displayNameLength: '显示名长度需为 1 到 40 个字符', passwordLength: '密码至少需要 8 个字符', invalidToken: '验证链接无效或已过期', emailAlreadyRegistered: '该邮箱已注册', checkVerificationEmail: '请查收验证邮件', emailVerified: '邮箱已验证', checkPasswordResetEmail: '如果该邮箱已注册,系统会发送密码重置链接。', passwordResetComplete: '密码已更新,请使用新密码登录。', passwordChanged: '密码已更新。', invalidCredentials: '邮箱或密码不正确', verifyEmailFirst: '请先完成邮箱验证', invalidResetToken: '密码重置链接无效或已过期', currentPasswordInvalid: '当前密码不正确', invalidReferralCode: '邀请码无效' }, validation: { nameRequired: '请输入名称', recordMissing: '记录不存在', languageCodeInvalid: '语言 Code 不合法', languageNameRequired: '请输入语言名称', defaultLanguageMustBeEnglish: '默认语言必须是 English', defaultLanguageMustBeEnabled: '默认语言必须启用', languageNotFound: '语言不存在', defaultLanguageRequired: '必须保留一个默认语言', defaultLanguageCannotBeDeleted: '默认语言不能删除', selectLanguage: '请选择语言', languageDoesNotExist: '语言不存在', pokemonIdentifierRequired: '请输入 Pokemon 标识', pokemonTypeDataUnavailable: 'Pokemon 属性数据不可用', pokemonDataNotFound: '未找到 Pokemon 数据', pokemonImagePathInvalid: 'Pokemon 图片路径不合法', imagePathInvalid: '图片路径不合法', imageUploadRequired: '请选择图片', imageUploadTypeInvalid: '不支持这种图片类型', imageUploadContentInvalid: '图片文件不合法', imageUploadEntityNameRequired: '请先输入名称再上传图片', imageUploadFailed: '图片上传失败', taskRequired: '请输入任务', selectTask: '请选择任务', taskDoesNotExist: '任务不存在', postRequired: '请输入动态内容', postTooLong: '动态内容过长', lifeTagRequired: '请至少选择 1 个标签', commentRequired: '请输入评论内容', commentTooLong: '评论内容过长', reactionInvalid: '互动类型不合法', cursorInvalid: '分页位置不合法', tagInvalid: '标签不合法', entityTypeInvalid: '实体类型不合法', recordInvalid: '记录不合法', commentInvalid: '评论不合法', selectRecord: '请选择记录', typeMin: '请至少选择 1 个属性', typeMax: '最多选择 2 个属性', skillMax: '最多选择 2 个特长', favoriteMax: '最多选择 6 个喜欢的东西', dropItemSelectedSkill: '掉落物必须关联到已选择的特长', pokemonIdRequired: '请输入 Pokemon ID', pokemonNameRequired: '请输入 Pokemon 名称', heightNonNegative: '身高必须是不小于 0 的数字', weightNonNegative: '体重必须是不小于 0 的数字', environmentRequired: '请选择喜欢的环境', skillNoDrop: '这个特长不能设置掉落物', habitatNameRequired: '请输入栖息地名称', usageRequired: '请选择用途', itemNameRequired: '请输入物品名称', categoryRequired: '请选择分类', recipeFreeWithRecipe: '已有材料单的物品不能标记为无材料单', itemRequired: '请选择物品', recipeFreeItem: '这个物品已标记为无材料单', statNonNegative: '六维必须是不小于 0 的整数', pokemonDataFileEmpty: 'Pokemon 数据文件为空', pokemonDataFileUnavailable: 'Pokemon 数据文件不可用' }, wordings: { keyNotFound: '系统文案 Key 不存在', localeRequired: '请选择语言', valueRequired: '请输入文案', placeholderMismatch: '占位符必须与默认文案一致' }, permissions: { nameRequired: '请输入名称', valueTooLong: '内容过长', invalidSelection: '选择项不合法', roleKeyInvalid: '角色 Key 不合法', roleNotFound: '角色不存在', ownerRequired: '必须至少保留一个 Owner', ownerRoleLocked: 'Owner 角色权限不能编辑', ownerRoleOperationDenied: '只有具备 Owner 分配权限的 Owner 可以分配或移除 Owner 角色', roleLevelOperationDenied: '只能分配或移除低于自己最高角色等级的角色', permissionKeyInvalid: '权限 Key 不合法', permissionNotFound: '权限不存在', criticalPermissionRequired: '关键管理权限必须保持启用', permissionManagerRequired: '必须至少保留一个可管理权限的已验证用户', userNotFound: '用户不存在' } }, email: { auth: { verificationSubject: '验证你的 Pokopia Wiki 邮箱', verificationHtml: '

请点击下面的链接完成邮箱验证:

验证邮箱

链接将在 {hours} 小时后失效。

', verificationText: '请打开以下链接完成 Pokopia Wiki 邮箱验证:{url}\n链接将在 {hours} 小时后失效。', passwordResetSubject: '重置你的 Pokopia Wiki 密码', passwordResetHtml: '

请点击下面的链接重置密码:

重置密码

链接将在 {hours} 小时后失效。

', passwordResetText: '请打开以下链接重置 Pokopia Wiki 密码:{url}\n链接将在 {hours} 小时后失效。' } } } } as const; export type SystemWordingSurface = 'frontend' | 'backend' | 'email'; export type SystemWordingCatalogEntry = { key: string; module: string; surface: SystemWordingSurface; description: string; placeholders: string[]; values: Record; }; const placeholderPattern = /\{([A-Za-z0-9_]+)\}/g; function isMessageTree(value: SystemWordingLeaf | SystemWordingTree): value is SystemWordingTree { return typeof value === 'object' && value !== null; } function collectPlaceholders(value: string): string[] { return [...new Set([...value.matchAll(placeholderPattern)].map((match) => match[1]))].sort(); } function mergePlaceholders(values: Record): string[] { return [...new Set(Object.values(values).flatMap(collectPlaceholders))].sort(); } function moduleForKey(key: string): string { const parts = key.split('.'); if ((parts[0] === 'pages' || parts[0] === 'server' || parts[0] === 'email') && parts[1]) { return `${parts[0]}.${parts[1]}`; } return parts[0] ?? 'system'; } function surfaceForKey(key: string): SystemWordingSurface { if (key.startsWith('email.')) return 'email'; if (key.startsWith('server.')) return 'backend'; return 'frontend'; } function flattenMessages(tree: SystemWordingTree, prefix = ''): Record { const entries: Record = {}; for (const [key, value] of Object.entries(tree)) { const nextKey = prefix ? `${prefix}.${key}` : key; if (isMessageTree(value)) { Object.assign(entries, flattenMessages(value, nextKey)); } else { entries[nextKey] = value; } } return entries; } export function flattenSystemWordingMessages(messages: SystemWordingMessages = systemWordingMessages): Record> { return Object.fromEntries(Object.entries(messages).map(([locale, tree]) => [locale, flattenMessages(tree)])); } export function systemWordingCatalogEntries(messages: SystemWordingMessages = systemWordingMessages): SystemWordingCatalogEntry[] { const flattened = flattenSystemWordingMessages(messages); const keys = Object.keys(flattened[defaultLocale] ?? {}).sort(); return keys.map((key) => { const values = Object.fromEntries( Object.entries(flattened) .map(([locale, localeMessages]) => [locale, localeMessages[key]]) .filter((entry): entry is [string, string] => typeof entry[1] === 'string' && entry[1].trim() !== '') ); return { key, module: moduleForKey(key), surface: surfaceForKey(key), description: '', placeholders: mergePlaceholders(values), values }; }); } export function systemWordingFallback(key: string, locale = defaultLocale): string | undefined { const flattened = flattenSystemWordingMessages(); return flattened[locale]?.[key] ?? flattened[defaultLocale]?.[key]; }