mirror of
https://github.com/OliveTin/OliveTin
synced 2026-09-26 03:35:44 +00:00
perf: modernize frontend tooling and loading (#1121)
This commit is contained in:
@@ -48,8 +48,8 @@ lang-generate:
|
||||
$(MAKE) -wC lang
|
||||
|
||||
generated-check: proto lang-generate
|
||||
git diff --exit-code -- service/gen frontend/resources/scripts/gen lang/combined_output.json
|
||||
@untracked="$$(git ls-files --others --exclude-standard -- service/gen frontend/resources/scripts/gen lang/combined_output.json)"; \
|
||||
git diff --exit-code -- service/gen frontend/resources/scripts/gen lang/generated
|
||||
@untracked="$$(git ls-files --others --exclude-standard -- service/gen frontend/resources/scripts/gen lang/generated)"; \
|
||||
test -z "$$untracked" || { printf 'Untracked generated files:\n%s\n' "$$untracked"; exit 1; }
|
||||
|
||||
dist:
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true
|
||||
},
|
||||
"extends": [
|
||||
"plugin:vue/recommended",
|
||||
"standard"
|
||||
],
|
||||
"parser": "vue-eslint-parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 12,
|
||||
"sourceType": "module",
|
||||
"parser": {
|
||||
"js": "espree"
|
||||
}
|
||||
},
|
||||
"rules": {
|
||||
"vue/multi-word-component-names": "off",
|
||||
"vue/require-default-prop": "off",
|
||||
"vue/no-v-html": "warn",
|
||||
"no-tabs": "off",
|
||||
"no-mixed-spaces-and-tabs": "off"
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -18,7 +18,7 @@ deps:
|
||||
npm ci
|
||||
|
||||
build:
|
||||
npx vite build
|
||||
npm run build
|
||||
|
||||
dist: deps clean build
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import neostandard from 'neostandard'
|
||||
import pluginVue from 'eslint-plugin-vue'
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: [
|
||||
'dist/**',
|
||||
'node_modules/**',
|
||||
'resources/scripts/gen/**'
|
||||
]
|
||||
},
|
||||
...neostandard({
|
||||
env: ['browser'],
|
||||
noJsx: true
|
||||
}),
|
||||
...pluginVue.configs['flat/recommended'],
|
||||
{
|
||||
files: ['**/*.{js,mjs,vue}'],
|
||||
languageOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module'
|
||||
},
|
||||
rules: {
|
||||
'@stylistic/no-tabs': 'off',
|
||||
'@stylistic/no-mixed-spaces-and-tabs': 'off',
|
||||
'vue/multi-word-component-names': 'off',
|
||||
'vue/require-default-prop': 'off',
|
||||
'vue/no-v-html': 'warn'
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -8,8 +8,6 @@
|
||||
|
||||
<title>OliveTin</title>
|
||||
|
||||
<link rel = "stylesheet" href = "node_modules/@xterm/xterm/css/xterm.css" />
|
||||
|
||||
<link rel = "shortcut icon" type = "image/png" href = "OliveTinLogo.png" />
|
||||
|
||||
<link rel = "apple-touch-icon" sizes="57x57" href="OliveTinLogo-57px.png" />
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import '@xterm/xterm/css/xterm.css'
|
||||
import { Terminal } from '@xterm/xterm'
|
||||
import { FitAddon } from '@xterm/addon-fit'
|
||||
import { WebLinksAddon } from '@xterm/addon-web-links'
|
||||
|
||||
+9
-37
@@ -18,49 +18,21 @@ import router from './resources/vue/router.js'
|
||||
import App from './resources/vue/App.vue'
|
||||
|
||||
import { initWebsocket } from './js/websocket.js'
|
||||
import { getSelectedLocale, loadInitialMessages } from './resources/vue/i18n.js'
|
||||
import { applyThemeStyles, getStoredThemePreference } from './resources/vue/utils/themeLoader.js'
|
||||
import combinedTranslations from '../lang/combined_output.json'
|
||||
|
||||
function getSelectedLanguage () {
|
||||
const storedLanguage = localStorage.getItem('olivetin-language')
|
||||
|
||||
if (storedLanguage && storedLanguage !== 'auto') {
|
||||
return storedLanguage
|
||||
}
|
||||
|
||||
if (storedLanguage === 'auto') {
|
||||
localStorage.removeItem('olivetin-language')
|
||||
}
|
||||
|
||||
if (navigator.languages && navigator.languages.length > 0) {
|
||||
const available = Object.keys(combinedTranslations.messages || {})
|
||||
|
||||
for (const candidate of navigator.languages) {
|
||||
const lowerCandidate = candidate.toLowerCase()
|
||||
const exact = available.find(locale => locale.toLowerCase() === lowerCandidate)
|
||||
|
||||
if (exact) {
|
||||
return exact
|
||||
}
|
||||
|
||||
const prefix = available.find(locale => locale.toLowerCase().startsWith(lowerCandidate.split('-')[0] + '-'))
|
||||
|
||||
if (prefix) {
|
||||
return prefix
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 'en'
|
||||
}
|
||||
|
||||
async function initClient () {
|
||||
const transport = createConnectTransport({
|
||||
baseUrl: window.location.protocol + '//' + window.location.host + '/api/'
|
||||
})
|
||||
const locale = getSelectedLocale()
|
||||
|
||||
window.client = createClient(OliveTinApiService, transport)
|
||||
window.initResponse = await window.client.init({})
|
||||
const [initResponse, messages] = await Promise.all([
|
||||
window.client.init({}),
|
||||
loadInitialMessages(locale)
|
||||
])
|
||||
window.initResponse = initResponse
|
||||
|
||||
if (window.initResponse.enableCustomJs) {
|
||||
const script = document.createElement('script')
|
||||
@@ -72,9 +44,9 @@ async function initClient () {
|
||||
|
||||
const i18nSettings = createI18n({
|
||||
legacy: false,
|
||||
locale: getSelectedLanguage(),
|
||||
locale,
|
||||
fallbackLocale: 'en',
|
||||
messages: combinedTranslations.messages,
|
||||
messages,
|
||||
postTranslation: (translated) => {
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
|
||||
|
||||
Generated
+1320
-992
File diff suppressed because it is too large
Load Diff
+9
-15
@@ -3,41 +3,35 @@
|
||||
"version": "1.0.0",
|
||||
"description": "The WebUI for OliveTin",
|
||||
"repository": "https://github.com/OliveTin/OliveTin",
|
||||
"source": "index.html",
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^6.0.8",
|
||||
"eslint": "^9.39.5",
|
||||
"eslint-plugin-vue": "^10.11.0",
|
||||
"process": "^0.11.10",
|
||||
"neostandard": "^0.13.0",
|
||||
"stylelint": "^17.15.0",
|
||||
"stylelint-config-standard": "^40.0.0"
|
||||
"stylelint-config-standard": "^40.0.0",
|
||||
"vite": "^8.2.2"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
"dev": "vite",
|
||||
"lint": "eslint main.js js/* resources/vue vite.config.mjs && stylelint \"style.css\" \"themes/**/*.css\"",
|
||||
"lint:fix": "eslint --fix main.js js/* resources/vue vite.config.mjs && stylelint --fix \"style.css\" \"themes/**/*.css\"",
|
||||
"preview": "vite preview",
|
||||
"test": "node --test resources/vue/components/*.test.mjs resources/vue/utils/*.test.mjs resources/vue/stores/*.test.mjs"
|
||||
},
|
||||
"author": "",
|
||||
"parcelIgnore": [
|
||||
"theme.css",
|
||||
"OliveTinLogo.png",
|
||||
"OliveTinLogo-57px.png",
|
||||
"OliveTinLogo-120px.png",
|
||||
"OliveTinLogo-180px.png"
|
||||
],
|
||||
"license": "AGPL-3.0-only",
|
||||
"dependencies": {
|
||||
"@connectrpc/connect": "^2.2.0",
|
||||
"@connectrpc/connect-web": "^2.2.0",
|
||||
"@hugeicons/core-free-icons": "^4.3.2",
|
||||
"@hugeicons/vue": "^1.0.8",
|
||||
"@vitejs/plugin-vue": "^6.0.8",
|
||||
"@xterm/addon-fit": "^0.11.0",
|
||||
"@xterm/addon-web-links": "^0.12.0",
|
||||
"@xterm/xterm": "^6.0.0",
|
||||
"iconify-icon": "^3.0.2",
|
||||
"picocrank": "^1.29.0",
|
||||
"standard": "^17.1.2",
|
||||
"unplugin-vue-components": "^32.1.0",
|
||||
"vite": "^8.2.2",
|
||||
"vue": "^3.5.42",
|
||||
"vue-i18n": "^11.4.10",
|
||||
"vue-router": "^5.3.1"
|
||||
|
||||
@@ -273,9 +273,9 @@ function constructFromJson (json) {
|
||||
if (bindingId.value) {
|
||||
rateLimits[bindingId.value] = rateLimitExpires.value
|
||||
setBindingExecutionState(
|
||||
bindingId.value,
|
||||
!!json.hasRunningInstance,
|
||||
!!json.hasQueuedInstance
|
||||
bindingId.value,
|
||||
!!json.hasRunningInstance,
|
||||
!!json.hasQueuedInstance
|
||||
)
|
||||
}
|
||||
updateRateLimitStatus()
|
||||
@@ -310,8 +310,8 @@ function updateRateLimitStatus () {
|
||||
isRateLimited.value = false
|
||||
rateLimitMessage.value = ''
|
||||
if (rateLimitInterval.value) {
|
||||
clearInterval(rateLimitInterval.value)
|
||||
rateLimitInterval.value = null
|
||||
clearInterval(rateLimitInterval.value)
|
||||
rateLimitInterval.value = null
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -325,8 +325,8 @@ function updateRateLimitStatus () {
|
||||
rateLimitMessage.value = ''
|
||||
rateLimitExpires.value = 0
|
||||
if (rateLimitInterval.value) {
|
||||
clearInterval(rateLimitInterval.value)
|
||||
rateLimitInterval.value = null
|
||||
clearInterval(rateLimitInterval.value)
|
||||
rateLimitInterval.value = null
|
||||
}
|
||||
} else {
|
||||
// Still rate limited
|
||||
@@ -336,9 +336,9 @@ function updateRateLimitStatus () {
|
||||
|
||||
// Set up interval to update every second
|
||||
if (!rateLimitInterval.value) {
|
||||
rateLimitInterval.value = setInterval(() => {
|
||||
rateLimitInterval.value = setInterval(() => {
|
||||
updateRateLimitStatus()
|
||||
}, 1000)
|
||||
}, 1000)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -360,12 +360,12 @@ async function handleClick () {
|
||||
const bindingId = props.actionData.bindingId
|
||||
const prefilled = props.prefilledArguments || {}
|
||||
if (Object.keys(prefilled).length > 0) {
|
||||
router.push({
|
||||
router.push({
|
||||
path: `/actionBinding/${bindingId}/argumentForm`,
|
||||
state: { prefilledArguments: prefilled }
|
||||
})
|
||||
})
|
||||
} else {
|
||||
router.push(`/actionBinding/${bindingId}/argumentForm`)
|
||||
router.push(`/actionBinding/${bindingId}/argumentForm`)
|
||||
}
|
||||
} else {
|
||||
await startAction()
|
||||
@@ -439,7 +439,7 @@ async function startAction (actionArgs) {
|
||||
stopButtonResultWatch = watch(
|
||||
() => buttonResults[startActionArgs.uniqueTrackingId],
|
||||
(newResult, oldResult) => {
|
||||
onLogEntryChanged(newResult)
|
||||
onLogEntryChanged(newResult)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -450,11 +450,11 @@ async function startAction (actionArgs) {
|
||||
const trackingId = response.executionTrackingId || startActionArgs.uniqueTrackingId
|
||||
|
||||
if (popupOnStart.value && popupOnStart.value.includes('execution-dialog')) {
|
||||
router.push(`/logs/${trackingId}`)
|
||||
router.push(`/logs/${trackingId}`)
|
||||
}
|
||||
|
||||
if (!connectionState.connected) {
|
||||
await pollExecutionUntilDone(trackingId)
|
||||
await pollExecutionUntilDone(trackingId)
|
||||
}
|
||||
} catch (err) {
|
||||
stopWatchingButtonResult()
|
||||
@@ -554,14 +554,14 @@ onMounted(() => {
|
||||
watch(
|
||||
rateLimits,
|
||||
() => {
|
||||
const id = bindingId.value
|
||||
if (id && rateLimits[id] !== undefined) {
|
||||
const id = bindingId.value
|
||||
if (id && rateLimits[id] !== undefined) {
|
||||
const newExpires = rateLimits[id]
|
||||
if (newExpires !== rateLimitExpires.value) {
|
||||
rateLimitExpires.value = newExpires
|
||||
updateRateLimitStatus()
|
||||
rateLimitExpires.value = newExpires
|
||||
updateRateLimitStatus()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
@@ -570,9 +570,9 @@ onMounted(() => {
|
||||
watch(
|
||||
() => pendingBindingFlash[bindingId.value],
|
||||
(pending) => {
|
||||
if (pending) {
|
||||
if (pending) {
|
||||
consumeAndFlashPendingResult()
|
||||
}
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
@@ -593,7 +593,7 @@ watch(
|
||||
(newData) => {
|
||||
updateFromJson(newData)
|
||||
if (newData?.icon !== undefined) {
|
||||
glyph.value = newData.icon ?? ''
|
||||
glyph.value = newData.icon ?? ''
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
|
||||
@@ -188,7 +188,7 @@ import { connectEventStreamIfNeeded } from '../../js/websocket.js'
|
||||
import { DashboardSquare01Icon } from '@hugeicons/core-free-icons'
|
||||
import logoUrl from '../../OliveTinLogo.png'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import combinedTranslations from '../../../lang/combined_output.json'
|
||||
import { ensureLocaleMessages, resolveBrowserLocale } from './i18n.js'
|
||||
import { searchIndexItems, clearSearchIndex, indexSystemNavigation, indexSearchHints, indexRootDashboardEntries } from './stores/searchIndex.js'
|
||||
import { applyThemeStyles } from './utils/themeLoader.js'
|
||||
const { t } = useI18n()
|
||||
@@ -220,6 +220,7 @@ const browserLanguages = ref([])
|
||||
const initialLanguagePreference = typeof window !== 'undefined' ? localStorage.getItem('olivetin-language') : null
|
||||
const languagePreference = ref(initialLanguagePreference || 'auto')
|
||||
const selectedLanguage = ref(languagePreference.value)
|
||||
let latestLanguageChange = 0
|
||||
|
||||
const themeDialog = ref(null)
|
||||
const availableThemes = ref([])
|
||||
@@ -272,30 +273,6 @@ const headerLoginRoute = computed(() => {
|
||||
return showLoginLink.value ? { name: 'Login' } : null
|
||||
})
|
||||
|
||||
function normalizeBrowserLanguage () {
|
||||
const available = Object.keys(combinedTranslations.messages || {})
|
||||
|
||||
if (navigator.languages && navigator.languages.length > 0) {
|
||||
for (const candidate of navigator.languages) {
|
||||
const lowerCandidate = candidate.toLowerCase()
|
||||
|
||||
// Try exact match (case-insensitive)
|
||||
const exact = available.find(locale => locale.toLowerCase() === lowerCandidate)
|
||||
if (exact) {
|
||||
return exact
|
||||
}
|
||||
|
||||
// Try prefix match (e.g., "zh-CN" -> "zh-Hans-CN")
|
||||
const prefix = available.find(locale => locale.toLowerCase().startsWith(lowerCandidate.split('-')[0] + '-'))
|
||||
if (prefix) {
|
||||
return prefix
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 'en'
|
||||
}
|
||||
|
||||
function toggleSidebar () {
|
||||
if (sidebar.value && showNavigation.value) {
|
||||
sidebar.value.toggle()
|
||||
@@ -518,19 +495,29 @@ function closeLanguageDialog () {
|
||||
}
|
||||
}
|
||||
|
||||
function changeLanguage () {
|
||||
async function changeLanguage () {
|
||||
if (!window.i18n || !selectedLanguage.value) {
|
||||
return
|
||||
}
|
||||
|
||||
if (selectedLanguage.value === 'auto') {
|
||||
const languageChange = ++latestLanguageChange
|
||||
const requestedPreference = selectedLanguage.value
|
||||
const requestedLocale = requestedPreference === 'auto' ? resolveBrowserLocale() : requestedPreference
|
||||
|
||||
await ensureLocaleMessages(window.i18n, requestedLocale)
|
||||
|
||||
if (languageChange !== latestLanguageChange) {
|
||||
return
|
||||
}
|
||||
|
||||
window.i18n.locale.value = requestedLocale
|
||||
|
||||
if (requestedPreference === 'auto') {
|
||||
localStorage.removeItem('olivetin-language')
|
||||
languagePreference.value = 'auto'
|
||||
window.i18n.locale.value = normalizeBrowserLanguage()
|
||||
} else {
|
||||
window.i18n.locale.value = selectedLanguage.value
|
||||
localStorage.setItem('olivetin-language', selectedLanguage.value)
|
||||
languagePreference.value = selectedLanguage.value
|
||||
localStorage.setItem('olivetin-language', requestedPreference)
|
||||
languagePreference.value = requestedPreference
|
||||
}
|
||||
|
||||
// Update navigation with new translations
|
||||
|
||||
@@ -1,38 +1,38 @@
|
||||
const fallbackNamedHtmlEntities = {
|
||||
amp: '&',
|
||||
apos: "'",
|
||||
darr: '\u2193',
|
||||
gt: '>',
|
||||
laquo: '\u00ab',
|
||||
larr: '\u2190',
|
||||
nbsp: '\u00a0',
|
||||
quot: '"',
|
||||
raquo: '\u00bb',
|
||||
rarr: '\u2192',
|
||||
uarr: '\u2191',
|
||||
amp: '&',
|
||||
apos: "'",
|
||||
darr: '\u2193',
|
||||
gt: '>',
|
||||
laquo: '\u00ab',
|
||||
larr: '\u2190',
|
||||
nbsp: '\u00a0',
|
||||
quot: '"',
|
||||
raquo: '\u00bb',
|
||||
rarr: '\u2192',
|
||||
uarr: '\u2191',
|
||||
}
|
||||
|
||||
export function decodeHtmlEntities(text) {
|
||||
if (typeof document !== 'undefined') {
|
||||
const textarea = document.createElement('textarea')
|
||||
textarea.innerHTML = text
|
||||
export function decodeHtmlEntities (text) {
|
||||
if (typeof document !== 'undefined') {
|
||||
const textarea = document.createElement('textarea')
|
||||
textarea.innerHTML = text
|
||||
|
||||
return textarea.value
|
||||
}
|
||||
return textarea.value
|
||||
}
|
||||
|
||||
return text.replace(/&#x([0-9a-fA-F]+);?/g, (_, hex) => {
|
||||
const codePoint = Number.parseInt(hex, 16)
|
||||
return Number.isFinite(codePoint) ? String.fromCodePoint(codePoint) : ''
|
||||
}).replace(/&#(\d+);?/g, (_, decimal) => {
|
||||
const codePoint = Number.parseInt(decimal, 10)
|
||||
return Number.isFinite(codePoint) ? String.fromCodePoint(codePoint) : ''
|
||||
}).replace(/&([a-zA-Z][a-zA-Z0-9]+);?/g, (entity, name) => {
|
||||
return fallbackNamedHtmlEntities[name] ?? entity
|
||||
})
|
||||
return text.replace(/&#x([0-9a-fA-F]+);?/g, (_, hex) => {
|
||||
const codePoint = Number.parseInt(hex, 16)
|
||||
return Number.isFinite(codePoint) ? String.fromCodePoint(codePoint) : ''
|
||||
}).replace(/&#(\d+);?/g, (_, decimal) => {
|
||||
const codePoint = Number.parseInt(decimal, 10)
|
||||
return Number.isFinite(codePoint) ? String.fromCodePoint(codePoint) : ''
|
||||
}).replace(/&([a-zA-Z][a-zA-Z0-9]+);?/g, (entity, name) => {
|
||||
return fallbackNamedHtmlEntities[name] ?? entity
|
||||
})
|
||||
}
|
||||
|
||||
export function glyphLooksLikeHtml(text) {
|
||||
const trimmedText = text.trim()
|
||||
export function glyphLooksLikeHtml (text) {
|
||||
const trimmedText = text.trim()
|
||||
|
||||
return trimmedText.startsWith('<') || /<img\b/i.test(text) || /\/custom-webui\//i.test(text)
|
||||
return trimmedText.startsWith('<') || /<img\b/i.test(text) || /\/custom-webui\//i.test(text)
|
||||
}
|
||||
|
||||
@@ -3,18 +3,18 @@ import assert from 'node:assert/strict'
|
||||
import { decodeHtmlEntities, glyphLooksLikeHtml } from './actionIconGlyphHelpers.mjs'
|
||||
|
||||
test('decodeHtmlEntities decodes named entity icons as plain glyph text', () => {
|
||||
assert.equal(decodeHtmlEntities('«'), '\u00ab')
|
||||
assert.equal(decodeHtmlEntities('→'), '\u2192')
|
||||
assert.equal(decodeHtmlEntities('« next →'), '\u00ab next \u2192')
|
||||
assert.equal(decodeHtmlEntities('«'), '\u00ab')
|
||||
assert.equal(decodeHtmlEntities('→'), '\u2192')
|
||||
assert.equal(decodeHtmlEntities('« next →'), '\u00ab next \u2192')
|
||||
})
|
||||
|
||||
test('decoded named entity icons are not treated as HTML markup', () => {
|
||||
const decodedGlyph = decodeHtmlEntities('→')
|
||||
const decodedGlyph = decodeHtmlEntities('→')
|
||||
|
||||
assert.equal(glyphLooksLikeHtml(decodedGlyph), false)
|
||||
assert.equal(glyphLooksLikeHtml(decodedGlyph), false)
|
||||
})
|
||||
|
||||
test('decodeHtmlEntities keeps existing numeric entity icon support', () => {
|
||||
assert.equal(decodeHtmlEntities('💩'), '\ud83d\udca9')
|
||||
assert.equal(decodeHtmlEntities('💾'), '\ud83d\udcbe')
|
||||
assert.equal(decodeHtmlEntities('💩'), '\ud83d\udca9')
|
||||
assert.equal(decodeHtmlEntities('💾'), '\ud83d\udcbe')
|
||||
})
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import { selectBrowserLocale } from './utils/localeSelection.js'
|
||||
|
||||
const localePathPrefix = '../../../lang/generated/'
|
||||
const localeModules = import.meta.glob('../../../lang/generated/*.json', {
|
||||
import: 'default'
|
||||
})
|
||||
|
||||
export const availableLocales = Object.keys(localeModules)
|
||||
.map(path => path.slice(localePathPrefix.length, -'.json'.length))
|
||||
.sort()
|
||||
|
||||
export function resolveBrowserLocale (browserLanguages = navigator.languages) {
|
||||
return selectBrowserLocale(availableLocales, browserLanguages)
|
||||
}
|
||||
|
||||
export function getSelectedLocale () {
|
||||
const storedLanguage = localStorage.getItem('olivetin-language')
|
||||
|
||||
if (storedLanguage && storedLanguage !== 'auto' && availableLocales.includes(storedLanguage)) {
|
||||
return storedLanguage
|
||||
}
|
||||
|
||||
if (storedLanguage === 'auto') {
|
||||
localStorage.removeItem('olivetin-language')
|
||||
}
|
||||
|
||||
return resolveBrowserLocale()
|
||||
}
|
||||
|
||||
export async function loadLocaleMessages (locale) {
|
||||
const loadLocale = localeModules[`${localePathPrefix}${locale}.json`]
|
||||
|
||||
if (!loadLocale) {
|
||||
throw new Error(`Unsupported locale: ${locale}`)
|
||||
}
|
||||
|
||||
return loadLocale()
|
||||
}
|
||||
|
||||
export async function loadInitialMessages (locale) {
|
||||
const locales = locale === 'en' ? ['en'] : ['en', locale]
|
||||
const loadedMessages = await Promise.all(locales.map(async currentLocale => {
|
||||
return [currentLocale, await loadLocaleMessages(currentLocale)]
|
||||
}))
|
||||
|
||||
return Object.fromEntries(loadedMessages)
|
||||
}
|
||||
|
||||
export async function ensureLocaleMessages (i18n, locale) {
|
||||
if (!i18n.availableLocales.includes(locale)) {
|
||||
i18n.setLocaleMessage(locale, await loadLocaleMessages(locale))
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ const choices = [
|
||||
{ title: 'Photos', value: 'photos' }
|
||||
]
|
||||
|
||||
test('parseChecklistValue parses JSON-encoded values', () => {
|
||||
test('parseChecklistValue parses JSON-encoded values', () => {
|
||||
assert.deepEqual(parseChecklistValue('["documents","photos"]'), ['documents', 'photos'])
|
||||
assert.deepEqual(parseChecklistValue('["kitchen,bedroom","hallway"]'), ['kitchen,bedroom', 'hallway'])
|
||||
assert.deepEqual(parseChecklistValue(''), [])
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
export function selectBrowserLocale (availableLocales, browserLanguages) {
|
||||
for (const candidate of browserLanguages || []) {
|
||||
const lowerCandidate = candidate.toLowerCase()
|
||||
const exact = availableLocales.find(locale => locale.toLowerCase() === lowerCandidate)
|
||||
|
||||
if (exact) {
|
||||
return exact
|
||||
}
|
||||
|
||||
const parts = lowerCandidate.split('-')
|
||||
|
||||
for (let length = parts.length - 1; length > 0; length--) {
|
||||
const prefix = `${parts.slice(0, length).join('-')}-`
|
||||
const match = availableLocales.find(locale => locale.toLowerCase().startsWith(prefix))
|
||||
|
||||
if (match) {
|
||||
return match
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 'en'
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import test from 'node:test'
|
||||
|
||||
import { selectBrowserLocale } from './localeSelection.js'
|
||||
|
||||
const availableLocales = ['de-DE', 'en', 'es-ES', 'zh-Hans-CN', 'zh-Hant-TW']
|
||||
|
||||
test('selectBrowserLocale prefers an exact locale match', () => {
|
||||
assert.equal(selectBrowserLocale(availableLocales, ['es-ES']), 'es-ES')
|
||||
})
|
||||
|
||||
test('selectBrowserLocale falls back to a matching language', () => {
|
||||
assert.equal(selectBrowserLocale(availableLocales, ['de-AT']), 'de-DE')
|
||||
})
|
||||
|
||||
test('selectBrowserLocale preserves script subtags during fallback', () => {
|
||||
assert.equal(selectBrowserLocale(availableLocales, ['zh-Hant-HK']), 'zh-Hant-TW')
|
||||
})
|
||||
|
||||
test('selectBrowserLocale uses English when no locale matches', () => {
|
||||
assert.equal(selectBrowserLocale(availableLocales, ['fr-FR']), 'en')
|
||||
})
|
||||
@@ -4,31 +4,31 @@ import assert from 'node:assert/strict'
|
||||
import { getInitialArgumentValue, readPrefilledArgumentsFromNavigation } from './prefilledArguments.js'
|
||||
|
||||
test('readPrefilledArgumentsFromNavigation returns navigation state values', () => {
|
||||
assert.deepEqual(
|
||||
readPrefilledArgumentsFromNavigation({ prefilledArguments: { ansible_host: '10.0.0.1' } }),
|
||||
{ ansible_host: '10.0.0.1' }
|
||||
)
|
||||
assert.deepEqual(
|
||||
readPrefilledArgumentsFromNavigation({ prefilledArguments: { ansible_host: '10.0.0.1' } }),
|
||||
{ ansible_host: '10.0.0.1' }
|
||||
)
|
||||
})
|
||||
|
||||
test('readPrefilledArgumentsFromNavigation returns empty object when state is absent', () => {
|
||||
assert.deepEqual(readPrefilledArgumentsFromNavigation({}), {})
|
||||
assert.deepEqual(readPrefilledArgumentsFromNavigation(undefined), {})
|
||||
assert.deepEqual(readPrefilledArgumentsFromNavigation({}), {})
|
||||
assert.deepEqual(readPrefilledArgumentsFromNavigation(undefined), {})
|
||||
})
|
||||
|
||||
test('getInitialArgumentValue prefers navigation state over query params', () => {
|
||||
assert.equal(
|
||||
getInitialArgumentValue(
|
||||
'ansible_host',
|
||||
{ ansible_host: '10.0.0.1' },
|
||||
'?ansible_host=10.0.0.2'
|
||||
),
|
||||
'10.0.0.1'
|
||||
)
|
||||
assert.equal(
|
||||
getInitialArgumentValue(
|
||||
'ansible_host',
|
||||
{ ansible_host: '10.0.0.1' },
|
||||
'?ansible_host=10.0.0.2'
|
||||
),
|
||||
'10.0.0.1'
|
||||
)
|
||||
})
|
||||
|
||||
test('getInitialArgumentValue falls back to query params when state is absent', () => {
|
||||
assert.equal(
|
||||
getInitialArgumentValue('ansible_host', {}, '?ansible_host=10.0.0.2'),
|
||||
'10.0.0.2'
|
||||
)
|
||||
assert.equal(
|
||||
getInitialArgumentValue('ansible_host', {}, '?ansible_host=10.0.0.2'),
|
||||
'10.0.0.2'
|
||||
)
|
||||
})
|
||||
|
||||
@@ -355,10 +355,10 @@ async function fetchExecutionResult (executionTrackingIdParam) {
|
||||
} catch (err) {
|
||||
// Check if it's a "not found" error (404 or similar)
|
||||
if (err.status === 404 || err.code === 'NotFound' || err.message?.includes('not found')) {
|
||||
notFound.value = true
|
||||
errorMessage.value = err.message || 'The execution could not be found in the system.'
|
||||
notFound.value = true
|
||||
errorMessage.value = err.message || 'The execution could not be found in the system.'
|
||||
} else {
|
||||
renderError(err)
|
||||
renderError(err)
|
||||
}
|
||||
throw err
|
||||
}
|
||||
@@ -434,7 +434,7 @@ async function renderExecutionResult (res) {
|
||||
if (terminal) {
|
||||
await terminal.reset()
|
||||
await terminal.write(res.logEntry.output, () => {
|
||||
terminal.fit()
|
||||
terminal.fit()
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -488,11 +488,11 @@ onMounted(() => {
|
||||
watch(
|
||||
() => buttonResults[props.executionTrackingId],
|
||||
(newResult, oldResult) => {
|
||||
if (newResult) {
|
||||
if (newResult) {
|
||||
renderExecutionResult({
|
||||
logEntry: newResult
|
||||
logEntry: newResult
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
@@ -1,20 +1,11 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import Components from 'unplugin-vue-components/vite'
|
||||
|
||||
export default defineConfig({
|
||||
resolve: {
|
||||
dedupe: ['vue', 'vue-router']
|
||||
},
|
||||
plugins: [
|
||||
Components({
|
||||
dirs: ['resources/vue/'],
|
||||
extensions: ['vue'],
|
||||
deep: true,
|
||||
dts: false
|
||||
}),
|
||||
vue()
|
||||
],
|
||||
plugins: [vue()],
|
||||
build: {
|
||||
rolldownOptions: {
|
||||
onLog (level, log, defaultHandler) {
|
||||
|
||||
+7
-6
@@ -1,6 +1,6 @@
|
||||
Hey, thanks for reading this quick introduction to translations. The project founder
|
||||
only speaks two languages; English, and Bad English (!), so the initial translations
|
||||
have all been AI-generated. It is assumed that "something is better than nothing".
|
||||
have all been AI-generated. It is assumed that "something is better than nothing".
|
||||
|
||||
It would be most welcome to have human contributors who are native speakers improve these
|
||||
translations, or add new ones.
|
||||
@@ -18,19 +18,20 @@ language pluralization and other advanced features. For docs, check the followin
|
||||
|
||||
[Vue i18n Pluralization Guide](https://vue-i18n.intlify.dev/guide/essentials/pluralization.html)
|
||||
|
||||
The translation files are in YAML format. Each file contains key-value pairs.
|
||||
The translation files are in YAML format. Each file contains key-value pairs.
|
||||
|
||||
OliveTin developers then "process" these files into JSON format used for the app.
|
||||
OliveTin developers then process these files into per-locale JSON files under
|
||||
`lang/generated/`, which lets the app load only the languages it needs.
|
||||
|
||||
If you are able, it would be appreciated if you run `make` in the language directory
|
||||
If you are able, it would be appreciated if you run `make` in the language directory
|
||||
to process your language file before submitting a PR. This will ensure that the JSON
|
||||
file is up-to-date. If you don't understand how to do this, don't worry; just submit
|
||||
the YAML file and the developers will take care of it.
|
||||
|
||||
## Contributing improvements
|
||||
|
||||
Please check out the file `CONTRIBUTING.md` for instructions on how to submit a pull
|
||||
Please check out the file `CONTRIBUTING.md` for instructions on how to submit a pull
|
||||
request with your improvements.
|
||||
|
||||
As always, if you need any help, please feel free to raise an issue on GitHub or
|
||||
As always, if you need any help, please feel free to raise an issue on GitHub or
|
||||
jump into the Discord server for OliveTin.
|
||||
|
||||
@@ -1,557 +0,0 @@
|
||||
{
|
||||
"_comment": "This file is generated. Please re-generate this file using 'make' when you update a translation.",
|
||||
"messages": {
|
||||
"de-DE": {
|
||||
"connected": "Verbunden",
|
||||
"diagnostics.browser-info": "Browser-Informationen",
|
||||
"diagnostics.browser-info-description": "Dieser Abschnitt ermöglicht es Ihnen, einen detaillierten Bericht über Ihre Browser-Informationen zu erstellen. Dies kann bei der Fehlerbehebung von browser-spezifischen Problemen hilfreich sein.",
|
||||
"diagnostics.config-issue-action": "Aktion",
|
||||
"diagnostics.config-issue-argument": "Argument",
|
||||
"diagnostics.config-issue-code": "Code",
|
||||
"diagnostics.config-issue-config-file": "Konfigurationsdatei",
|
||||
"diagnostics.config-issue-detail": "Detail",
|
||||
"diagnostics.config-issue-message": "Meldung",
|
||||
"diagnostics.config-issue-severity": "Schweregrad",
|
||||
"diagnostics.config-issue-source": "Quelle",
|
||||
"diagnostics.config-issues": "Konfigurationsprobleme",
|
||||
"diagnostics.config-issues-description": "In der aktuellen OliveTin-Konfiguration erkannte Probleme. Beheben Sie diese in Ihren Konfigurationsdateien und laden Sie OliveTin neu.",
|
||||
"diagnostics.config-issues-none": "Keine Konfigurationsprobleme erkannt.",
|
||||
"diagnostics.copied": "Kopiert!",
|
||||
"diagnostics.copy-to-clipboard": "In Zwischenablage kopieren",
|
||||
"diagnostics.found-config": "Konfiguration gefunden",
|
||||
"diagnostics.found-key": "Schlüssel gefunden",
|
||||
"diagnostics.generate-browser-info": "Browser-Informationen erstellen",
|
||||
"diagnostics.generate-server-diagnostics": "Server-Diagnostik erstellen",
|
||||
"diagnostics.get-support": "Unterstützung erhalten",
|
||||
"diagnostics.get-support-description": "Wenn Sie Probleme mit OliveTin haben und eine Support-Anfrage stellen möchten, wäre es sehr hilfreich, Server-Diagnostik von dieser Seite einzufügen.",
|
||||
"diagnostics.server-diagnostics": "Server-Diagnostik",
|
||||
"diagnostics.server-diagnostics-description": "Dieser Abschnitt ermöglicht es Ihnen, einen detaillierten Bericht über Ihre Konfiguration und Umgebung zu erstellen. Es ist eine gute Idee, dies bei einer Support-Anfrage einzufügen.",
|
||||
"diagnostics.server-diagnostics-docs": "Server-Diagnostik-Dokumentation",
|
||||
"diagnostics.ssh": "SSH",
|
||||
"diagnostics.unknown": "Unbekannt",
|
||||
"diagnostics.useragent-data-error": "Fehler beim Abrufen von userAgentData",
|
||||
"diagnostics.where-to-find-help": "Wo Sie Hilfe finden",
|
||||
"disconnected": "Getrennt",
|
||||
"disconnected-banner-announcement": "Events-Websocket getrennt.",
|
||||
"disconnected-banner-link-text": "Events-Websocket getrennt",
|
||||
"disconnected-banner-suffix": " seit {disconnectedSince}. Erneuter Verbindungsversuch in {reconnectIn}.",
|
||||
"disconnected-banner-suffix-reconnecting": " seit {disconnectedSince}. Verbindungsversuch…",
|
||||
"docs": "Dokumentation",
|
||||
"language-dialog.browser-languages": "Browser-Sprachen",
|
||||
"language-dialog.close": "Schließen",
|
||||
"language-dialog.not-available": "Nicht verfügbar",
|
||||
"language-dialog.title": "Sprache auswählen",
|
||||
"login-button": "Login",
|
||||
"logs.action": "Aktion",
|
||||
"logs.action-group-limits": "gleichzeitig: {concurrent}, Warteschlangengröße: {queueSize}",
|
||||
"logs.back-to-list": "Zurück zur Liste",
|
||||
"logs.blocked": "Blockiert",
|
||||
"logs.calendar": "Kalender",
|
||||
"logs.calendar-title": "Protokoll-Kalender",
|
||||
"logs.clear-date-filter": "Datumsfilter löschen",
|
||||
"logs.clear-filter": "Suchfilter löschen",
|
||||
"logs.completed": "Abgeschlossen",
|
||||
"logs.execution-id": "Ausführungs-ID",
|
||||
"logs.exit-code": "Ausführungscode",
|
||||
"logs.filter-error": "Filterausdruck konnte nicht angewendet werden.",
|
||||
"logs.filter-help-examples": "Beispiele: backup · !Update · Status != Completed · Status == Blocked · Action contains backup and Status == Completed",
|
||||
"logs.filter-help-fields": "Felder: Status, Action, User, Output, Blocked, TimedOut, Running, ExitCode. Operatoren: ==, !=, contains. Ein !-Präfix vor einem einzelnen Wort schließt passende Einträge aus.",
|
||||
"logs.filter-help-intro": "Filter laufen auf dem Server über Einträge, die Sie anzeigen dürfen. Kombinieren Sie Begriffe mit and / or.",
|
||||
"logs.filter-help-title": "Filtersyntax",
|
||||
"logs.filter-placeholder": "Protokolle filtern (z. B. !Update oder Status != Completed)",
|
||||
"logs.metadata": "Metadaten",
|
||||
"logs.no-logs-for-filter": "Keine Protokolle entsprechen dem aktuellen Filter.",
|
||||
"logs.no-logs-to-display": "Es gibt keine Protokolle zu anzeigen.",
|
||||
"logs.page-description": "Dies ist eine Liste von Protokollen von Aktionen, die ausgeführt wurden. Sie können die Liste nach Aktionstitel filtern.",
|
||||
"logs.queue": "Warteschlange",
|
||||
"logs.queue-action-details": "Aktionsdetails",
|
||||
"logs.queue-default-group": "Standard",
|
||||
"logs.queue-empty": "Derzeit gibt es keine aktiven oder wartenden Ausführungen.",
|
||||
"logs.queue-entity": "Entität",
|
||||
"logs.queue-group-active": "{active} aktiv (max. {max})",
|
||||
"logs.queue-group-active-unlimited": "{active} aktiv",
|
||||
"logs.queue-page-description": "Aktive und wartende Ausführungen, nach Aktionsgruppe gruppiert. Einträge ohne Berechtigung werden ausgeblendet.",
|
||||
"logs.queue-position": "#{position}",
|
||||
"logs.queue-running": "Läuft",
|
||||
"logs.queue-title": "Ausführungswarteschlange",
|
||||
"logs.queue-waiting": "Wartend",
|
||||
"logs.status": "Status",
|
||||
"logs.timed-out": "Zeitüberschreitung",
|
||||
"logs.timestamp": "Zeitstempel",
|
||||
"logs.title": "Protokolle",
|
||||
"nav.actions": "Aktionen",
|
||||
"nav.diagnostics": "Diagnostik",
|
||||
"nav.entities": "Entitäten",
|
||||
"nav.logs": "Protokolle",
|
||||
"nav.system": "System",
|
||||
"raise-issue": "Ein Problem melden auf GitHub",
|
||||
"reconnecting": "Verbinde erneut…",
|
||||
"return-to-index": "Zurück zur Startseite",
|
||||
"search-filter": "Filter aktuelle Seite",
|
||||
"theme-dialog.close": "Schließen",
|
||||
"theme-dialog.default": "Standard-Design",
|
||||
"theme-dialog.title": "Design auswählen",
|
||||
"welcome": "Willkommen bei OliveTin"
|
||||
},
|
||||
"en": {
|
||||
"connected": "Connected",
|
||||
"diagnostics.browser-info": "Browser Info",
|
||||
"diagnostics.browser-info-description": "This section allows you to generate a detailed report of your browser information. This can be helpful when troubleshooting browser-specific issues.",
|
||||
"diagnostics.config-issue-action": "Action",
|
||||
"diagnostics.config-issue-argument": "Argument",
|
||||
"diagnostics.config-issue-code": "Code",
|
||||
"diagnostics.config-issue-config-file": "Config file",
|
||||
"diagnostics.config-issue-detail": "Detail",
|
||||
"diagnostics.config-issue-message": "Message",
|
||||
"diagnostics.config-issue-severity": "Severity",
|
||||
"diagnostics.config-issue-source": "Source",
|
||||
"diagnostics.config-issues": "Configuration issues",
|
||||
"diagnostics.config-issues-description": "Problems detected in the current OliveTin configuration. Fix these in your config files and reload OliveTin.",
|
||||
"diagnostics.config-issues-none": "No configuration issues detected.",
|
||||
"diagnostics.copied": "Copied!",
|
||||
"diagnostics.copy-to-clipboard": "Copy to Clipboard",
|
||||
"diagnostics.found-config": "Found Config",
|
||||
"diagnostics.found-key": "Found Key",
|
||||
"diagnostics.generate-browser-info": "Generate Browser Info",
|
||||
"diagnostics.generate-server-diagnostics": "Generate Server Diagnostics",
|
||||
"diagnostics.get-support": "Get support",
|
||||
"diagnostics.get-support-description": "If you are having problems with OliveTin and want to raise a support request, it would be very helpful to include Server Diagnostics from this page.",
|
||||
"diagnostics.server-diagnostics": "Server Diagnostics",
|
||||
"diagnostics.server-diagnostics-description": "This section allows you to generate a detailed report of your configuration and environment. It is a good idea to include this when raising a support request.",
|
||||
"diagnostics.server-diagnostics-docs": "Server Diagnostics Documentation",
|
||||
"diagnostics.ssh": "SSH",
|
||||
"diagnostics.unknown": "Unknown",
|
||||
"diagnostics.useragent-data-error": "Error retrieving userAgentData",
|
||||
"diagnostics.where-to-find-help": "Where to find help",
|
||||
"disconnected": "Disconnected",
|
||||
"disconnected-banner-announcement": "Events websocket disconnected.",
|
||||
"disconnected-banner-link-text": "Events websocket disconnected",
|
||||
"disconnected-banner-suffix": " since {disconnectedSince}. Trying reconnect in {reconnectIn}.",
|
||||
"disconnected-banner-suffix-reconnecting": " since {disconnectedSince}. Trying reconnect…",
|
||||
"docs": "Documentation",
|
||||
"language-dialog.browser-languages": "Browser languages",
|
||||
"language-dialog.close": "Close",
|
||||
"language-dialog.not-available": "Not available",
|
||||
"language-dialog.title": "Select Language",
|
||||
"login-button": "Login",
|
||||
"logs.action": "Action",
|
||||
"logs.action-group-limits": "concurrent: {concurrent}, queue size: {queueSize}",
|
||||
"logs.back-to-list": "Back to List",
|
||||
"logs.blocked": "Blocked",
|
||||
"logs.calendar": "Calendar",
|
||||
"logs.calendar-title": "Logs Calendar",
|
||||
"logs.clear-date-filter": "Clear date filter",
|
||||
"logs.clear-filter": "Clear search filter",
|
||||
"logs.completed": "Completed",
|
||||
"logs.execution-id": "Execution ID",
|
||||
"logs.exit-code": "Exit code",
|
||||
"logs.filter-error": "Could not apply filter expression.",
|
||||
"logs.filter-help-examples": "Examples: backup · !Update · Status != Completed · Status == Blocked · Action contains backup and Status == Completed",
|
||||
"logs.filter-help-fields": "Fields: Status, Action, User, Output, Blocked, TimedOut, Running, ExitCode. Operators: ==, !=, contains. Prefix ! on a single word excludes matching entries.",
|
||||
"logs.filter-help-intro": "Filters run on the server over entries you are allowed to view. Combine terms with and / or.",
|
||||
"logs.filter-help-title": "Filter syntax",
|
||||
"logs.filter-placeholder": "Filter logs (e.g. !Update or Status != Completed)",
|
||||
"logs.metadata": "Metadata",
|
||||
"logs.no-logs-for-filter": "No logs match the current filter.",
|
||||
"logs.no-logs-to-display": "There are no logs to display.",
|
||||
"logs.page-description": "This is a list of logs from actions that have been executed. Use the filter box for search terms and expressions.",
|
||||
"logs.queue": "Queue",
|
||||
"logs.queue-action-details": "Action Details",
|
||||
"logs.queue-default-group": "Default",
|
||||
"logs.queue-empty": "There are no active or waiting executions right now.",
|
||||
"logs.queue-entity": "Entity",
|
||||
"logs.queue-group-active": "{active} active (max {max})",
|
||||
"logs.queue-group-active-unlimited": "{active} active",
|
||||
"logs.queue-page-description": "Active and waiting executions grouped by action group. Entries you are not permitted to view are hidden.",
|
||||
"logs.queue-position": "#{position}",
|
||||
"logs.queue-running": "Running",
|
||||
"logs.queue-title": "Execution Queue",
|
||||
"logs.queue-waiting": "Waiting",
|
||||
"logs.status": "Status",
|
||||
"logs.timed-out": "Timed out",
|
||||
"logs.timestamp": "Timestamp",
|
||||
"logs.title": "Logs",
|
||||
"nav.actions": "Actions",
|
||||
"nav.diagnostics": "Diagnostics",
|
||||
"nav.entities": "Entities",
|
||||
"nav.logs": "Logs",
|
||||
"nav.system": "System",
|
||||
"raise-issue": "Raise an issue on GitHub",
|
||||
"reconnecting": "Reconnecting…",
|
||||
"return-to-index": "Return to index",
|
||||
"search-filter": "Filter current page",
|
||||
"theme-dialog.close": "Close",
|
||||
"theme-dialog.default": "Default Theme",
|
||||
"theme-dialog.title": "Select Theme",
|
||||
"welcome": "Welcome to OliveTin"
|
||||
},
|
||||
"es-ES": {
|
||||
"connected": "Conectado",
|
||||
"diagnostics.browser-info": "Información del navegador",
|
||||
"diagnostics.browser-info-description": "Esta sección le permite generar un informe detallado de su información del navegador. Esto puede ser útil al solucionar problemas específicos del navegador.",
|
||||
"diagnostics.config-issue-action": "Acción",
|
||||
"diagnostics.config-issue-argument": "Argumento",
|
||||
"diagnostics.config-issue-code": "Código",
|
||||
"diagnostics.config-issue-config-file": "Archivo de configuración",
|
||||
"diagnostics.config-issue-detail": "Detalle",
|
||||
"diagnostics.config-issue-message": "Mensaje",
|
||||
"diagnostics.config-issue-severity": "Severidad",
|
||||
"diagnostics.config-issue-source": "Origen",
|
||||
"diagnostics.config-issues": "Problemas de configuración",
|
||||
"diagnostics.config-issues-description": "Problemas detectados en la configuración actual de OliveTin. Corríjalos en sus archivos de configuración y vuelva a cargar OliveTin.",
|
||||
"diagnostics.config-issues-none": "No se detectaron problemas de configuración.",
|
||||
"diagnostics.copied": "¡Copiado!",
|
||||
"diagnostics.copy-to-clipboard": "Copiar al portapapeles",
|
||||
"diagnostics.found-config": "Configuración encontrada",
|
||||
"diagnostics.found-key": "Clave encontrada",
|
||||
"diagnostics.generate-browser-info": "Generar información del navegador",
|
||||
"diagnostics.generate-server-diagnostics": "Generar diagnóstico del servidor",
|
||||
"diagnostics.get-support": "Obtener soporte",
|
||||
"diagnostics.get-support-description": "Si tiene problemas con OliveTin y desea presentar una solicitud de soporte, sería muy útil incluir diagnósticos del servidor desde esta página.",
|
||||
"diagnostics.server-diagnostics": "Diagnóstico del servidor",
|
||||
"diagnostics.server-diagnostics-description": "Esta sección le permite generar un informe detallado de su configuración y entorno. Es una buena idea incluir esto al presentar una solicitud de soporte.",
|
||||
"diagnostics.server-diagnostics-docs": "Documentación de diagnóstico del servidor",
|
||||
"diagnostics.ssh": "SSH",
|
||||
"diagnostics.unknown": "Desconocido",
|
||||
"diagnostics.useragent-data-error": "Error al recuperar userAgentData",
|
||||
"diagnostics.where-to-find-help": "Dónde encontrar ayuda",
|
||||
"disconnected": "Desconectado",
|
||||
"disconnected-banner-announcement": "Websocket de eventos desconectado.",
|
||||
"disconnected-banner-link-text": "Websocket de eventos desconectado",
|
||||
"disconnected-banner-suffix": " desde {disconnectedSince}. Reintentando conexión en {reconnectIn}.",
|
||||
"disconnected-banner-suffix-reconnecting": " desde {disconnectedSince}. Reintentando conexión…",
|
||||
"docs": "Documentación",
|
||||
"language-dialog.browser-languages": "Idiomas del navegador",
|
||||
"language-dialog.close": "Cerrar",
|
||||
"language-dialog.not-available": "No disponible",
|
||||
"language-dialog.title": "Seleccionar idioma",
|
||||
"login-button": "Iniciar sesión",
|
||||
"logs.action": "Acción",
|
||||
"logs.action-group-limits": "simultáneas: {concurrent}, tamaño de cola: {queueSize}",
|
||||
"logs.back-to-list": "Volver a la Lista",
|
||||
"logs.blocked": "Bloqueado",
|
||||
"logs.calendar": "Calendario",
|
||||
"logs.calendar-title": "Calendario de Registros",
|
||||
"logs.clear-date-filter": "Limpiar filtro de fecha",
|
||||
"logs.clear-filter": "Limpiar filtro de búsqueda",
|
||||
"logs.completed": "Completado",
|
||||
"logs.execution-id": "ID de ejecución",
|
||||
"logs.exit-code": "Código de salida",
|
||||
"logs.filter-error": "No se pudo aplicar la expresión del filtro.",
|
||||
"logs.filter-help-examples": "Ejemplos: backup · !Update · Status != Completed · Status == Blocked · Action contains backup and Status == Completed",
|
||||
"logs.filter-help-fields": "Campos: Status, Action, User, Output, Blocked, TimedOut, Running, ExitCode. Operadores: ==, !=, contains. Un prefijo ! en una sola palabra excluye las entradas coincidentes.",
|
||||
"logs.filter-help-intro": "Los filtros se ejecutan en el servidor sobre las entradas que puede ver. Combine términos con and / or.",
|
||||
"logs.filter-help-title": "Sintaxis del filtro",
|
||||
"logs.filter-placeholder": "Filtrar registros (p. ej. !Update o Status != Completed)",
|
||||
"logs.metadata": "Metadatos",
|
||||
"logs.no-logs-for-filter": "Ningún registro coincide con el filtro actual.",
|
||||
"logs.no-logs-to-display": "No hay registros para mostrar.",
|
||||
"logs.page-description": "Esta es una lista de registros de acciones que han sido ejecutadas. Puede filtrar la lista por título de acción.",
|
||||
"logs.queue": "Cola",
|
||||
"logs.queue-action-details": "Detalles de la acción",
|
||||
"logs.queue-default-group": "Predeterminado",
|
||||
"logs.queue-empty": "No hay ejecuciones activas o en espera en este momento.",
|
||||
"logs.queue-entity": "Entidad",
|
||||
"logs.queue-group-active": "{active} activas (máx. {max})",
|
||||
"logs.queue-group-active-unlimited": "{active} activas",
|
||||
"logs.queue-page-description": "Ejecuciones activas y en espera agrupadas por grupo de acciones. Las entradas que no puede ver se ocultan.",
|
||||
"logs.queue-position": "#{position}",
|
||||
"logs.queue-running": "En ejecución",
|
||||
"logs.queue-title": "Cola de ejecución",
|
||||
"logs.queue-waiting": "En espera",
|
||||
"logs.status": "Estado",
|
||||
"logs.timed-out": "Tiempo agotado",
|
||||
"logs.timestamp": "Marca de tiempo",
|
||||
"logs.title": "Registros",
|
||||
"nav.actions": "Acciones",
|
||||
"nav.diagnostics": "Diagnósticos",
|
||||
"nav.entities": "Entidades",
|
||||
"nav.logs": "Registros",
|
||||
"nav.system": "Sistema",
|
||||
"raise-issue": "Reportar un problema en GitHub",
|
||||
"reconnecting": "Reconectando…",
|
||||
"return-to-index": "Volver a la página principal",
|
||||
"search-filter": "Filtrar página actual",
|
||||
"theme-dialog.close": "Cerrar",
|
||||
"theme-dialog.default": "Tema Predeterminado",
|
||||
"theme-dialog.title": "Seleccionar tema",
|
||||
"welcome": "Bienvenido a OliveTin"
|
||||
},
|
||||
"it-IT": {
|
||||
"connected": "Connesso",
|
||||
"diagnostics.browser-info": "Informazioni del browser",
|
||||
"diagnostics.browser-info-description": "Questa sezione ti consente di generare un rapporto dettagliato delle informazioni del tuo browser. Questo può essere utile durante la risoluzione dei problemi specifici del browser.",
|
||||
"diagnostics.config-issue-action": "Azione",
|
||||
"diagnostics.config-issue-argument": "Argomento",
|
||||
"diagnostics.config-issue-code": "Codice",
|
||||
"diagnostics.config-issue-config-file": "File di configurazione",
|
||||
"diagnostics.config-issue-detail": "Dettaglio",
|
||||
"diagnostics.config-issue-message": "Messaggio",
|
||||
"diagnostics.config-issue-severity": "Gravità",
|
||||
"diagnostics.config-issue-source": "Origine",
|
||||
"diagnostics.config-issues": "Problemi di configurazione",
|
||||
"diagnostics.config-issues-description": "Problemi rilevati nella configurazione corrente di OliveTin. Correggili nei file di configurazione e ricarica OliveTin.",
|
||||
"diagnostics.config-issues-none": "Nessun problema di configurazione rilevato.",
|
||||
"diagnostics.copied": "Copiato!",
|
||||
"diagnostics.copy-to-clipboard": "Copia negli appunti",
|
||||
"diagnostics.found-config": "Configurazione trovata",
|
||||
"diagnostics.found-key": "Chiave trovata",
|
||||
"diagnostics.generate-browser-info": "Genera informazioni del browser",
|
||||
"diagnostics.generate-server-diagnostics": "Genera diagnostica del server",
|
||||
"diagnostics.get-support": "Ottenere supporto",
|
||||
"diagnostics.get-support-description": "Se hai problemi con OliveTin e vuoi presentare una richiesta di supporto, sarebbe molto utile includere la diagnostica del server da questa pagina.",
|
||||
"diagnostics.server-diagnostics": "Diagnostica del server",
|
||||
"diagnostics.server-diagnostics-description": "Questa sezione ti consente di generare un rapporto dettagliato della tua configurazione e ambiente. È una buona idea includere questo quando si presenta una richiesta di supporto.",
|
||||
"diagnostics.server-diagnostics-docs": "Documentazione diagnostica del server",
|
||||
"diagnostics.ssh": "SSH",
|
||||
"diagnostics.unknown": "Sconosciuto",
|
||||
"diagnostics.useragent-data-error": "Errore nel recupero di userAgentData",
|
||||
"diagnostics.where-to-find-help": "Dove trovare aiuto",
|
||||
"disconnected": "Disconnesso",
|
||||
"disconnected-banner-announcement": "Websocket eventi disconnesso.",
|
||||
"disconnected-banner-link-text": "Websocket eventi disconnesso",
|
||||
"disconnected-banner-suffix": " dalle {disconnectedSince}. Nuovo tentativo tra {reconnectIn}.",
|
||||
"disconnected-banner-suffix-reconnecting": " dalle {disconnectedSince}. Tentativo di connessione…",
|
||||
"docs": "Documentazione",
|
||||
"language-dialog.browser-languages": "Lingue del browser",
|
||||
"language-dialog.close": "Chiudi",
|
||||
"language-dialog.not-available": "Non disponibile",
|
||||
"language-dialog.title": "Seleziona lingua",
|
||||
"login-button": "Login",
|
||||
"logs.action": "Azione",
|
||||
"logs.action-group-limits": "simultanei: {concurrent}, dimensione coda: {queueSize}",
|
||||
"logs.back-to-list": "Torna all'Elenco",
|
||||
"logs.blocked": "Bloccato",
|
||||
"logs.calendar": "Calendario",
|
||||
"logs.calendar-title": "Calendario dei Registri",
|
||||
"logs.clear-date-filter": "Cancella filtro data",
|
||||
"logs.clear-filter": "Cancella filtro di ricerca",
|
||||
"logs.completed": "Completato",
|
||||
"logs.execution-id": "ID esecuzione",
|
||||
"logs.exit-code": "Codice di uscita",
|
||||
"logs.filter-error": "Impossibile applicare l'espressione di filtro.",
|
||||
"logs.filter-help-examples": "Esempi: backup · !Update · Status != Completed · Status == Blocked · Action contains backup and Status == Completed",
|
||||
"logs.filter-help-fields": "Campi: Status, Action, User, Output, Blocked, TimedOut, Running, ExitCode. Operatori: ==, !=, contains. Un prefisso ! su una singola parola esclude le voci corrispondenti.",
|
||||
"logs.filter-help-intro": "I filtri vengono eseguiti sul server sulle voci che sei autorizzato a visualizzare. Combina i termini con and / or.",
|
||||
"logs.filter-help-title": "Sintassi del filtro",
|
||||
"logs.filter-placeholder": "Filtra i registri (es. !Update o Status != Completed)",
|
||||
"logs.metadata": "Metadati",
|
||||
"logs.no-logs-for-filter": "Nessun registro corrisponde al filtro corrente.",
|
||||
"logs.no-logs-to-display": "Non ci sono registri da mostrare.",
|
||||
"logs.page-description": "Questa è una lista di registri delle azioni che sono state eseguite. Puoi filtrare la lista per titolo dell'azione.",
|
||||
"logs.queue": "Coda",
|
||||
"logs.queue-action-details": "Dettagli azione",
|
||||
"logs.queue-default-group": "Predefinito",
|
||||
"logs.queue-empty": "Non ci sono esecuzioni attive o in attesa al momento.",
|
||||
"logs.queue-entity": "Entità",
|
||||
"logs.queue-group-active": "{active} attive (max {max})",
|
||||
"logs.queue-group-active-unlimited": "{active} attive",
|
||||
"logs.queue-page-description": "Esecuzioni attive e in attesa raggruppate per gruppo di azioni. Le voci non autorizzate sono nascoste.",
|
||||
"logs.queue-position": "#{position}",
|
||||
"logs.queue-running": "In esecuzione",
|
||||
"logs.queue-title": "Coda di esecuzione",
|
||||
"logs.queue-waiting": "In attesa",
|
||||
"logs.status": "Stato",
|
||||
"logs.timed-out": "Tempo scaduto",
|
||||
"logs.timestamp": "Date e ora",
|
||||
"logs.title": "Registri",
|
||||
"nav.actions": "Azioni",
|
||||
"nav.diagnostics": "Diagnostica",
|
||||
"nav.entities": "Entità",
|
||||
"nav.logs": "Registri",
|
||||
"nav.system": "Sistema",
|
||||
"raise-issue": "Segnala un problema su GitHub",
|
||||
"reconnecting": "Riconnessione…",
|
||||
"return-to-index": "Torna alla pagina principale",
|
||||
"search-filter": "Filtra la pagina corrente",
|
||||
"theme-dialog.close": "Chiudi",
|
||||
"theme-dialog.default": "Tema Predefinito",
|
||||
"theme-dialog.title": "Seleziona tema",
|
||||
"welcome": "Benvenuto in OliveTin"
|
||||
},
|
||||
"zh-Hans-CN": {
|
||||
"connected": "已连接",
|
||||
"diagnostics.browser-info": "浏览器信息",
|
||||
"diagnostics.browser-info-description": "此部分允许您生成浏览器信息的详细报告。这在排查浏览器特定问题时很有帮助。",
|
||||
"diagnostics.config-issue-action": "操作",
|
||||
"diagnostics.config-issue-argument": "参数",
|
||||
"diagnostics.config-issue-code": "代码",
|
||||
"diagnostics.config-issue-config-file": "配置文件",
|
||||
"diagnostics.config-issue-detail": "详情",
|
||||
"diagnostics.config-issue-message": "消息",
|
||||
"diagnostics.config-issue-severity": "严重程度",
|
||||
"diagnostics.config-issue-source": "来源",
|
||||
"diagnostics.config-issues": "配置问题",
|
||||
"diagnostics.config-issues-description": "当前 OliveTin 配置中检测到的问题。请在配置文件中修复这些问题并重新加载 OliveTin。",
|
||||
"diagnostics.config-issues-none": "未检测到配置问题。",
|
||||
"diagnostics.copied": "已复制!",
|
||||
"diagnostics.copy-to-clipboard": "复制到剪贴板",
|
||||
"diagnostics.found-config": "找到配置",
|
||||
"diagnostics.found-key": "找到密钥",
|
||||
"diagnostics.generate-browser-info": "生成浏览器信息",
|
||||
"diagnostics.generate-server-diagnostics": "生成服务器诊断",
|
||||
"diagnostics.get-support": "获取支持",
|
||||
"diagnostics.get-support-description": "如果您在使用 OliveTin 时遇到问题并希望提交支持请求,从本页面包含服务器诊断将非常有帮助。",
|
||||
"diagnostics.server-diagnostics": "服务器诊断",
|
||||
"diagnostics.server-diagnostics-description": "此部分允许您生成配置和环境的详细报告。在提交支持请求时包含此信息是个好主意。",
|
||||
"diagnostics.server-diagnostics-docs": "服务器诊断文档",
|
||||
"diagnostics.ssh": "SSH",
|
||||
"diagnostics.unknown": "未知",
|
||||
"diagnostics.useragent-data-error": "检索 userAgentData 时出错",
|
||||
"diagnostics.where-to-find-help": "在哪里找到帮助",
|
||||
"disconnected": "已断开连接",
|
||||
"disconnected-banner-announcement": "事件 WebSocket 已断开。",
|
||||
"disconnected-banner-link-text": "事件 WebSocket 已断开",
|
||||
"disconnected-banner-suffix": "自 {disconnectedSince}。{reconnectIn} 后尝试重连。",
|
||||
"disconnected-banner-suffix-reconnecting": "自 {disconnectedSince}。正在尝试重连…",
|
||||
"docs": "文档",
|
||||
"language-dialog.browser-languages": "浏览器语言",
|
||||
"language-dialog.close": "关闭",
|
||||
"language-dialog.not-available": "不可用",
|
||||
"language-dialog.title": "选择语言",
|
||||
"login-button": "登录",
|
||||
"logs.action": "动作",
|
||||
"logs.action-group-limits": "并发:{concurrent},队列大小:{queueSize}",
|
||||
"logs.back-to-list": "返回列表",
|
||||
"logs.blocked": "阻塞",
|
||||
"logs.calendar": "日历",
|
||||
"logs.calendar-title": "日志日历",
|
||||
"logs.clear-date-filter": "清除日期筛选器",
|
||||
"logs.clear-filter": "清除搜索筛选器",
|
||||
"logs.completed": "完成",
|
||||
"logs.execution-id": "执行 ID",
|
||||
"logs.exit-code": "退出代码",
|
||||
"logs.filter-error": "无法应用过滤表达式。",
|
||||
"logs.filter-help-examples": "示例:backup · !Update · Status != Completed · Status == Blocked · Action contains backup and Status == Completed",
|
||||
"logs.filter-help-fields": "字段:Status, Action, User, Output, Blocked, TimedOut, Running, ExitCode。运算符:==, !=, contains。在单个单词前加上 ! 前缀可排除匹配的条目。",
|
||||
"logs.filter-help-intro": "过滤器会在服务器端针对您有权限查看的条目进行筛选。您可以使用 and / or 来组合搜索条件。",
|
||||
"logs.filter-help-title": "过滤语法",
|
||||
"logs.filter-placeholder": "过滤日志(例如 !Update 或 Status != Completed)",
|
||||
"logs.metadata": "元数据",
|
||||
"logs.no-logs-for-filter": "没有符合当前过滤条件的日志。",
|
||||
"logs.no-logs-to-display": "没有日志可显示。",
|
||||
"logs.page-description": "这是一个动作执行日志列表。您可以按动作标题过滤列表。",
|
||||
"logs.queue": "队列",
|
||||
"logs.queue-action-details": "动作详细信息",
|
||||
"logs.queue-default-group": "默认",
|
||||
"logs.queue-empty": "当前没有正在运行或等待中的执行。",
|
||||
"logs.queue-entity": "实体",
|
||||
"logs.queue-group-active": "{active} 个活动(上限 {max})",
|
||||
"logs.queue-group-active-unlimited": "{active} 个活动",
|
||||
"logs.queue-page-description": "按动作组分组显示正在运行和等待中的执行。您无权查看的条目会被隐藏。",
|
||||
"logs.queue-position": "第 {position} 位",
|
||||
"logs.queue-running": "运行中",
|
||||
"logs.queue-title": "执行队列",
|
||||
"logs.queue-waiting": "等待中",
|
||||
"logs.status": "状态",
|
||||
"logs.timed-out": "超时",
|
||||
"logs.timestamp": "时间戳",
|
||||
"logs.title": "日志",
|
||||
"nav.actions": "动作",
|
||||
"nav.diagnostics": "诊断",
|
||||
"nav.entities": "实体",
|
||||
"nav.logs": "日志",
|
||||
"nav.system": "系统",
|
||||
"raise-issue": "在 GitHub 上报告问题",
|
||||
"reconnecting": "正在重新连接…",
|
||||
"return-to-index": "返回首页",
|
||||
"search-filter": "过滤当前页面",
|
||||
"theme-dialog.close": "关闭",
|
||||
"theme-dialog.default": "默认主题",
|
||||
"theme-dialog.title": "选择主题",
|
||||
"welcome": "欢迎使用 OliveTin"
|
||||
},
|
||||
"zh-Hant-TW": {
|
||||
"connected": "已連線",
|
||||
"diagnostics.browser-info": "瀏覽器資訊",
|
||||
"diagnostics.browser-info-description": "此區塊可讓您產生瀏覽器資訊的詳細報告。這在排解特定瀏覽器問題時相當有幫助。",
|
||||
"diagnostics.config-issue-action": "動作",
|
||||
"diagnostics.config-issue-argument": "參數",
|
||||
"diagnostics.config-issue-code": "代碼",
|
||||
"diagnostics.config-issue-config-file": "設定檔",
|
||||
"diagnostics.config-issue-detail": "詳細資料",
|
||||
"diagnostics.config-issue-message": "訊息",
|
||||
"diagnostics.config-issue-severity": "嚴重程度",
|
||||
"diagnostics.config-issue-source": "來源",
|
||||
"diagnostics.config-issues": "設定問題",
|
||||
"diagnostics.config-issues-description": "目前 OliveTin 設定中偵測到的問題。請在設定檔中修正這些問題並重新載入 OliveTin。",
|
||||
"diagnostics.config-issues-none": "未偵測到設定問題。",
|
||||
"diagnostics.copied": "已複製!",
|
||||
"diagnostics.copy-to-clipboard": "複製到剪貼簿",
|
||||
"diagnostics.found-config": "找到設定檔 (Config)",
|
||||
"diagnostics.found-key": "找到金鑰 (Key)",
|
||||
"diagnostics.generate-browser-info": "產生瀏覽器資訊",
|
||||
"diagnostics.generate-server-diagnostics": "產生伺服器診斷資訊",
|
||||
"diagnostics.get-support": "尋求技術支援",
|
||||
"diagnostics.get-support-description": "如果您在使用 OliveTin 時遇到問題並希望提出支援請求,提供此頁面的「伺服器診斷資訊」將會有很大的幫助。",
|
||||
"diagnostics.server-diagnostics": "伺服器診斷",
|
||||
"diagnostics.server-diagnostics-description": "此區塊可讓您產生詳細的設定與環境報告。建議在提出支援請求時附上此報告。",
|
||||
"diagnostics.server-diagnostics-docs": "伺服器診斷官方文件",
|
||||
"diagnostics.ssh": "SSH",
|
||||
"diagnostics.unknown": "未知",
|
||||
"diagnostics.useragent-data-error": "取得 userAgentData 時發生錯誤",
|
||||
"diagnostics.where-to-find-help": "哪裡可以尋求協助",
|
||||
"disconnected": "已斷線",
|
||||
"disconnected-banner-announcement": "系統事件 WebSocket 已斷線。",
|
||||
"disconnected-banner-link-text": "系統事件 WebSocket 已斷線",
|
||||
"disconnected-banner-suffix": " 自 {disconnectedSince} 起。將在 {reconnectIn} 後嘗試重新連線。",
|
||||
"disconnected-banner-suffix-reconnecting": " 自 {disconnectedSince} 起。嘗試重新連線中…",
|
||||
"docs": "官方文件",
|
||||
"language-dialog.browser-languages": "瀏覽器語言",
|
||||
"language-dialog.close": "關閉",
|
||||
"language-dialog.not-available": "無法使用",
|
||||
"language-dialog.title": "選擇語言",
|
||||
"login-button": "登入",
|
||||
"logs.action": "動作",
|
||||
"logs.action-group-limits": "並行數:{concurrent},佇列大小:{queueSize}",
|
||||
"logs.back-to-list": "返回清單",
|
||||
"logs.blocked": "已阻擋",
|
||||
"logs.calendar": "日曆",
|
||||
"logs.calendar-title": "日誌日曆",
|
||||
"logs.clear-date-filter": "清除日期過濾條件",
|
||||
"logs.clear-filter": "清除搜尋過濾條件",
|
||||
"logs.completed": "已完成",
|
||||
"logs.execution-id": "執行 ID",
|
||||
"logs.exit-code": "結束代碼",
|
||||
"logs.filter-error": "無法套用過濾運算式。",
|
||||
"logs.filter-help-examples": "範例:backup · !Update · Status != Completed · Status == Blocked · Action contains backup and Status == Completed",
|
||||
"logs.filter-help-fields": "欄位:Status, Action, User, Output, Blocked, TimedOut, Running, ExitCode。運算子:==, !=, contains。在單一單字前加上 ! 前綴可排除相符的項目。",
|
||||
"logs.filter-help-intro": "過濾器會在伺服器端針對您有權限檢視的項目進行篩選。您可以使用 and / or 來組合搜尋條件。",
|
||||
"logs.filter-help-title": "過濾語法",
|
||||
"logs.filter-placeholder": "過濾日誌 (例如 !Update 或 Status != Completed)",
|
||||
"logs.metadata": "中繼資料",
|
||||
"logs.no-logs-for-filter": "沒有符合目前過濾條件的日誌。",
|
||||
"logs.no-logs-to-display": "目前沒有可顯示的日誌。",
|
||||
"logs.page-description": "這是已執行動作的日誌清單。請使用過濾條件欄位來搜尋關鍵字與運算式。",
|
||||
"logs.queue": "佇列",
|
||||
"logs.queue-action-details": "動作詳細資訊",
|
||||
"logs.queue-default-group": "預設",
|
||||
"logs.queue-empty": "目前沒有使用中或等待中的執行項目。",
|
||||
"logs.queue-entity": "實體",
|
||||
"logs.queue-group-active": "{active} 個使用中 (最大值 {max})",
|
||||
"logs.queue-group-active-unlimited": "{active} 個使用中",
|
||||
"logs.queue-page-description": "依動作群組分類的使用中與等待中執行項目。您無權檢視的項目將會被隱藏。",
|
||||
"logs.queue-position": "#{position}",
|
||||
"logs.queue-running": "執行中",
|
||||
"logs.queue-title": "執行佇列",
|
||||
"logs.queue-waiting": "等待中",
|
||||
"logs.status": "狀態",
|
||||
"logs.timed-out": "執行逾時",
|
||||
"logs.timestamp": "時間戳記",
|
||||
"logs.title": "日誌",
|
||||
"nav.actions": "動作",
|
||||
"nav.diagnostics": "診斷",
|
||||
"nav.entities": "實體",
|
||||
"nav.logs": "日誌",
|
||||
"nav.system": "系統",
|
||||
"raise-issue": "在 GitHub 上建立 Issue",
|
||||
"reconnecting": "重新連線中…",
|
||||
"return-to-index": "返回首頁",
|
||||
"search-filter": "過濾目前頁面",
|
||||
"theme-dialog.close": "關閉",
|
||||
"theme-dialog.default": "預設佈景主題",
|
||||
"theme-dialog.title": "選擇佈景主題",
|
||||
"welcome": "歡迎使用 OliveTin"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
{
|
||||
"connected": "Verbunden",
|
||||
"diagnostics.browser-info": "Browser-Informationen",
|
||||
"diagnostics.browser-info-description": "Dieser Abschnitt ermöglicht es Ihnen, einen detaillierten Bericht über Ihre Browser-Informationen zu erstellen. Dies kann bei der Fehlerbehebung von browser-spezifischen Problemen hilfreich sein.",
|
||||
"diagnostics.config-issue-action": "Aktion",
|
||||
"diagnostics.config-issue-argument": "Argument",
|
||||
"diagnostics.config-issue-code": "Code",
|
||||
"diagnostics.config-issue-config-file": "Konfigurationsdatei",
|
||||
"diagnostics.config-issue-detail": "Detail",
|
||||
"diagnostics.config-issue-message": "Meldung",
|
||||
"diagnostics.config-issue-severity": "Schweregrad",
|
||||
"diagnostics.config-issue-source": "Quelle",
|
||||
"diagnostics.config-issues": "Konfigurationsprobleme",
|
||||
"diagnostics.config-issues-description": "In der aktuellen OliveTin-Konfiguration erkannte Probleme. Beheben Sie diese in Ihren Konfigurationsdateien und laden Sie OliveTin neu.",
|
||||
"diagnostics.config-issues-none": "Keine Konfigurationsprobleme erkannt.",
|
||||
"diagnostics.copied": "Kopiert!",
|
||||
"diagnostics.copy-to-clipboard": "In Zwischenablage kopieren",
|
||||
"diagnostics.found-config": "Konfiguration gefunden",
|
||||
"diagnostics.found-key": "Schlüssel gefunden",
|
||||
"diagnostics.generate-browser-info": "Browser-Informationen erstellen",
|
||||
"diagnostics.generate-server-diagnostics": "Server-Diagnostik erstellen",
|
||||
"diagnostics.get-support": "Unterstützung erhalten",
|
||||
"diagnostics.get-support-description": "Wenn Sie Probleme mit OliveTin haben und eine Support-Anfrage stellen möchten, wäre es sehr hilfreich, Server-Diagnostik von dieser Seite einzufügen.",
|
||||
"diagnostics.server-diagnostics": "Server-Diagnostik",
|
||||
"diagnostics.server-diagnostics-description": "Dieser Abschnitt ermöglicht es Ihnen, einen detaillierten Bericht über Ihre Konfiguration und Umgebung zu erstellen. Es ist eine gute Idee, dies bei einer Support-Anfrage einzufügen.",
|
||||
"diagnostics.server-diagnostics-docs": "Server-Diagnostik-Dokumentation",
|
||||
"diagnostics.ssh": "SSH",
|
||||
"diagnostics.unknown": "Unbekannt",
|
||||
"diagnostics.useragent-data-error": "Fehler beim Abrufen von userAgentData",
|
||||
"diagnostics.where-to-find-help": "Wo Sie Hilfe finden",
|
||||
"disconnected": "Getrennt",
|
||||
"disconnected-banner-announcement": "Events-Websocket getrennt.",
|
||||
"disconnected-banner-link-text": "Events-Websocket getrennt",
|
||||
"disconnected-banner-suffix": " seit {disconnectedSince}. Erneuter Verbindungsversuch in {reconnectIn}.",
|
||||
"disconnected-banner-suffix-reconnecting": " seit {disconnectedSince}. Verbindungsversuch…",
|
||||
"docs": "Dokumentation",
|
||||
"language-dialog.browser-languages": "Browser-Sprachen",
|
||||
"language-dialog.close": "Schließen",
|
||||
"language-dialog.not-available": "Nicht verfügbar",
|
||||
"language-dialog.title": "Sprache auswählen",
|
||||
"login-button": "Login",
|
||||
"logs.action": "Aktion",
|
||||
"logs.action-group-limits": "gleichzeitig: {concurrent}, Warteschlangengröße: {queueSize}",
|
||||
"logs.back-to-list": "Zurück zur Liste",
|
||||
"logs.blocked": "Blockiert",
|
||||
"logs.calendar": "Kalender",
|
||||
"logs.calendar-title": "Protokoll-Kalender",
|
||||
"logs.clear-date-filter": "Datumsfilter löschen",
|
||||
"logs.clear-filter": "Suchfilter löschen",
|
||||
"logs.completed": "Abgeschlossen",
|
||||
"logs.execution-id": "Ausführungs-ID",
|
||||
"logs.exit-code": "Ausführungscode",
|
||||
"logs.filter-error": "Filterausdruck konnte nicht angewendet werden.",
|
||||
"logs.filter-help-examples": "Beispiele: backup · !Update · Status != Completed · Status == Blocked · Action contains backup and Status == Completed",
|
||||
"logs.filter-help-fields": "Felder: Status, Action, User, Output, Blocked, TimedOut, Running, ExitCode. Operatoren: ==, !=, contains. Ein !-Präfix vor einem einzelnen Wort schließt passende Einträge aus.",
|
||||
"logs.filter-help-intro": "Filter laufen auf dem Server über Einträge, die Sie anzeigen dürfen. Kombinieren Sie Begriffe mit and / or.",
|
||||
"logs.filter-help-title": "Filtersyntax",
|
||||
"logs.filter-placeholder": "Protokolle filtern (z. B. !Update oder Status != Completed)",
|
||||
"logs.metadata": "Metadaten",
|
||||
"logs.no-logs-for-filter": "Keine Protokolle entsprechen dem aktuellen Filter.",
|
||||
"logs.no-logs-to-display": "Es gibt keine Protokolle zu anzeigen.",
|
||||
"logs.page-description": "Dies ist eine Liste von Protokollen von Aktionen, die ausgeführt wurden. Sie können die Liste nach Aktionstitel filtern.",
|
||||
"logs.queue": "Warteschlange",
|
||||
"logs.queue-action-details": "Aktionsdetails",
|
||||
"logs.queue-default-group": "Standard",
|
||||
"logs.queue-empty": "Derzeit gibt es keine aktiven oder wartenden Ausführungen.",
|
||||
"logs.queue-entity": "Entität",
|
||||
"logs.queue-group-active": "{active} aktiv (max. {max})",
|
||||
"logs.queue-group-active-unlimited": "{active} aktiv",
|
||||
"logs.queue-page-description": "Aktive und wartende Ausführungen, nach Aktionsgruppe gruppiert. Einträge ohne Berechtigung werden ausgeblendet.",
|
||||
"logs.queue-position": "#{position}",
|
||||
"logs.queue-running": "Läuft",
|
||||
"logs.queue-title": "Ausführungswarteschlange",
|
||||
"logs.queue-waiting": "Wartend",
|
||||
"logs.status": "Status",
|
||||
"logs.timed-out": "Zeitüberschreitung",
|
||||
"logs.timestamp": "Zeitstempel",
|
||||
"logs.title": "Protokolle",
|
||||
"nav.actions": "Aktionen",
|
||||
"nav.diagnostics": "Diagnostik",
|
||||
"nav.entities": "Entitäten",
|
||||
"nav.logs": "Protokolle",
|
||||
"nav.system": "System",
|
||||
"raise-issue": "Ein Problem melden auf GitHub",
|
||||
"reconnecting": "Verbinde erneut…",
|
||||
"return-to-index": "Zurück zur Startseite",
|
||||
"search-filter": "Filter aktuelle Seite",
|
||||
"theme-dialog.close": "Schließen",
|
||||
"theme-dialog.default": "Standard-Design",
|
||||
"theme-dialog.title": "Design auswählen",
|
||||
"welcome": "Willkommen bei OliveTin"
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
{
|
||||
"connected": "Connected",
|
||||
"diagnostics.browser-info": "Browser Info",
|
||||
"diagnostics.browser-info-description": "This section allows you to generate a detailed report of your browser information. This can be helpful when troubleshooting browser-specific issues.",
|
||||
"diagnostics.config-issue-action": "Action",
|
||||
"diagnostics.config-issue-argument": "Argument",
|
||||
"diagnostics.config-issue-code": "Code",
|
||||
"diagnostics.config-issue-config-file": "Config file",
|
||||
"diagnostics.config-issue-detail": "Detail",
|
||||
"diagnostics.config-issue-message": "Message",
|
||||
"diagnostics.config-issue-severity": "Severity",
|
||||
"diagnostics.config-issue-source": "Source",
|
||||
"diagnostics.config-issues": "Configuration issues",
|
||||
"diagnostics.config-issues-description": "Problems detected in the current OliveTin configuration. Fix these in your config files and reload OliveTin.",
|
||||
"diagnostics.config-issues-none": "No configuration issues detected.",
|
||||
"diagnostics.copied": "Copied!",
|
||||
"diagnostics.copy-to-clipboard": "Copy to Clipboard",
|
||||
"diagnostics.found-config": "Found Config",
|
||||
"diagnostics.found-key": "Found Key",
|
||||
"diagnostics.generate-browser-info": "Generate Browser Info",
|
||||
"diagnostics.generate-server-diagnostics": "Generate Server Diagnostics",
|
||||
"diagnostics.get-support": "Get support",
|
||||
"diagnostics.get-support-description": "If you are having problems with OliveTin and want to raise a support request, it would be very helpful to include Server Diagnostics from this page.",
|
||||
"diagnostics.server-diagnostics": "Server Diagnostics",
|
||||
"diagnostics.server-diagnostics-description": "This section allows you to generate a detailed report of your configuration and environment. It is a good idea to include this when raising a support request.",
|
||||
"diagnostics.server-diagnostics-docs": "Server Diagnostics Documentation",
|
||||
"diagnostics.ssh": "SSH",
|
||||
"diagnostics.unknown": "Unknown",
|
||||
"diagnostics.useragent-data-error": "Error retrieving userAgentData",
|
||||
"diagnostics.where-to-find-help": "Where to find help",
|
||||
"disconnected": "Disconnected",
|
||||
"disconnected-banner-announcement": "Events websocket disconnected.",
|
||||
"disconnected-banner-link-text": "Events websocket disconnected",
|
||||
"disconnected-banner-suffix": " since {disconnectedSince}. Trying reconnect in {reconnectIn}.",
|
||||
"disconnected-banner-suffix-reconnecting": " since {disconnectedSince}. Trying reconnect…",
|
||||
"docs": "Documentation",
|
||||
"language-dialog.browser-languages": "Browser languages",
|
||||
"language-dialog.close": "Close",
|
||||
"language-dialog.not-available": "Not available",
|
||||
"language-dialog.title": "Select Language",
|
||||
"login-button": "Login",
|
||||
"logs.action": "Action",
|
||||
"logs.action-group-limits": "concurrent: {concurrent}, queue size: {queueSize}",
|
||||
"logs.back-to-list": "Back to List",
|
||||
"logs.blocked": "Blocked",
|
||||
"logs.calendar": "Calendar",
|
||||
"logs.calendar-title": "Logs Calendar",
|
||||
"logs.clear-date-filter": "Clear date filter",
|
||||
"logs.clear-filter": "Clear search filter",
|
||||
"logs.completed": "Completed",
|
||||
"logs.execution-id": "Execution ID",
|
||||
"logs.exit-code": "Exit code",
|
||||
"logs.filter-error": "Could not apply filter expression.",
|
||||
"logs.filter-help-examples": "Examples: backup · !Update · Status != Completed · Status == Blocked · Action contains backup and Status == Completed",
|
||||
"logs.filter-help-fields": "Fields: Status, Action, User, Output, Blocked, TimedOut, Running, ExitCode. Operators: ==, !=, contains. Prefix ! on a single word excludes matching entries.",
|
||||
"logs.filter-help-intro": "Filters run on the server over entries you are allowed to view. Combine terms with and / or.",
|
||||
"logs.filter-help-title": "Filter syntax",
|
||||
"logs.filter-placeholder": "Filter logs (e.g. !Update or Status != Completed)",
|
||||
"logs.metadata": "Metadata",
|
||||
"logs.no-logs-for-filter": "No logs match the current filter.",
|
||||
"logs.no-logs-to-display": "There are no logs to display.",
|
||||
"logs.page-description": "This is a list of logs from actions that have been executed. Use the filter box for search terms and expressions.",
|
||||
"logs.queue": "Queue",
|
||||
"logs.queue-action-details": "Action Details",
|
||||
"logs.queue-default-group": "Default",
|
||||
"logs.queue-empty": "There are no active or waiting executions right now.",
|
||||
"logs.queue-entity": "Entity",
|
||||
"logs.queue-group-active": "{active} active (max {max})",
|
||||
"logs.queue-group-active-unlimited": "{active} active",
|
||||
"logs.queue-page-description": "Active and waiting executions grouped by action group. Entries you are not permitted to view are hidden.",
|
||||
"logs.queue-position": "#{position}",
|
||||
"logs.queue-running": "Running",
|
||||
"logs.queue-title": "Execution Queue",
|
||||
"logs.queue-waiting": "Waiting",
|
||||
"logs.status": "Status",
|
||||
"logs.timed-out": "Timed out",
|
||||
"logs.timestamp": "Timestamp",
|
||||
"logs.title": "Logs",
|
||||
"nav.actions": "Actions",
|
||||
"nav.diagnostics": "Diagnostics",
|
||||
"nav.entities": "Entities",
|
||||
"nav.logs": "Logs",
|
||||
"nav.system": "System",
|
||||
"raise-issue": "Raise an issue on GitHub",
|
||||
"reconnecting": "Reconnecting…",
|
||||
"return-to-index": "Return to index",
|
||||
"search-filter": "Filter current page",
|
||||
"theme-dialog.close": "Close",
|
||||
"theme-dialog.default": "Default Theme",
|
||||
"theme-dialog.title": "Select Theme",
|
||||
"welcome": "Welcome to OliveTin"
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
{
|
||||
"connected": "Conectado",
|
||||
"diagnostics.browser-info": "Información del navegador",
|
||||
"diagnostics.browser-info-description": "Esta sección le permite generar un informe detallado de su información del navegador. Esto puede ser útil al solucionar problemas específicos del navegador.",
|
||||
"diagnostics.config-issue-action": "Acción",
|
||||
"diagnostics.config-issue-argument": "Argumento",
|
||||
"diagnostics.config-issue-code": "Código",
|
||||
"diagnostics.config-issue-config-file": "Archivo de configuración",
|
||||
"diagnostics.config-issue-detail": "Detalle",
|
||||
"diagnostics.config-issue-message": "Mensaje",
|
||||
"diagnostics.config-issue-severity": "Severidad",
|
||||
"diagnostics.config-issue-source": "Origen",
|
||||
"diagnostics.config-issues": "Problemas de configuración",
|
||||
"diagnostics.config-issues-description": "Problemas detectados en la configuración actual de OliveTin. Corríjalos en sus archivos de configuración y vuelva a cargar OliveTin.",
|
||||
"diagnostics.config-issues-none": "No se detectaron problemas de configuración.",
|
||||
"diagnostics.copied": "¡Copiado!",
|
||||
"diagnostics.copy-to-clipboard": "Copiar al portapapeles",
|
||||
"diagnostics.found-config": "Configuración encontrada",
|
||||
"diagnostics.found-key": "Clave encontrada",
|
||||
"diagnostics.generate-browser-info": "Generar información del navegador",
|
||||
"diagnostics.generate-server-diagnostics": "Generar diagnóstico del servidor",
|
||||
"diagnostics.get-support": "Obtener soporte",
|
||||
"diagnostics.get-support-description": "Si tiene problemas con OliveTin y desea presentar una solicitud de soporte, sería muy útil incluir diagnósticos del servidor desde esta página.",
|
||||
"diagnostics.server-diagnostics": "Diagnóstico del servidor",
|
||||
"diagnostics.server-diagnostics-description": "Esta sección le permite generar un informe detallado de su configuración y entorno. Es una buena idea incluir esto al presentar una solicitud de soporte.",
|
||||
"diagnostics.server-diagnostics-docs": "Documentación de diagnóstico del servidor",
|
||||
"diagnostics.ssh": "SSH",
|
||||
"diagnostics.unknown": "Desconocido",
|
||||
"diagnostics.useragent-data-error": "Error al recuperar userAgentData",
|
||||
"diagnostics.where-to-find-help": "Dónde encontrar ayuda",
|
||||
"disconnected": "Desconectado",
|
||||
"disconnected-banner-announcement": "Websocket de eventos desconectado.",
|
||||
"disconnected-banner-link-text": "Websocket de eventos desconectado",
|
||||
"disconnected-banner-suffix": " desde {disconnectedSince}. Reintentando conexión en {reconnectIn}.",
|
||||
"disconnected-banner-suffix-reconnecting": " desde {disconnectedSince}. Reintentando conexión…",
|
||||
"docs": "Documentación",
|
||||
"language-dialog.browser-languages": "Idiomas del navegador",
|
||||
"language-dialog.close": "Cerrar",
|
||||
"language-dialog.not-available": "No disponible",
|
||||
"language-dialog.title": "Seleccionar idioma",
|
||||
"login-button": "Iniciar sesión",
|
||||
"logs.action": "Acción",
|
||||
"logs.action-group-limits": "simultáneas: {concurrent}, tamaño de cola: {queueSize}",
|
||||
"logs.back-to-list": "Volver a la Lista",
|
||||
"logs.blocked": "Bloqueado",
|
||||
"logs.calendar": "Calendario",
|
||||
"logs.calendar-title": "Calendario de Registros",
|
||||
"logs.clear-date-filter": "Limpiar filtro de fecha",
|
||||
"logs.clear-filter": "Limpiar filtro de búsqueda",
|
||||
"logs.completed": "Completado",
|
||||
"logs.execution-id": "ID de ejecución",
|
||||
"logs.exit-code": "Código de salida",
|
||||
"logs.filter-error": "No se pudo aplicar la expresión del filtro.",
|
||||
"logs.filter-help-examples": "Ejemplos: backup · !Update · Status != Completed · Status == Blocked · Action contains backup and Status == Completed",
|
||||
"logs.filter-help-fields": "Campos: Status, Action, User, Output, Blocked, TimedOut, Running, ExitCode. Operadores: ==, !=, contains. Un prefijo ! en una sola palabra excluye las entradas coincidentes.",
|
||||
"logs.filter-help-intro": "Los filtros se ejecutan en el servidor sobre las entradas que puede ver. Combine términos con and / or.",
|
||||
"logs.filter-help-title": "Sintaxis del filtro",
|
||||
"logs.filter-placeholder": "Filtrar registros (p. ej. !Update o Status != Completed)",
|
||||
"logs.metadata": "Metadatos",
|
||||
"logs.no-logs-for-filter": "Ningún registro coincide con el filtro actual.",
|
||||
"logs.no-logs-to-display": "No hay registros para mostrar.",
|
||||
"logs.page-description": "Esta es una lista de registros de acciones que han sido ejecutadas. Puede filtrar la lista por título de acción.",
|
||||
"logs.queue": "Cola",
|
||||
"logs.queue-action-details": "Detalles de la acción",
|
||||
"logs.queue-default-group": "Predeterminado",
|
||||
"logs.queue-empty": "No hay ejecuciones activas o en espera en este momento.",
|
||||
"logs.queue-entity": "Entidad",
|
||||
"logs.queue-group-active": "{active} activas (máx. {max})",
|
||||
"logs.queue-group-active-unlimited": "{active} activas",
|
||||
"logs.queue-page-description": "Ejecuciones activas y en espera agrupadas por grupo de acciones. Las entradas que no puede ver se ocultan.",
|
||||
"logs.queue-position": "#{position}",
|
||||
"logs.queue-running": "En ejecución",
|
||||
"logs.queue-title": "Cola de ejecución",
|
||||
"logs.queue-waiting": "En espera",
|
||||
"logs.status": "Estado",
|
||||
"logs.timed-out": "Tiempo agotado",
|
||||
"logs.timestamp": "Marca de tiempo",
|
||||
"logs.title": "Registros",
|
||||
"nav.actions": "Acciones",
|
||||
"nav.diagnostics": "Diagnósticos",
|
||||
"nav.entities": "Entidades",
|
||||
"nav.logs": "Registros",
|
||||
"nav.system": "Sistema",
|
||||
"raise-issue": "Reportar un problema en GitHub",
|
||||
"reconnecting": "Reconectando…",
|
||||
"return-to-index": "Volver a la página principal",
|
||||
"search-filter": "Filtrar página actual",
|
||||
"theme-dialog.close": "Cerrar",
|
||||
"theme-dialog.default": "Tema Predeterminado",
|
||||
"theme-dialog.title": "Seleccionar tema",
|
||||
"welcome": "Bienvenido a OliveTin"
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
{
|
||||
"connected": "Connesso",
|
||||
"diagnostics.browser-info": "Informazioni del browser",
|
||||
"diagnostics.browser-info-description": "Questa sezione ti consente di generare un rapporto dettagliato delle informazioni del tuo browser. Questo può essere utile durante la risoluzione dei problemi specifici del browser.",
|
||||
"diagnostics.config-issue-action": "Azione",
|
||||
"diagnostics.config-issue-argument": "Argomento",
|
||||
"diagnostics.config-issue-code": "Codice",
|
||||
"diagnostics.config-issue-config-file": "File di configurazione",
|
||||
"diagnostics.config-issue-detail": "Dettaglio",
|
||||
"diagnostics.config-issue-message": "Messaggio",
|
||||
"diagnostics.config-issue-severity": "Gravità",
|
||||
"diagnostics.config-issue-source": "Origine",
|
||||
"diagnostics.config-issues": "Problemi di configurazione",
|
||||
"diagnostics.config-issues-description": "Problemi rilevati nella configurazione corrente di OliveTin. Correggili nei file di configurazione e ricarica OliveTin.",
|
||||
"diagnostics.config-issues-none": "Nessun problema di configurazione rilevato.",
|
||||
"diagnostics.copied": "Copiato!",
|
||||
"diagnostics.copy-to-clipboard": "Copia negli appunti",
|
||||
"diagnostics.found-config": "Configurazione trovata",
|
||||
"diagnostics.found-key": "Chiave trovata",
|
||||
"diagnostics.generate-browser-info": "Genera informazioni del browser",
|
||||
"diagnostics.generate-server-diagnostics": "Genera diagnostica del server",
|
||||
"diagnostics.get-support": "Ottenere supporto",
|
||||
"diagnostics.get-support-description": "Se hai problemi con OliveTin e vuoi presentare una richiesta di supporto, sarebbe molto utile includere la diagnostica del server da questa pagina.",
|
||||
"diagnostics.server-diagnostics": "Diagnostica del server",
|
||||
"diagnostics.server-diagnostics-description": "Questa sezione ti consente di generare un rapporto dettagliato della tua configurazione e ambiente. È una buona idea includere questo quando si presenta una richiesta di supporto.",
|
||||
"diagnostics.server-diagnostics-docs": "Documentazione diagnostica del server",
|
||||
"diagnostics.ssh": "SSH",
|
||||
"diagnostics.unknown": "Sconosciuto",
|
||||
"diagnostics.useragent-data-error": "Errore nel recupero di userAgentData",
|
||||
"diagnostics.where-to-find-help": "Dove trovare aiuto",
|
||||
"disconnected": "Disconnesso",
|
||||
"disconnected-banner-announcement": "Websocket eventi disconnesso.",
|
||||
"disconnected-banner-link-text": "Websocket eventi disconnesso",
|
||||
"disconnected-banner-suffix": " dalle {disconnectedSince}. Nuovo tentativo tra {reconnectIn}.",
|
||||
"disconnected-banner-suffix-reconnecting": " dalle {disconnectedSince}. Tentativo di connessione…",
|
||||
"docs": "Documentazione",
|
||||
"language-dialog.browser-languages": "Lingue del browser",
|
||||
"language-dialog.close": "Chiudi",
|
||||
"language-dialog.not-available": "Non disponibile",
|
||||
"language-dialog.title": "Seleziona lingua",
|
||||
"login-button": "Login",
|
||||
"logs.action": "Azione",
|
||||
"logs.action-group-limits": "simultanei: {concurrent}, dimensione coda: {queueSize}",
|
||||
"logs.back-to-list": "Torna all'Elenco",
|
||||
"logs.blocked": "Bloccato",
|
||||
"logs.calendar": "Calendario",
|
||||
"logs.calendar-title": "Calendario dei Registri",
|
||||
"logs.clear-date-filter": "Cancella filtro data",
|
||||
"logs.clear-filter": "Cancella filtro di ricerca",
|
||||
"logs.completed": "Completato",
|
||||
"logs.execution-id": "ID esecuzione",
|
||||
"logs.exit-code": "Codice di uscita",
|
||||
"logs.filter-error": "Impossibile applicare l'espressione di filtro.",
|
||||
"logs.filter-help-examples": "Esempi: backup · !Update · Status != Completed · Status == Blocked · Action contains backup and Status == Completed",
|
||||
"logs.filter-help-fields": "Campi: Status, Action, User, Output, Blocked, TimedOut, Running, ExitCode. Operatori: ==, !=, contains. Un prefisso ! su una singola parola esclude le voci corrispondenti.",
|
||||
"logs.filter-help-intro": "I filtri vengono eseguiti sul server sulle voci che sei autorizzato a visualizzare. Combina i termini con and / or.",
|
||||
"logs.filter-help-title": "Sintassi del filtro",
|
||||
"logs.filter-placeholder": "Filtra i registri (es. !Update o Status != Completed)",
|
||||
"logs.metadata": "Metadati",
|
||||
"logs.no-logs-for-filter": "Nessun registro corrisponde al filtro corrente.",
|
||||
"logs.no-logs-to-display": "Non ci sono registri da mostrare.",
|
||||
"logs.page-description": "Questa è una lista di registri delle azioni che sono state eseguite. Puoi filtrare la lista per titolo dell'azione.",
|
||||
"logs.queue": "Coda",
|
||||
"logs.queue-action-details": "Dettagli azione",
|
||||
"logs.queue-default-group": "Predefinito",
|
||||
"logs.queue-empty": "Non ci sono esecuzioni attive o in attesa al momento.",
|
||||
"logs.queue-entity": "Entità",
|
||||
"logs.queue-group-active": "{active} attive (max {max})",
|
||||
"logs.queue-group-active-unlimited": "{active} attive",
|
||||
"logs.queue-page-description": "Esecuzioni attive e in attesa raggruppate per gruppo di azioni. Le voci non autorizzate sono nascoste.",
|
||||
"logs.queue-position": "#{position}",
|
||||
"logs.queue-running": "In esecuzione",
|
||||
"logs.queue-title": "Coda di esecuzione",
|
||||
"logs.queue-waiting": "In attesa",
|
||||
"logs.status": "Stato",
|
||||
"logs.timed-out": "Tempo scaduto",
|
||||
"logs.timestamp": "Date e ora",
|
||||
"logs.title": "Registri",
|
||||
"nav.actions": "Azioni",
|
||||
"nav.diagnostics": "Diagnostica",
|
||||
"nav.entities": "Entità",
|
||||
"nav.logs": "Registri",
|
||||
"nav.system": "Sistema",
|
||||
"raise-issue": "Segnala un problema su GitHub",
|
||||
"reconnecting": "Riconnessione…",
|
||||
"return-to-index": "Torna alla pagina principale",
|
||||
"search-filter": "Filtra la pagina corrente",
|
||||
"theme-dialog.close": "Chiudi",
|
||||
"theme-dialog.default": "Tema Predefinito",
|
||||
"theme-dialog.title": "Seleziona tema",
|
||||
"welcome": "Benvenuto in OliveTin"
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
{
|
||||
"connected": "已连接",
|
||||
"diagnostics.browser-info": "浏览器信息",
|
||||
"diagnostics.browser-info-description": "此部分允许您生成浏览器信息的详细报告。这在排查浏览器特定问题时很有帮助。",
|
||||
"diagnostics.config-issue-action": "操作",
|
||||
"diagnostics.config-issue-argument": "参数",
|
||||
"diagnostics.config-issue-code": "代码",
|
||||
"diagnostics.config-issue-config-file": "配置文件",
|
||||
"diagnostics.config-issue-detail": "详情",
|
||||
"diagnostics.config-issue-message": "消息",
|
||||
"diagnostics.config-issue-severity": "严重程度",
|
||||
"diagnostics.config-issue-source": "来源",
|
||||
"diagnostics.config-issues": "配置问题",
|
||||
"diagnostics.config-issues-description": "当前 OliveTin 配置中检测到的问题。请在配置文件中修复这些问题并重新加载 OliveTin。",
|
||||
"diagnostics.config-issues-none": "未检测到配置问题。",
|
||||
"diagnostics.copied": "已复制!",
|
||||
"diagnostics.copy-to-clipboard": "复制到剪贴板",
|
||||
"diagnostics.found-config": "找到配置",
|
||||
"diagnostics.found-key": "找到密钥",
|
||||
"diagnostics.generate-browser-info": "生成浏览器信息",
|
||||
"diagnostics.generate-server-diagnostics": "生成服务器诊断",
|
||||
"diagnostics.get-support": "获取支持",
|
||||
"diagnostics.get-support-description": "如果您在使用 OliveTin 时遇到问题并希望提交支持请求,从本页面包含服务器诊断将非常有帮助。",
|
||||
"diagnostics.server-diagnostics": "服务器诊断",
|
||||
"diagnostics.server-diagnostics-description": "此部分允许您生成配置和环境的详细报告。在提交支持请求时包含此信息是个好主意。",
|
||||
"diagnostics.server-diagnostics-docs": "服务器诊断文档",
|
||||
"diagnostics.ssh": "SSH",
|
||||
"diagnostics.unknown": "未知",
|
||||
"diagnostics.useragent-data-error": "检索 userAgentData 时出错",
|
||||
"diagnostics.where-to-find-help": "在哪里找到帮助",
|
||||
"disconnected": "已断开连接",
|
||||
"disconnected-banner-announcement": "事件 WebSocket 已断开。",
|
||||
"disconnected-banner-link-text": "事件 WebSocket 已断开",
|
||||
"disconnected-banner-suffix": "自 {disconnectedSince}。{reconnectIn} 后尝试重连。",
|
||||
"disconnected-banner-suffix-reconnecting": "自 {disconnectedSince}。正在尝试重连…",
|
||||
"docs": "文档",
|
||||
"language-dialog.browser-languages": "浏览器语言",
|
||||
"language-dialog.close": "关闭",
|
||||
"language-dialog.not-available": "不可用",
|
||||
"language-dialog.title": "选择语言",
|
||||
"login-button": "登录",
|
||||
"logs.action": "动作",
|
||||
"logs.action-group-limits": "并发:{concurrent},队列大小:{queueSize}",
|
||||
"logs.back-to-list": "返回列表",
|
||||
"logs.blocked": "阻塞",
|
||||
"logs.calendar": "日历",
|
||||
"logs.calendar-title": "日志日历",
|
||||
"logs.clear-date-filter": "清除日期筛选器",
|
||||
"logs.clear-filter": "清除搜索筛选器",
|
||||
"logs.completed": "完成",
|
||||
"logs.execution-id": "执行 ID",
|
||||
"logs.exit-code": "退出代码",
|
||||
"logs.filter-error": "无法应用过滤表达式。",
|
||||
"logs.filter-help-examples": "示例:backup · !Update · Status != Completed · Status == Blocked · Action contains backup and Status == Completed",
|
||||
"logs.filter-help-fields": "字段:Status, Action, User, Output, Blocked, TimedOut, Running, ExitCode。运算符:==, !=, contains。在单个单词前加上 ! 前缀可排除匹配的条目。",
|
||||
"logs.filter-help-intro": "过滤器会在服务器端针对您有权限查看的条目进行筛选。您可以使用 and / or 来组合搜索条件。",
|
||||
"logs.filter-help-title": "过滤语法",
|
||||
"logs.filter-placeholder": "过滤日志(例如 !Update 或 Status != Completed)",
|
||||
"logs.metadata": "元数据",
|
||||
"logs.no-logs-for-filter": "没有符合当前过滤条件的日志。",
|
||||
"logs.no-logs-to-display": "没有日志可显示。",
|
||||
"logs.page-description": "这是一个动作执行日志列表。您可以按动作标题过滤列表。",
|
||||
"logs.queue": "队列",
|
||||
"logs.queue-action-details": "动作详细信息",
|
||||
"logs.queue-default-group": "默认",
|
||||
"logs.queue-empty": "当前没有正在运行或等待中的执行。",
|
||||
"logs.queue-entity": "实体",
|
||||
"logs.queue-group-active": "{active} 个活动(上限 {max})",
|
||||
"logs.queue-group-active-unlimited": "{active} 个活动",
|
||||
"logs.queue-page-description": "按动作组分组显示正在运行和等待中的执行。您无权查看的条目会被隐藏。",
|
||||
"logs.queue-position": "第 {position} 位",
|
||||
"logs.queue-running": "运行中",
|
||||
"logs.queue-title": "执行队列",
|
||||
"logs.queue-waiting": "等待中",
|
||||
"logs.status": "状态",
|
||||
"logs.timed-out": "超时",
|
||||
"logs.timestamp": "时间戳",
|
||||
"logs.title": "日志",
|
||||
"nav.actions": "动作",
|
||||
"nav.diagnostics": "诊断",
|
||||
"nav.entities": "实体",
|
||||
"nav.logs": "日志",
|
||||
"nav.system": "系统",
|
||||
"raise-issue": "在 GitHub 上报告问题",
|
||||
"reconnecting": "正在重新连接…",
|
||||
"return-to-index": "返回首页",
|
||||
"search-filter": "过滤当前页面",
|
||||
"theme-dialog.close": "关闭",
|
||||
"theme-dialog.default": "默认主题",
|
||||
"theme-dialog.title": "选择主题",
|
||||
"welcome": "欢迎使用 OliveTin"
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
{
|
||||
"connected": "已連線",
|
||||
"diagnostics.browser-info": "瀏覽器資訊",
|
||||
"diagnostics.browser-info-description": "此區塊可讓您產生瀏覽器資訊的詳細報告。這在排解特定瀏覽器問題時相當有幫助。",
|
||||
"diagnostics.config-issue-action": "動作",
|
||||
"diagnostics.config-issue-argument": "參數",
|
||||
"diagnostics.config-issue-code": "代碼",
|
||||
"diagnostics.config-issue-config-file": "設定檔",
|
||||
"diagnostics.config-issue-detail": "詳細資料",
|
||||
"diagnostics.config-issue-message": "訊息",
|
||||
"diagnostics.config-issue-severity": "嚴重程度",
|
||||
"diagnostics.config-issue-source": "來源",
|
||||
"diagnostics.config-issues": "設定問題",
|
||||
"diagnostics.config-issues-description": "目前 OliveTin 設定中偵測到的問題。請在設定檔中修正這些問題並重新載入 OliveTin。",
|
||||
"diagnostics.config-issues-none": "未偵測到設定問題。",
|
||||
"diagnostics.copied": "已複製!",
|
||||
"diagnostics.copy-to-clipboard": "複製到剪貼簿",
|
||||
"diagnostics.found-config": "找到設定檔 (Config)",
|
||||
"diagnostics.found-key": "找到金鑰 (Key)",
|
||||
"diagnostics.generate-browser-info": "產生瀏覽器資訊",
|
||||
"diagnostics.generate-server-diagnostics": "產生伺服器診斷資訊",
|
||||
"diagnostics.get-support": "尋求技術支援",
|
||||
"diagnostics.get-support-description": "如果您在使用 OliveTin 時遇到問題並希望提出支援請求,提供此頁面的「伺服器診斷資訊」將會有很大的幫助。",
|
||||
"diagnostics.server-diagnostics": "伺服器診斷",
|
||||
"diagnostics.server-diagnostics-description": "此區塊可讓您產生詳細的設定與環境報告。建議在提出支援請求時附上此報告。",
|
||||
"diagnostics.server-diagnostics-docs": "伺服器診斷官方文件",
|
||||
"diagnostics.ssh": "SSH",
|
||||
"diagnostics.unknown": "未知",
|
||||
"diagnostics.useragent-data-error": "取得 userAgentData 時發生錯誤",
|
||||
"diagnostics.where-to-find-help": "哪裡可以尋求協助",
|
||||
"disconnected": "已斷線",
|
||||
"disconnected-banner-announcement": "系統事件 WebSocket 已斷線。",
|
||||
"disconnected-banner-link-text": "系統事件 WebSocket 已斷線",
|
||||
"disconnected-banner-suffix": " 自 {disconnectedSince} 起。將在 {reconnectIn} 後嘗試重新連線。",
|
||||
"disconnected-banner-suffix-reconnecting": " 自 {disconnectedSince} 起。嘗試重新連線中…",
|
||||
"docs": "官方文件",
|
||||
"language-dialog.browser-languages": "瀏覽器語言",
|
||||
"language-dialog.close": "關閉",
|
||||
"language-dialog.not-available": "無法使用",
|
||||
"language-dialog.title": "選擇語言",
|
||||
"login-button": "登入",
|
||||
"logs.action": "動作",
|
||||
"logs.action-group-limits": "並行數:{concurrent},佇列大小:{queueSize}",
|
||||
"logs.back-to-list": "返回清單",
|
||||
"logs.blocked": "已阻擋",
|
||||
"logs.calendar": "日曆",
|
||||
"logs.calendar-title": "日誌日曆",
|
||||
"logs.clear-date-filter": "清除日期過濾條件",
|
||||
"logs.clear-filter": "清除搜尋過濾條件",
|
||||
"logs.completed": "已完成",
|
||||
"logs.execution-id": "執行 ID",
|
||||
"logs.exit-code": "結束代碼",
|
||||
"logs.filter-error": "無法套用過濾運算式。",
|
||||
"logs.filter-help-examples": "範例:backup · !Update · Status != Completed · Status == Blocked · Action contains backup and Status == Completed",
|
||||
"logs.filter-help-fields": "欄位:Status, Action, User, Output, Blocked, TimedOut, Running, ExitCode。運算子:==, !=, contains。在單一單字前加上 ! 前綴可排除相符的項目。",
|
||||
"logs.filter-help-intro": "過濾器會在伺服器端針對您有權限檢視的項目進行篩選。您可以使用 and / or 來組合搜尋條件。",
|
||||
"logs.filter-help-title": "過濾語法",
|
||||
"logs.filter-placeholder": "過濾日誌 (例如 !Update 或 Status != Completed)",
|
||||
"logs.metadata": "中繼資料",
|
||||
"logs.no-logs-for-filter": "沒有符合目前過濾條件的日誌。",
|
||||
"logs.no-logs-to-display": "目前沒有可顯示的日誌。",
|
||||
"logs.page-description": "這是已執行動作的日誌清單。請使用過濾條件欄位來搜尋關鍵字與運算式。",
|
||||
"logs.queue": "佇列",
|
||||
"logs.queue-action-details": "動作詳細資訊",
|
||||
"logs.queue-default-group": "預設",
|
||||
"logs.queue-empty": "目前沒有使用中或等待中的執行項目。",
|
||||
"logs.queue-entity": "實體",
|
||||
"logs.queue-group-active": "{active} 個使用中 (最大值 {max})",
|
||||
"logs.queue-group-active-unlimited": "{active} 個使用中",
|
||||
"logs.queue-page-description": "依動作群組分類的使用中與等待中執行項目。您無權檢視的項目將會被隱藏。",
|
||||
"logs.queue-position": "#{position}",
|
||||
"logs.queue-running": "執行中",
|
||||
"logs.queue-title": "執行佇列",
|
||||
"logs.queue-waiting": "等待中",
|
||||
"logs.status": "狀態",
|
||||
"logs.timed-out": "執行逾時",
|
||||
"logs.timestamp": "時間戳記",
|
||||
"logs.title": "日誌",
|
||||
"nav.actions": "動作",
|
||||
"nav.diagnostics": "診斷",
|
||||
"nav.entities": "實體",
|
||||
"nav.logs": "日誌",
|
||||
"nav.system": "系統",
|
||||
"raise-issue": "在 GitHub 上建立 Issue",
|
||||
"reconnecting": "重新連線中…",
|
||||
"return-to-index": "返回首頁",
|
||||
"search-filter": "過濾目前頁面",
|
||||
"theme-dialog.close": "關閉",
|
||||
"theme-dialog.default": "預設佈景主題",
|
||||
"theme-dialog.title": "選擇佈景主題",
|
||||
"welcome": "歡迎使用 OliveTin"
|
||||
}
|
||||
+34
-10
@@ -25,23 +25,47 @@ type CombinedTranslationsOutput struct {
|
||||
|
||||
func main() {
|
||||
combinedContent := getCombinedLanguageContent()
|
||||
|
||||
sortedContent := sortTranslations(combinedContent)
|
||||
|
||||
jsonData, err := json.MarshalIndent(sortedContent, "", " ")
|
||||
|
||||
err := writeTranslationFiles(sortedContent.Messages)
|
||||
if err != nil {
|
||||
log.Fatalf("Error marshalling combined language content: %v", err)
|
||||
log.Fatalf("Error writing language content: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func writeTranslationFiles(messages map[string]map[string]string) error {
|
||||
err := os.RemoveAll("generated")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = os.Mkdir("generated", 0o755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for languageName, translations := range messages {
|
||||
err = writeTranslationFile(languageName, translations)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
log.Infof("Language content saved to generated/")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeTranslationFile(languageName string, translations map[string]string) error {
|
||||
jsonData, err := json.MarshalIndent(translations, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
jsonData = append(jsonData, '\n')
|
||||
err = os.WriteFile("combined_output.json", jsonData, 0644)
|
||||
filename := filepath.Join("generated", languageName+".json")
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("Error saving combined language content to file: %v", err)
|
||||
}
|
||||
|
||||
log.Infof("Combined language content saved to combined_output.json")
|
||||
return os.WriteFile(filename, jsonData, 0o644)
|
||||
}
|
||||
|
||||
// sortTranslations creates a new structure with sorted keys for deterministic output.
|
||||
|
||||
Reference in New Issue
Block a user