feat: add i18n tooling scripts and refine translations (#7269)

#### What type of PR is this?

/area ui
/kind feature
/milestone 2.20.x

#### What this PR does / why we need it:

Added three Node.js scripts to help manage YAML translation files:

1. `find_missing_translations.mjs`: Identifies missing translations by comparing language files with the English base file and generates ⁠_missing_translations_*.yaml files.
2. `apply_missing_translations.mjs`: Merges translated entries from missing translation files into the main language files, while preserving untranslated entries for future work.
3. `fix_translations.mjs`: Removes keys that exist in language files but not in the English base file.

Usage Example:

```
# Find missing translations
node scripts/find_missing_translations.mjs

# Apply only the translated entries
node scripts/apply_missing_translations.mjs

# Remove extra keys
node scripts/fix_translations.mjs
```

These scripts streamline the translation workflow and help maintain consistency across language files.

#### Which issue(s) this PR fixes:

None

#### Special notes for your reviewer:

#### Does this PR introduce a user-facing change?

```release-note
None
```
pull/7272/head
Ryan Wang 2025-03-06 18:16:59 +08:00 committed by GitHub
parent 1d8a25cd69
commit 62f479253e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 1248 additions and 239 deletions

View File

@ -119,9 +119,9 @@
"@types/qs": "^6.9.7",
"@types/randomstring": "^1.1.8",
"@types/ua-parser-js": "^0.7.39",
"@vitejs/plugin-legacy": "^6.0.0",
"@vitejs/plugin-vue": "^5.2.1",
"@vitejs/plugin-vue-jsx": "^4.1.1",
"@vitejs/plugin-legacy": "^6.0.0",
"@vitest/ui": "^0.34.1",
"@vue/compiler-sfc": "^3.5.13",
"@vue/eslint-config-prettier": "^7.1.0",
@ -133,6 +133,7 @@
"eslint": "^8.43.0",
"eslint-plugin-vue": "^9.17.0",
"husky": "^8.0.3",
"js-yaml": "^4.1.0",
"jsdom": "^20.0.3",
"lint-staged": "^13.2.2",
"npm-run-all": "^4.1.5",

View File

@ -294,6 +294,9 @@ importers:
husky:
specifier: ^8.0.3
version: 8.0.3
js-yaml:
specifier: ^4.1.0
version: 4.1.0
jsdom:
specifier: ^20.0.3
version: 20.0.3
@ -10284,8 +10287,8 @@ packages:
vue-component-type-helpers@2.0.19:
resolution: {integrity: sha512-cN3f1aTxxKo4lzNeQAkVopswuImUrb5Iurll9Gaw5cqpnbTAxtEMM1mgi6ou4X79OCyqYv1U1mzBHJkzmiK82w==}
vue-component-type-helpers@2.2.0:
resolution: {integrity: sha512-cYrAnv2me7bPDcg9kIcGwjJiSB6Qyi08+jLDo9yuvoFQjzHiPTzML7RnkJB1+3P6KMsX/KbCD4QE3Tv/knEllw==}
vue-component-type-helpers@2.2.8:
resolution: {integrity: sha512-4bjIsC284coDO9om4HPA62M7wfsTvcmZyzdfR0aUlFXqq4tXxM1APyXpNVxPC8QazKw9OhmZNHBVDA6ODaZsrA==}
vue-demi@0.13.11:
resolution: {integrity: sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==}
@ -15056,7 +15059,7 @@ snapshots:
ts-dedent: 2.2.0
type-fest: 2.19.0
vue: 3.5.13(typescript@5.6.2)
vue-component-type-helpers: 2.2.0
vue-component-type-helpers: 2.2.8
transitivePeerDependencies:
- encoding
- supports-color
@ -22218,7 +22221,7 @@ snapshots:
vue-component-type-helpers@2.0.19: {}
vue-component-type-helpers@2.2.0: {}
vue-component-type-helpers@2.2.8: {}
vue-demi@0.13.11(vue@3.5.13(typescript@5.6.2)):
dependencies:

View File

@ -0,0 +1,240 @@
/**
* Apply Missing Translations
* -------------------------
* This script applies translated entries from "_missing_translations_[lang].yaml" files
* to their corresponding language files.
*
* For each missing translations file, it:
* 1. Compares entries with the English base file
* 2. Identifies which entries have been translated (values different from English)
* 3. Merges only the translated entries into the main language file
* 4. Updates the missing translations file to keep only untranslated entries
*
* Usage:
* node scripts/apply_missing_translations.mjs
*
* Example output:
* Processing: src/locales/_missing_translations_zh-TW.yaml for language: zh-TW
* Updated src/locales/zh-TW.yaml with 15 translated entries.
* Updated src/locales/_missing_translations_zh-TW.yaml with 10 remaining untranslated entries.
*
* This script is designed to be run repeatedly as you translate more entries in the
* missing translations files. It will only apply entries that differ from the English version.
*/
import { existsSync } from "fs";
import fs from "fs/promises";
import yaml from "js-yaml";
import path from "path";
import { fileURLToPath } from "url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const translationsDirPath = path.resolve(__dirname, "../src/locales");
const baseFile = path.join(translationsDirPath, "en.yaml");
const VERBOSE = true;
async function main() {
try {
const dirEntries = await fs.readdir(translationsDirPath, {
withFileTypes: true,
});
const missingFiles = dirEntries
.filter(
(entry) =>
entry.isFile() &&
entry.name.includes("_missing_translations_") &&
entry.name.endsWith(".yaml")
)
.map((entry) => path.join(translationsDirPath, entry.name));
if (missingFiles.length === 0) {
console.log("No missing translation files found.");
return;
}
const enTranslations = await loadYamlFile(baseFile);
for (const missingFile of missingFiles) {
const fileName = path.basename(missingFile, ".yaml");
const langCode = fileName.replace("_missing_translations_", "");
const targetFile = path.join(translationsDirPath, `${langCode}.yaml`);
console.log(`\nProcessing: ${missingFile} for language: ${langCode}`);
if (!existsSync(targetFile)) {
console.log(`Target translation file ${targetFile} does not exist`);
continue;
}
try {
const missingTranslations = await loadYamlFile(missingFile);
const currentTranslations = await loadYamlFile(targetFile);
const translatedEntries = {};
const untranslatedEntries = {};
const stats = { added: 0, skipped: 0 };
const keyPaths = collectKeyPaths(missingTranslations);
console.log(
`Found ${keyPaths.length} keys in missing translations file.`
);
for (const keyPath of keyPaths) {
const missingValue = getValueByPath(missingTranslations, keyPath);
const enValue = getValueByPath(enTranslations, keyPath);
if (
missingValue !== enValue &&
missingValue !== null &&
missingValue !== undefined
) {
setValueByPath(translatedEntries, keyPath, missingValue);
stats.added++;
if (VERBOSE) {
console.log(
`✓ TRANSLATED: ${keyPath.join(
"."
)} = "${missingValue}" (EN: "${enValue}")`
);
}
} else {
setValueByPath(untranslatedEntries, keyPath, missingValue);
stats.skipped++;
if (VERBOSE) {
console.log(
`✗ NOT TRANSLATED: ${keyPath.join(
"."
)} = "${missingValue}" (same as EN: "${enValue}")`
);
}
}
}
if (stats.added > 0) {
const updatedTranslations = deepMerge(
currentTranslations,
translatedEntries
);
await saveYamlFile(updatedTranslations, targetFile);
console.log(
`Updated ${targetFile} with ${stats.added} translated entries.`
);
await saveYamlFile(untranslatedEntries, missingFile);
console.log(
`Updated ${missingFile} with ${stats.skipped} remaining untranslated entries.`
);
} else {
console.log(
`No translated entries found in ${missingFile}, files not updated.`
);
}
console.log(`\nSummary for ${langCode}:`);
console.log(`- Added: ${stats.added} translated entries`);
console.log(`- Remaining: ${stats.skipped} untranslated entries`);
} catch (e) {
console.error(`Error processing ${missingFile}:`, e);
}
}
} catch (e) {
console.error(`Error:`, e);
}
}
async function loadYamlFile(filePath) {
try {
const content = await fs.readFile(filePath, "utf8");
return yaml.load(content) || {};
} catch (error) {
console.error(`Error loading file ${filePath}:`, error);
return {};
}
}
async function saveYamlFile(data, filePath) {
try {
const yamlContent = yaml.dump(data, {
indent: 2,
lineWidth: -1,
});
await fs.writeFile(filePath, yamlContent, "utf8");
return true;
} catch (error) {
console.error(`Error saving file ${filePath}:`, error);
return false;
}
}
function collectKeyPaths(obj, currentPath = [], result = []) {
if (obj === null || typeof obj !== "object") {
return result;
}
for (const key of Object.keys(obj)) {
const newPath = [...currentPath, key];
if (obj[key] === null || typeof obj[key] !== "object") {
result.push(newPath);
} else {
collectKeyPaths(obj[key], newPath, result);
}
}
return result;
}
function getValueByPath(obj, path) {
let current = obj;
for (const key of path) {
if (
current === null ||
current === undefined ||
typeof current !== "object"
) {
return undefined;
}
current = current[key];
}
return current;
}
function setValueByPath(obj, path, value) {
let current = obj;
for (let i = 0; i < path.length - 1; i++) {
const key = path[i];
if (!current[key] || typeof current[key] !== "object") {
current[key] = {};
}
current = current[key];
}
const lastKey = path[path.length - 1];
current[lastKey] = value;
}
function deepMerge(target, source) {
const result = { ...target };
for (const key of Object.keys(source)) {
if (source[key] === null || typeof source[key] !== "object") {
result[key] = source[key];
} else if (target[key] === null || typeof target[key] !== "object") {
result[key] = { ...source[key] };
} else {
result[key] = deepMerge(target[key], source[key]);
}
}
return result;
}
main();

View File

@ -0,0 +1,112 @@
/**
* Find Missing Translations
* -------------------------
* This script identifies missing translations in language files by comparing them
* with the English base file (en.yaml).
*
* For each language file, it:
* 1. Compares it with the English base file
* 2. Identifies keys that are missing in the target language
* 3. Creates a "_missing_translations_[lang].yaml" file with those missing keys
*
* Usage:
* node scripts/find_missing_translations.mjs
*
* Example output:
* Generated src/locales/_missing_translations_zh-TW.yaml with 25 missing translations
*
* After running this script, you can translate the missing entries in the generated files,
* then use apply_missing_translations.mjs to merge them into the main language files.
*/
import fs from "fs/promises";
import yaml from "js-yaml";
import path from "path";
import { fileURLToPath } from "url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const translationsDirPath = path.resolve(__dirname, "../src/locales");
const baseFile = `${translationsDirPath}/en.yaml`;
async function main() {
try {
const baseTranslations = await loadYamlFile(baseFile);
const dirEntries = await fs.readdir(translationsDirPath, {
withFileTypes: true,
});
const translationFiles = dirEntries
.filter(
(entry) =>
entry.isFile() &&
entry.name.endsWith(".yaml") &&
entry.name !== "en.yaml" &&
!entry.name.includes("_missing_translations_")
)
.map((entry) => path.join(translationsDirPath, entry.name));
for (const transFile of translationFiles) {
const langCode = path.basename(transFile, ".yaml");
try {
const translations = await loadYamlFile(transFile);
const missing = findMissingTranslations(baseTranslations, translations);
if (Object.keys(missing).length > 0) {
const missingFile = `${translationsDirPath}/_missing_translations_${langCode}.yaml`;
await saveYamlFile(missing, missingFile);
console.log(
`Generated ${missingFile} with ${
Object.keys(missing).length
} missing translations`
);
}
} catch (e) {
console.log(`Error processing ${transFile}: ${e}`);
}
}
} catch (e) {
console.log(`Error: ${e}`);
}
}
async function loadYamlFile(filePath) {
const content = await fs.readFile(filePath, "utf8");
return yaml.load(content) || {};
}
async function saveYamlFile(data, filePath) {
const yamlContent = yaml.dump(data, {
indent: 2,
lineWidth: -1,
});
await fs.writeFile(filePath, yamlContent, "utf8");
}
function findMissingTranslations(baseDict, compareDict) {
const missing = {};
for (const key of Object.keys(baseDict)) {
if (!Object.prototype.hasOwnProperty.call(compareDict, key)) {
missing[key] = baseDict[key];
} else if (
typeof baseDict[key] === "object" &&
baseDict[key] !== null &&
typeof compareDict[key] === "object" &&
compareDict[key] !== null
) {
const subMissing = findMissingTranslations(
baseDict[key],
compareDict[key]
);
if (Object.keys(subMissing).length > 0) {
missing[key] = subMissing;
}
}
}
return missing;
}
main();

View File

@ -0,0 +1,117 @@
/**
* Fix Translations
* -------------------------
* This script removes translation keys that exist in language files but are not
* present in the English base file (en.yaml).
*
* For each language file, it:
* 1. Compares it with the English base file
* 2. Identifies keys that exist in the language file but not in the English file
* 3. Removes these extra keys from the language file
*
* Usage:
* node scripts/fix_translations.mjs
*
* Example output:
* Extra key found: common.outdatedKey
* Removed 5 extra keys from src/locales/zh-TW.yaml
*
* This script helps maintain consistency across language files by ensuring they
* only contain keys that are present in the English base file.
*/
import fs from "fs/promises";
import yaml from "js-yaml";
import path from "path";
import { fileURLToPath } from "url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const translationsDirPath = path.resolve(__dirname, "../src/locales");
const baseFile = path.join(translationsDirPath, "en.yaml");
async function main() {
try {
const baseTranslations = await loadYamlFile(baseFile);
const dirEntries = await fs.readdir(translationsDirPath, {
withFileTypes: true,
});
const translationFiles = dirEntries
.filter(
(entry) =>
entry.isFile() &&
entry.name.endsWith(".yaml") &&
entry.name !== "en.yaml" &&
!entry.name.includes("_missing_translations_")
)
.map((entry) => path.join(translationsDirPath, entry.name));
for (const transFile of translationFiles) {
try {
const translations = await loadYamlFile(transFile);
const extraKeysCount = removeExtraTranslations(
translations,
baseTranslations
);
if (extraKeysCount > 0) {
await saveYamlFile(translations, transFile);
console.log(`Removed ${extraKeysCount} extra keys from ${transFile}`);
} else {
console.log(`No extra keys found in ${transFile}`);
}
} catch (e) {
console.log(`Error processing ${transFile}: ${e}`);
}
}
} catch (e) {
console.log(`Error: ${e}`);
}
}
async function loadYamlFile(filePath) {
const content = await fs.readFile(filePath, "utf8");
return yaml.load(content) || {};
}
async function saveYamlFile(data, filePath) {
const yamlContent = yaml.dump(data, {
indent: 2,
lineWidth: -1,
});
await fs.writeFile(filePath, yamlContent, "utf8");
}
function removeExtraTranslations(translations, baseTranslations) {
let extraKeysCount = 0;
function cleanObject(obj, baseObj, path = "") {
const keysToDelete = [];
for (const key of Object.keys(obj)) {
if (!Object.prototype.hasOwnProperty.call(baseObj, key)) {
keysToDelete.push(key);
extraKeysCount++;
console.log(`Extra key found: ${path}${key}`);
} else if (
typeof obj[key] === "object" &&
obj[key] !== null &&
typeof baseObj[key] === "object" &&
baseObj[key] !== null
) {
cleanObject(obj[key], baseObj[key], `${path}${key}.`);
}
}
for (const key of keysToDelete) {
delete obj[key];
}
}
cleanObject(translations, baseTranslations);
return extraKeysCount;
}
main();

View File

@ -0,0 +1,700 @@
core:
sidebar:
menu:
items:
tools: Tools
operations:
logout:
tooltip: Logout
description: Clicking Confirm will redirect to the logout page. Please ensure that the content you are editing is saved.
profile:
tooltip: Profile
profile:
aggregate_role: Aggregate Role
uc_sidebar:
menu:
items:
profile: Profile
notification: Notifications
posts: Posts
operations:
console:
tooltip: Console
profile:
aggregate_role: Aggregate Role
dashboard:
widgets:
presets:
recent_published:
publishTime: Publish Time {publishTime}
notification:
title: Notifications
empty:
title: No unread notifications
post:
operations:
publish_in_batch:
title: Publish posts
description: Batch publish posts, the selected posts will be set to published status
cancel_publish_in_batch:
title: Cancel publish posts
description: Batch cancel publish posts, the selected posts will be set to unpublished status
batch_setting:
button: Batch settings
filters:
status:
items:
scheduling: Scheduling publish
sort:
items:
last_modify_time_desc: Recently Updated
last_modify_time_asc: Earliest Updated
list:
fields:
schedule_publish:
tooltip: Schedule publish
settings:
fields:
publish_time:
help:
schedule_publish: Schedule a timed task and publish it at {datetime}
owner:
label: Owner
tag:
filters:
sort:
items:
create_time_desc: Latest Created
create_time_asc: Earliest Created
display_name_desc: Descending order by tag name
display_name_asc: Ascending order by tag name
batch_setting_modal:
title: Post batch settings
fields:
common:
enabled: Enabled
op:
label: Operate
options:
add: Add
replace: Replace
remove_all: Remove all
category_group: Category
category_names: Select categories
tag_group: Tag
tag_names: Select tags
visible_group: Visible
visible_value: "Select visible option "
allow_comment_group: " Allow comment"
allow_comment_value: Choose whether to allow comments
owner_group: Owner
owner_value: Select owner
post_editor:
actions:
snapshots: Snapshots
post_tag:
operations:
delete_in_batch:
title: Delete the selected tags
post_category:
editing_modal:
fields:
template:
help: Customize the rendering template of the category archive page, which requires support from the theme
post_template:
label: Custom post template
help: Customize the rendering template of posts in the current category, which requires support from the theme
hide_from_list:
label: Hide from list
help: After turning on this option, this category and its subcategories, as well as its posts, will not be displayed in the front-end list. You need to actively visit the category archive page. This feature is only effective for the first-level directory.
list:
fields:
prevent_parent_post_cascade_query: Prevent parent category from including this category and its subcategories in cascade post queries
hide_from_list: This category is hidden, This category and its subcategories, as well as its posts, will not be displayed in the front-end list. You need to actively visit the category archive page
page_editor:
actions:
snapshots: Snapshots
attachment:
filters:
sort:
items:
display_name_asc: Ascending order by display name
display_name_desc: Descending order by display name
group_editing_modal:
toast:
group_name_exists: Group name already exists
policy_editing_modal:
toast:
policy_name_exists: Storage policy name already exists
uc_attachment:
empty:
title: There are no attachments.
message: There are no attachments, you can try refreshing or uploading attachments.
actions:
upload: Upload Attachment
filters:
sort:
items:
create_time_desc: Latest uploaded
create_time_asc: Earliest uploaded
size_desc: Descending order by file size
size_asc: Ascending order by file size
display_name_asc: Ascending order by display name
display_name_desc: Descending order by display name
view_type:
items:
grid: Grid Mode
list: List Mode
detail_modal:
title: "Attachment: {display_name}"
fields:
preview: Preview
display_name: Display name
media_type: Media type
size: Size
owner: Owner
creation_time: Creation time
permalink: Permalink
preview:
click_to_exit: Click to exit preview
video_not_support: The current browser does not support video playback.
audio_not_support: The current browser does not support audio playback.
not_support: This file does not support preview.
upload_modal:
title: Upload attachment
select_modal:
title: Select attachment
providers:
default:
label: Attachments
operations:
select:
result: ({count} items selected)
theme:
operations:
existed_during_installation:
title: The theme already exists.
description: The currently installed theme already exists, do you want to upgrade?
clear_templates_cache:
button: Clear templates cache
title: Clear templates cache
description: This feature allows you to refresh the cache to view the latest web results after modifying template files at runtime.
export_configuration:
button: Export theme configuration
import_configuration:
button: Import theme configuration
version_mismatch:
title: Version mismatch
description: The imported configuration file version does not match the current theme version, which may lead to compatibility issues. Do you want to continue importing?
invalid_format: Invalid theme configuration file
mismatched_theme: Configuration file does not match the selected theme
list_modal:
tabs:
local_upload: Local install / upgrade
remote_download:
label: Remote
fields:
url: Remote URL
detail:
fields:
homepage: Website
description: Description
license: License
issues: Issues feedback
plugin:
operations:
uninstall_in_batch:
title: Uninstall the selected plugins
uninstall_and_delete_config:
button: Uninstall and delete config
uninstall_and_delete_config_in_batch:
button: Uninstall and delete config
title: Uninstall the selected plugins and its corresponding configuration
change_status_in_batch:
activate_title: Activate the selected plugins
inactivate_title: Inactivate the selected plugins
reload_window:
button: Reload required
detail:
fields:
homepage: Homepage
repo: Source Repository
load_location: Storage Location
issues: Issues feedback
operations:
view_conditions:
button: View recent conditions
conditions_modal:
title: Recent conditions
fields:
type: Type
status: Status
reason: Reason
message: Message
last_transition_time: Last transition time
extension-settings:
title: Extension settings
extension-point-definition:
title: Extension point definitions
extension-definition:
empty:
title: There is currently no extension point implemented
actions:
extension-point-settings: Extension settings
user:
grant_permission_modal:
roles_preview:
all: The currently selected role contains all permissions
includes: "The currently selected role contains the following permissions:"
detail:
actions:
grant_permission:
title: Grant permission
profile:
title: Profile
fields:
email_verified:
tooltip: Verified
email_not_verified:
tooltip: Not verified
role:
editing_modal:
fields:
disallow_access_console:
label: Disable access to Console
help: Once checked, this role will not be able to access the Console
identity_authentication:
fields:
display_name:
local: Login with credentials
description:
local: Default login method built into Halo
list:
types:
form: Basic Authentication Method
oauth2: Third-party Authentication Method
uc_profile:
title: Profile
tabs:
detail: Detail
notification-preferences: Notification Preferences
pat: Personal Access Tokens
2fa: 2FA
devices: Devices
actions:
update_profile:
title: Update profile
change_password:
title: Change password
detail:
fields:
display_name: Display name
username: Username
email: Email
roles: Roles
bio: Bio
creation_time: Creation time
identity_authentication: Identity authentication
operations:
bind:
button: Bind
unbind:
button: Unbind
title: Unbind the login method for {display_name}
email_not_set:
description: Your email address has not been set yet. Click the button below to set it up.
title: Set up email
email_not_verified:
description: Your email address has not been verified yet, click the button below to verify it
title: Verify email
email_verified:
tooltip: Verified
2fa:
operations:
enable:
button: Enable 2FA
title: Enable 2FA
disable:
title: Disable 2FA
disable_totp:
title: Disable TOTP
password_validation_form:
fields:
password:
label: Password
help: Login password of the current account
methods:
title: Two-factor methods
totp:
title: TOTP
description: Configure two-step verification with TOTP application
fields:
status:
configured: Configured
not_configured: Not configured
operations:
reconfigure:
button: Reconfigure
configure:
button: Configure
title: TOTP configuration
fields:
code:
label: Verification code
help: 6-digit verification code obtained from the validator application
password:
label: Password
help: Login password of the current account
qrcode:
label: "Use the validator application to scan the QR code below:"
manual:
label: If you can't scan the QR code, click to view the alternative steps.
help: "Manually configure the validator application with the following code:"
pat:
operations:
delete:
title: Delete Personal Access Token
description: Are you sure you want to delete this personal access token?
revoke:
button: Revoke
title: Revoke Personal Access Token
description: Are you sure you want to revoke this personal access token?
toast_success: Revocation succeeded
copy:
title: Please copy and save immediately, Token will only be displayed once.
restore:
button: Restore
toast_success: Restore successfully
list:
empty:
title: No personal access tokens have been created
message: You can try refreshing or creating a new personal access token
fields:
expiresAt:
dynamic: Expires on {expiresAt}
forever: Never expires
status:
normal: Normal
revoked: Revoked
expired: Expired
creation_modal:
title: Create Personal Access Token
groups:
general: General
permissions: Permissions
fields:
name:
label: Name
expiresAt:
label: Expiration Time
help: Leave empty for no expiration
description:
label: Description
notification-preferences:
fields:
type: Type
editing_modal:
title: Edit Profile
groups:
general: General
annotations: Annotations
fields:
username:
label: Username
validation: Can only contain numbers, lowercase letters, periods (.), and hyphens (-), and cannot start or end with a period (.) or hyphen (-).
display_name:
label: Display name
email:
label: Email
phone:
label: Phone
avatar:
label: Avatar
bio:
label: Bio
change_password_modal:
title: Change password
fields:
new_password:
label: New password
confirm_password:
label: Confirm password
old_password:
label: Old password
email_verify_modal:
fields:
code:
label: Verification code
email:
label: Email address
new_email:
label: New email address
password:
label: Password
help: The login password for the current account
operations:
send_code:
buttons:
countdown: Resend in {timer} seconds
send: Send the verification code
sending: sending
toast_email_empty: Please enter your email address
toast_success: Verification code sent
verify:
toast_success: Verification successful
titles:
modify: Modify email address
verify: Verify email
device:
list:
fields:
current: Current
last_accessed_time: "Last accessed time: {time}"
detail_modal:
title: Login device details
fields:
os: OS
browser: Browser
creation_timestamp: Creation time
last_accessed_times: Last accessed time
last_authenticated_time: Last authenticated time
operations:
revoke:
title: Revoke device
description: Are you sure you want to revoke this device? After revoking, this device will be logged out
revoke_others:
title: Revoke all other devices
description: Are you sure you want to revoke all other devices? After you revoke, other devices will be logged out
toast_success: Login status of other devices has been revoked
uc_notification:
title: Notifications
tabs:
unread: Unread
read: Read
empty:
titles:
unread: No unread notifications
read: No read notifications
operations:
mark_as_read:
button: Mark as read
delete:
description: Are you sure you want to delete this notification?
title: Delete
overview:
fields:
activated_theme: Activated theme
enabled_plugins: Enabled plugins
backup:
operations:
remote_download:
button: Download and restore
restore_by_backup:
button: Restore
title: Restore from backup file
description: After clicking OK, data will be restored from the backup file {filename}.
restore:
tabs:
local:
label: Upload
remote:
label: Remote
fields:
url: Remote URL
backup:
label: Restore from backup files
empty:
title: No backup files
message: Currently no backup files are scanned. You can manually upload the backup files to the backups directory of the Halo working directory.
rbac:
role-template-manage-posts: Post Manage
role-template-post-author: Allows you to manage your own posts
role-template-post-contributor: Contributions allowed
role-template-post-publisher: Allow to publish own posts
role-template-post-attachment-manager: Allow images to be uploaded in posts
Actuator Management: System Information
Actuator Manage: Access System Information
Cache Management: Cache
Cache Manage: Cache Manage
Notification Configuration: Notification Configuration
role-template-notifier-config: Configure Notifier
Post Attachment Manager: Allow images to be uploaded in posts
Post Author: Allows you to manage your own posts
Post Contributor: Contributions allowed
Post Publisher: Allow to publish own posts
UC Attachment Manage: Allow to manage own attachments
role-template-uc-attachment-manager: Allow to manage own attachments
Recycle My Post: Recycle My Post
role-template-recycle-my-post: Recycle My Post
components:
annotations_form:
buttons:
expand: View more
collapse: Collapse
default_editor:
extensions:
upload:
error: Upload failed
click_retry: Click to retry
loading: Loading
attachment:
title: Attachment Library
permalink:
title: Input Link
placeholder: Input link and press Enter to confirm
operations:
replace:
button: Replace
toolbox:
show_hide_sidebar: Show/Hide Sidebar
title_placeholder: Please enter the title
user_avatar:
title: Avatar
toast_upload_failed: Failed to upload avatar
toast_remove_failed: Failed to delete avatar
cropper_modal:
title: Crop Avatar
remove:
title: Delete avatar
tooltips:
upload: Upload
zoom_in: Zoom In
zoom_out: Zoom Out
flip_horizontal: Flip Horizontal
flip_vertical: Flip Vertical
reset: Reset
editor_provider_selector:
tooltips:
disallow: The content format is different and cannot be switched
uppy:
image_editor:
revert: Revert
rotate: Rotate
zoom_in: Zoom in
zoom_out: Zoom out
flip_horizontal: Flip horizontal
aspect_ratio_square: Crop square
aspect_ratio_landscape: Crop landscape (16:9)
aspect_ratio_portrait: Crop portrait (9:16)
h2_warning_alert:
title: "Warning: H2 database in use"
description: The H2 database is only suitable for development and testing environments and is not recommended for use in production environments. H2 is very easy to cause data file corruption due to improper operation. If you must use it, please back up the data on time.
formkit:
select:
no_data: No data
validation:
password: "The password can only use uppercase and lowercase letters (A-Z, a-z), numbers (0-9), and the following special characters: !{'@'}#$%^&*"
verification_form:
no_action_defined: "{label} interface not defined"
verify_success: "{label} successful"
verify_failed: "{label} failed"
secret:
creation_label: Create a new secret based on the text entered
placeholder: Search for an existing secret or enter new content to create one
required_key_missing_label: The needed fields are missing, Please select and complete them
creation_modal:
title: Create secret
edit_modal:
title: Edit secret
list_modal:
title: Secrets
operations:
delete:
title: Delete secret
description: Are you sure you want to delete this secret? Please make sure that this secret is not being used anywhere, otherwise you need to reset it in a specific place
form:
fields:
description: Description
string_data: String Data
code:
fullscreen:
exit: Exit fullscreen
enter: Enter fullscreen to edit
common:
buttons:
activate: Activate
inactivate: Inactivate
select: Select
view_all: View all
verify: Verify
modify: Modify
access: Access
schedule_publish: Schedule publish
revoke: Revoke
disable: Disable
enable: Enable
continue: Continue
toast:
disable_success: Disabled successfully
enable_success: Enabled successfully
dialog:
titles:
login_expired: Login expired
descriptions:
login_expired: The current session has expired. Click Confirm to go to the login page. Please ensure that the current content is saved. You can click Cancel to manually copy any unsaved content.
status:
starting_up: Starting
text:
system_protection: System protection
uc_post:
creation_modal:
title: Create post
operations:
cancel_publish:
description: Are you sure you want to cancel publishing?
title: Cancel publish
delete:
title: Delete post
description: This action will move the post to the recycle bin, where it will be managed by the site administrator.
publish_modal:
title: Publish post
setting_modal:
title: Post settings
title: My posts
tool:
title: Tools
empty:
title: There are no tools available
message: There are currently no tools available, and system tools may be provided by plugins
post_snapshots:
operations:
revert:
button: Revert
title: Revert snapshot
description: Are you sure you want to restore this snapshot? This operation will create a new snapshot based on this one and publish it.
toast_success: Reverted successfully
delete:
title: Delete snapshot
description: Are you sure you want to delete this snapshot? This operation is irreversible.
cleanup:
button: Cleanup
title: Cleanup snapshots
description: Are you sure you want to delete all unused snapshots? Only published, base version, and draft versions will be retained.
toast_empty: There are no snapshots to be cleaned up
toast_success: Cleanup completed
status:
released: Released
draft: Draft
base: Base
title: Post snapshots
page_snapshots:
operations:
revert:
button: Revert
title: Revert snapshot
description: Are you sure you want to restore this snapshot? This operation will create a new snapshot based on this one and publish it.
toast_success: Reverted successfully
delete:
title: Delete snapshot
description: Are you sure you want to delete this snapshot? This operation is irreversible.
cleanup:
button: Cleanup
title: Cleanup snapshots
description: Are you sure you want to delete all unused snapshots? Only published, base version, and draft versions will be retained.
toast_empty: There are no snapshots to be cleaned up
toast_success: Cleanup completed
status:
released: Released
draft: Draft
base: Base
title: Page snapshots

View File

@ -0,0 +1 @@
{}

View File

@ -1075,8 +1075,8 @@ core:
title: Update profile
change_password:
title: Change password
grant_permission:
title: Grant permission
grant_permission:
title: Grant permission
profile:
title: Profile
fields:

View File

@ -23,10 +23,8 @@ core:
backup: Respaldo
operations:
logout:
button: Cerrar sesión
title: ¿Estás seguro de que deseas cerrar sesión?
profile:
button: Perfil
profile: {}
visit_homepage:
title: Visitar página de inicio
dashboard:
@ -74,15 +72,8 @@ core:
refresh_search_engine:
title: Actualizar Motor de Búsqueda
dialog_title: ¿Deseas actualizar el índice del motor de búsqueda?
dialog_content: >-
Esta operación recreará los índices del motor de búsqueda para
todas las publicaciones publicadas.
dialog_content: Esta operación recreará los índices del motor de búsqueda para todas las publicaciones publicadas.
success_message: Índice del motor de búsqueda actualizado exitosamente.
evict_page_cache:
title: Actualizar Caché de Página
dialog_title: ¿Deseas actualizar el caché de las páginas?
dialog_content: Esta operación borrará la caché de todas las páginas.
success_message: Caché de página actualizada exitosamente.
user_stats:
title: Usuarios
comment_stats:
@ -101,14 +92,10 @@ core:
operations:
delete:
title: ¿Estás seguro de que deseas eliminar esta publicación?
description: >-
Esta operación moverá la publicación a la papelera de reciclaje, y
podrá ser restaurada desde la papelera de reciclaje posteriormente.
description: Esta operación moverá la publicación a la papelera de reciclaje, y podrá ser restaurada desde la papelera de reciclaje posteriormente.
delete_in_batch:
title: ¿Estás seguro de que deseas eliminar las publicaciones seleccionadas?
description: >-
Esta operación moverá las publicaciones a la papelera de reciclaje, y
podrán ser restauradas desde la papelera de reciclaje posteriormente.
description: Esta operación moverá las publicaciones a la papelera de reciclaje, y podrán ser restauradas desde la papelera de reciclaje posteriormente.
filters:
status:
items:
@ -154,9 +141,7 @@ core:
label: Título
slug:
label: Slug
help: >-
Usualmente usado para generar el enlace permanente a las
publicaciones
help: Usualmente usado para generar el enlace permanente a las publicaciones
refresh_message: Regenerar slug basado en el título.
categories:
label: Categorías
@ -188,20 +173,14 @@ core:
title: ¿Estás seguro de que deseas eliminar permanentemente esta publicación?
description: Después de la eliminación, no será posible recuperarla.
delete_in_batch:
title: >-
¿Estás seguro de que deseas eliminar permanentemente las publicaciones
seleccionadas?
title: ¿Estás seguro de que deseas eliminar permanentemente las publicaciones seleccionadas?
description: Después de la eliminación, no será posible recuperarlas.
recovery:
title: ¿Quieres restaurar esta publicación?
description: >-
Esta operación restaurará la publicación a su estado antes de la
eliminación.
description: Esta operación restaurará la publicación a su estado antes de la eliminación.
recovery_in_batch:
title: ¿Estás seguro de que deseas restaurar las publicaciones seleccionadas?
description: >-
Esta operación restaurará las publicaciones a su estado antes de la
eliminación.
description: Esta operación restaurará las publicaciones a su estado antes de la eliminación.
post_editor:
title: Edición de publicación
untitled: Publicación sin título
@ -215,9 +194,7 @@ core:
operations:
delete:
title: ¿Estás seguro de que deseas eliminar esta etiqueta?
description: >-
Después de eliminar esta etiqueta, se eliminará la asociación con el
artículo correspondiente. Esta operación no se puede deshacer.
description: Después de eliminar esta etiqueta, se eliminará la asociación con el artículo correspondiente. Esta operación no se puede deshacer.
editing_modal:
titles:
update: Actualizar etiqueta de publicación
@ -230,9 +207,7 @@ core:
label: Nombre para mostrar
slug:
label: Slug
help: >-
Usualmente utilizado para generar el enlace permanente de las
etiquetas
help: Usualmente utilizado para generar el enlace permanente de las etiquetas
refresh_message: Regenerar slug basado en el nombre para mostrar.
color:
label: Color
@ -250,9 +225,7 @@ core:
operations:
delete:
title: ¿Estás seguro de que deseas eliminar esta categoría?
description: >-
Después de eliminar esta categoría, se eliminará la asociación con los
artículos correspondientes. Esta operación no se puede deshacer.
description: Después de eliminar esta categoría, se eliminará la asociación con los artículos correspondientes. Esta operación no se puede deshacer.
add_sub_category:
button: Agregar subcategoría
editing_modal:
@ -269,9 +242,7 @@ core:
label: Nombre para mostrar
slug:
label: Slug
help: >-
Usualmente utilizado para generar el enlace permanente de las
categorías
help: Usualmente utilizado para generar el enlace permanente de las categorías
refresh_message: Regenerar slug basado en el nombre para mostrar.
template:
label: Plantilla personalizada
@ -283,9 +254,7 @@ core:
help: Se requiere adaptación del tema para ser compatible
prevent_parent_post_cascade_query:
label: Evitar consulta en cascada de publicación principal
help: >-
Si se selecciona, las publicaciones de las subcategorías no se
agregarán a la categoría principal
help: Si se selecciona, las publicaciones de las subcategorías no se agregarán a la categoría principal
page:
title: Páginas
actions:
@ -296,14 +265,10 @@ core:
operations:
delete:
title: ¿Estás seguro de que deseas eliminar esta página?
description: >-
Esta operación moverá la página a la papelera de reciclaje, y podrá
ser restaurada desde la papelera de reciclaje posteriormente.
description: Esta operación moverá la página a la papelera de reciclaje, y podrá ser restaurada desde la papelera de reciclaje posteriormente.
delete_in_batch:
title: ¿Estás seguro de que deseas eliminar las páginas seleccionadas?
description: >-
Esta operación moverá las páginas a la papelera de reciclaje, y podrá
ser restaurada desde la papelera de reciclaje posteriormente.
description: Esta operación moverá las páginas a la papelera de reciclaje, y podrá ser restaurada desde la papelera de reciclaje posteriormente.
filters:
status:
items:
@ -339,9 +304,7 @@ core:
label: Título
slug:
label: Slug
help: >-
Usualmente utilizado para generar el enlace permanente de las
páginas
help: Usualmente utilizado para generar el enlace permanente de las páginas
refresh_message: Regenerar slug basado en el título.
auto_generate_excerpt:
label: Generar Extracto Automáticamente
@ -369,20 +332,14 @@ core:
title: ¿Estás seguro de que deseas eliminar permanentemente esta página?
description: Después de la eliminación, no será posible recuperarla.
delete_in_batch:
title: >-
¿Estás seguro de que deseas eliminar permanentemente las páginas
seleccionadas?
title: ¿Estás seguro de que deseas eliminar permanentemente las páginas seleccionadas?
description: Después de la eliminación, no será posible recuperarlas.
recovery:
title: ¿Quieres restaurar esta página?
description: >-
Esta operación restaurará la página a su estado antes de la
eliminación.
description: Esta operación restaurará la página a su estado antes de la eliminación.
recovery_in_batch:
title: ¿Estás seguro de que deseas restaurar las páginas seleccionadas?
description: >-
Esta operación restaurará las páginas a su estado antes de la
eliminación.
description: Esta operación restaurará las páginas a su estado antes de la eliminación.
page_editor:
title: Edición de Página
untitled: Página Sin Título
@ -398,24 +355,16 @@ core:
operations:
delete_comment:
title: ¿Estás seguro de que deseas eliminar este comentario?
description: >-
Todas las respuestas bajo los comentarios se eliminarán al mismo
tiempo, y esta operación no se puede deshacer.
description: Todas las respuestas bajo los comentarios se eliminarán al mismo tiempo, y esta operación no se puede deshacer.
delete_comment_in_batch:
title: ¿Estás seguro de que deseas eliminar los comentarios seleccionados?
description: >-
Todas las respuestas bajo los comentarios se eliminarán al mismo
tiempo, y esta operación no se puede deshacer.
description: Todas las respuestas bajo los comentarios se eliminarán al mismo tiempo, y esta operación no se puede deshacer.
approve_comment_in_batch:
button: Aprobar
title: >-
¿Estás seguro de que deseas aprobar los comentarios seleccionados para
su revisión?
title: ¿Estás seguro de que deseas aprobar los comentarios seleccionados para su revisión?
approve_applies_in_batch:
button: Aprobar todas las respuestas
title: >-
¿Estás seguro de que deseas aprobar todas las respuestas a este
comentario para su revisión?
title: ¿Estás seguro de que deseas aprobar todas las respuestas a este comentario para su revisión?
delete_reply:
title: ¿Estás seguro de que deseas eliminar esta respuesta?
approve_reply:
@ -464,9 +413,7 @@ core:
storage_policies: Políticas de Almacenamiento
empty:
title: No hay adjuntos en el grupo actual.
message: >-
El grupo actual no tiene adjuntos, puedes intentar actualizar o cargar
adjuntos.
message: El grupo actual no tiene adjuntos, puedes intentar actualizar o cargar adjuntos.
actions:
upload: Cargar Adjunto
operations:
@ -536,26 +483,18 @@ core:
delete:
button: Y mover adjunto a sin grupo
title: ¿Estás seguro de que deseas eliminar este grupo?
description: >-
El grupo se eliminará, y los adjuntos bajo el grupo se moverán a sin
grupo. Esta operación no se puede deshacer.
description: El grupo se eliminará, y los adjuntos bajo el grupo se moverán a sin grupo. Esta operación no se puede deshacer.
toast_success: Eliminación exitosa, {total} adjuntos se han movido a sin grupo
delete_with_attachments:
button: También eliminar adjuntos
title: ¿Estás seguro de que deseas eliminar este grupo?
description: >-
Al eliminar el grupo y todos los adjuntos dentro de él, esta acción
no se puede deshacer.
toast_success: >-
Eliminación exitosa, {total} adjuntos se han eliminado
simultáneamente
description: Al eliminar el grupo y todos los adjuntos dentro de él, esta acción no se puede deshacer.
toast_success: Eliminación exitosa, {total} adjuntos se han eliminado simultáneamente
policies_modal:
title: Políticas de Almacenamiento
empty:
title: Actualmente no hay estrategias de almacenamiento disponibles.
message: >-
No hay políticas de almacenamiento disponibles en este momento. Puedes
intentar actualizar o crear una nueva política.
message: No hay políticas de almacenamiento disponibles en este momento. Puedes intentar actualizar o crear una nueva política.
operations:
delete:
title: ¿Estás seguro de que deseas eliminar esta política?
@ -579,9 +518,7 @@ core:
label: "Seleccionar política de almacenamiento:"
empty:
title: Sin política de almacenamiento
description: >-
Antes de cargar, es necesario crear una nueva política de
almacenamiento.
description: Antes de cargar, es necesario crear una nueva política de almacenamiento.
not_select: Por favor, selecciona una política de almacenamiento primero
select_modal:
title: Seleccionar adjunto
@ -610,61 +547,29 @@ core:
title: ¿Estás seguro de activar el tema actual?
toast_success: Tema activado exitosamente
reset:
title: >-
¿Estás seguro de que deseas restablecer todas las configuraciones del
tema?
description: >-
Esta operación eliminará la configuración guardada y la restablecerá a
los ajustes predeterminados.
title: ¿Estás seguro de que deseas restablecer todas las configuraciones del tema?
description: Esta operación eliminará la configuración guardada y la restablecerá a los ajustes predeterminados.
toast_success: Configuración restablecida exitosamente
reload:
button: Recargar
title: >-
¿Estás seguro de que deseas recargar todas las configuraciones del
tema?
description: >-
Esta operación solo recargará la configuración del tema y la
definición del formulario de ajustes, y no eliminará ninguna
configuración guardada.
title: ¿Estás seguro de que deseas recargar todas las configuraciones del tema?
description: Esta operación solo recargará la configuración del tema y la definición del formulario de ajustes, y no eliminará ninguna configuración guardada.
toast_success: Recarga de configuración exitosa
uninstall:
title: ¿Estás seguro de que deseas desinstalar este tema?
uninstall_and_delete_config:
button: Desinstalar y eliminar configuración
title: >-
¿Estás seguro de que deseas desinstalar este tema y su configuración
correspondiente?
title: ¿Estás seguro de que deseas desinstalar este tema y su configuración correspondiente?
remote_download:
title: Se ha detectado una dirección de descarga remota, ¿deseas descargar?
description: >-
Por favor, verifica cuidadosamente si esta dirección es confiable:
{url}
upload_modal:
titles:
install: Instalar tema
upgrade: Actualizar tema ({display_name})
operations:
existed_during_installation:
title: El tema ya existe.
description: El tema instalado actualmente ya existe, deseas actualizarlo?
tabs:
local: Local
remote:
title: Remoto
fields:
url: URL Remota
description: "Por favor, verifica cuidadosamente si esta dirección es confiable: {url}"
list_modal:
titles:
installed_themes: Temas Instalados
not_installed_themes: Temas no Instalados
tabs:
installed: Instalados
not_installed: No Instalados
empty:
title: No hay temas instalados actualmente.
message: >-
No hay temas instalados actualmente, puedes intentar actualizar o
instalar un nuevo tema.
message: No hay temas instalados actualmente, puedes intentar actualizar o instalar un nuevo tema.
not_installed_empty:
title: No hay temas actualmente no instalados.
preview_model:
@ -676,7 +581,6 @@ core:
detail:
fields:
author: Autor
website: Sitio Web
repo: Repositorio Fuente
version: Versión
requires: Requiere
@ -700,14 +604,10 @@ core:
toast_success: Configuración exitosa
delete_menu:
title: ¿Estás seguro de que deseas eliminar este menú?
description: >-
Todos los elementos de menú de este menú se eliminarán al mismo tiempo
y esta operación no se puede deshacer.
description: Todos los elementos de menú de este menú se eliminarán al mismo tiempo y esta operación no se puede deshacer.
delete_menu_item:
title: ¿Estás seguro de que deseas eliminar este elemento de menú?
description: >-
Todos los subelementos de menú se eliminarán al mismo tiempo y no se
pueden restaurar después de la eliminación.
description: Todos los subelementos de menú se eliminarán al mismo tiempo y no se pueden restaurar después de la eliminación.
add_sub_menu_item:
button: Agregar subelemento de menú
list:
@ -758,32 +658,21 @@ core:
detail: Detail
empty:
title: There are no installed plugins currently.
message: >-
There are no installed plugins currently, you can try refreshing or
installing new plugins.
message: There are no installed plugins currently, you can try refreshing or installing new plugins.
actions:
install: Install Plugin
operations:
reset:
title: Are you sure you want to reset all configurations of the plugin?
description: >-
This operation will delete the saved configuration and reset it to
default settings.
description: This operation will delete the saved configuration and reset it to default settings.
toast_success: Reset configuration successfully
uninstall:
title: Are you sure you want to uninstall this plugin?
uninstall_and_delete_config:
title: >-
Are you sure you want to uninstall this plugin and its corresponding
configuration?
title: Are you sure you want to uninstall this plugin and its corresponding configuration?
uninstall_when_enabled:
confirm_text: Stop running and uninstall
description: >-
The current plugin is still in the enabled state and will be
uninstalled after it stops running. This operation cannot be undone.
change_status:
active_title: Are you sure you want to active this plugin?
inactive_title: Are you sure you want to inactive this plugin?
description: The current plugin is still in the enabled state and will be uninstalled after it stops running. This operation cannot be undone.
remote_download:
title: Remote download address detected, do you want to download?
description: "Please carefully verify whether this address can be trusted: {url}"
@ -796,9 +685,6 @@ core:
items:
create_time_desc: Latest Installed
create_time_asc: Earliest Installed
list:
actions:
uninstall_and_delete_config: Uninstall and delete config
upload_modal:
titles:
install: Install plugin
@ -815,15 +701,12 @@ core:
description: Would you like to activate the currently installed plugin?
existed_during_installation:
title: The plugin already exists.
description: >-
The currently installed plugin already exists, do you want to
upgrade?
description: The currently installed plugin already exists, do you want to upgrade?
detail:
title: Plugin detail
header:
title: Plugin information
fields:
display_name: Display Name
description: Description
version: Version
requires: Requires
@ -846,9 +729,7 @@ core:
identity_authentication: Autenticación de Identidad
empty:
title: Actualmente no hay usuarios que cumplan con los criterios de filtrado.
message: >-
No hay usuarios que coincidan con los criterios de filtrado en este
momento. Puedes intentar actualizar o crear un nuevo usuario.
message: No hay usuarios que coincidan con los criterios de filtrado en este momento. Puedes intentar actualizar o crear un nuevo usuario.
operations:
delete:
title: ¿Estás seguro de que deseas eliminar a este usuario?
@ -911,14 +792,6 @@ core:
title: Actualizar perfil
change_password:
title: Cambiar contraseña
operations:
bind:
button: Vincular
unbind:
button: Desvincular
title: >-
¿Estás seguro de que deseas desvincular el método de inicio de
sesión para {display_name}?
fields:
display_name: Nombre para mostrar
username: Nombre de usuario
@ -927,21 +800,6 @@ core:
bio: Biografía
creation_time: Fecha de creación
identity_authentication: Autenticación de identidad
avatar:
title: Avatar
toast_upload_failed: No se pudo cargar el avatar
toast_remove_failed: No se pudo eliminar el avatar
cropper_modal:
title: Recortar Avatar
remove:
title: ¿Estás seguro de que deseas eliminar el avatar?
tooltips:
upload: Cargar
zoom_in: Acercar
zoom_out: Alejar
flip_horizontal: Voltear Horizontalmente
flip_vertical: Voltear Verticalmente
reset: Restablecer
role:
title: Roles
common:
@ -955,9 +813,7 @@ core:
operations:
delete:
title: ¿Estás seguro de que deseas eliminar este rol?
description: >-
Una vez eliminado el rol, se eliminarán las asignaciones de rol de los
usuarios asociados y esta operación no se puede deshacer.
description: Una vez eliminado el rol, se eliminarán las asignaciones de rol de los usuarios asociados y esta operación no se puede deshacer.
create_based_on_this_role:
button: Crear basado en este rol
detail:
@ -974,9 +830,7 @@ core:
creation_time: Fecha de creación
permissions_detail:
system_reserved_alert:
description: >-
El rol reservado del sistema no admite modificaciones. Se recomienda
crear un nuevo rol basado en este.
description: El rol reservado del sistema no admite modificaciones. Se recomienda crear un nuevo rol basado en este.
editing_modal:
titles:
create: Crear rol
@ -993,17 +847,11 @@ core:
setting: Configuración
operations:
enable:
title: >-
¿Estás seguro de que deseas habilitar este método de autenticación de
identidad?
title: ¿Estás seguro de que deseas habilitar este método de autenticación de identidad?
disable:
title: >-
¿Estás seguro de que deseas deshabilitar este método de autenticación
de identidad?
title: ¿Estás seguro de que deseas deshabilitar este método de autenticación de identidad?
disable_privileged:
tooltip: >-
El método de autenticación reservado por el sistema no se puede
deshabilitar
tooltip: El método de autenticación reservado por el sistema no se puede deshabilitar
detail:
title: Detalle de la autenticación de identidad
fields:
@ -1044,11 +892,7 @@ core:
database: "Base de datos: {database}"
os: "Sistema operativo: {os}"
alert:
external_url_invalid: >-
La URL de acceso externo detectada no coincide con la URL de acceso
actual, lo que podría causar que algunos enlaces no se redirijan
correctamente. Por favor, verifica la configuración de la URL de acceso
externo.
external_url_invalid: La URL de acceso externo detectada no coincide con la URL de acceso actual, lo que podría causar que algunos enlaces no se redirijan correctamente. Por favor, verifica la configuración de la URL de acceso externo.
backup:
title: Copia de Seguridad y Restauración
tabs:
@ -1061,19 +905,14 @@ core:
create:
button: Crear copia de seguridad
title: Crear copia de seguridad
description: >-
¿Estás seguro de que deseas crear una copia de seguridad? Esta
operación puede tomar un tiempo.
description: ¿Estás seguro de que deseas crear una copia de seguridad? Esta operación puede tomar un tiempo.
toast_success: Solicitud de creación de copia de seguridad realizada
delete:
title: Eliminar copia de seguridad
description: ¿Estás seguro de que deseas eliminar esta copia de seguridad?
restore:
title: Restauración exitosa
description: >-
Después de una restauración exitosa, necesitas reiniciar Halo para
cargar los recursos del sistema normalmente. Después de hacer clic en
OK, reiniciaremos automáticamente Halo.
description: Después de una restauración exitosa, necesitas reiniciar Halo para cargar los recursos del sistema normalmente. Después de hacer clic en OK, reiniciaremos automáticamente Halo.
restart:
toast_success: Solicitud de reinicio realizada
list:
@ -1086,15 +925,9 @@ core:
expiresAt: Expira el {expiresAt}
restore:
tips:
first: >-
1. El proceso de restauración puede tomar un tiempo, por favor no
actualices la página durante este período.
second: >-
2. Durante el proceso de restauración, aunque los datos existentes no
se eliminarán, si hay un conflicto, los datos se sobrescribirán.
third: >-
3. Después de completar la restauración, necesitas reiniciar Halo para
cargar los recursos del sistema normalmente.
first: 1. El proceso de restauración puede tomar un tiempo, por favor no actualices la página durante este período.
second: 2. Durante el proceso de restauración, aunque los datos existentes no se eliminarán, si hay un conflicto, los datos se sobrescribirán.
third: 3. Después de completar la restauración, necesitas reiniciar Halo para cargar los recursos del sistema normalmente.
complete: Restauración completada, esperando reinicio...
start: Iniciar Restauración
exception:
@ -1211,9 +1044,7 @@ core:
size_label: elementos por página
total_label: Total de {total} elementos
app_download_alert:
description: >-
Los temas y complementos para Halo se pueden descargar en las siguientes
direcciones:
description: "Los temas y complementos para Halo se pueden descargar en las siguientes direcciones:"
sources:
app_store: "Tienda de aplicaciones oficial: {url}"
github: "GitHub: {url}"
@ -1253,7 +1084,6 @@ core:
preview: Vista previa
recovery: Recuperar
delete_permanently: Borrar permanentemente
active: Activar
download: Descargar
copy: Copiar
upload: Subir
@ -1298,9 +1128,7 @@ core:
warning: Advertencia
descriptions:
cannot_be_recovered: Esta operación es irreversible.
editor_not_found: >-
No se encontró ningún editor que coincida con el formato {raw_type}.
Por favor verifica si el complemento del editor ha sido instalado.
editor_not_found: No se encontró ningún editor que coincida con el formato {raw_type}. Por favor verifica si el complemento del editor ha sido instalado.
filters:
results:
keyword: "Palabra clave: {keyword}"

View File

@ -223,6 +223,14 @@ core:
allow_comment_value: 選擇是否允許評論
owner_group: 作者
owner_value: 設置作者
tag:
filters:
sort:
items:
create_time_desc: 較近建立
create_time_asc: 較早建立
display_name_desc: 標籤名稱降序
display_name_asc: 標籤名稱升序
deleted_post:
title: 文章回收站
empty:
@ -1246,6 +1254,7 @@ core:
list:
fields:
last_accessed_time: 上次訪問:{time}
current: 目前設備
detail_modal:
title: 登入設備詳情
fields:
@ -1547,9 +1556,7 @@ core:
aspect_ratio_portrait: 裁剪為縱向 (9:16)
h2_warning_alert:
title: 警告:正在使用 H2 資料庫
description: >-
H2 資料庫僅適用於開發環境和測試環境不建議在生產環境中使用H2
非常容易因為操作不當而導致資料檔案損壞。如果必須要使用,請按時進行資料備份。
description: H2 資料庫僅適用於開發環境和測試環境不建議在生產環境中使用H2 非常容易因為操作不當而導致資料檔案損壞。如果必須要使用,請按時進行資料備份。
composables:
content_cache:
toast_recovered: 已從緩存中恢復未保存的內容