mirror of
https://github.com/go-gitea/gitea.git
synced 2026-07-02 09:01:59 +00:00
fix(locales): Replace hardcoded strings (#37788)
The Workflow Dependencies graph in the Actions run details view had hard-coded English strings. Also in projects view and contributors view I found some hard-coded strings. The other items in the issue #37787 (Summary / All jobs / Run Details / Workflow file / Triggered via / Total duration) were already wired through ctx.Locale.Tr; their translations just need to land in the non-English locale_*.json files via the translation pipeline. Fixes #37787 --------- Co-authored-by: silverwind <me@silverwind.io> Co-authored-by: Claude (Opus 4.8) <noreply@anthropic.com>
This commit is contained in:
@@ -2725,6 +2725,7 @@
|
||||
"graphs.code_frequency.what": "code frequency",
|
||||
"graphs.contributors.what": "contributions",
|
||||
"graphs.recent_commits.what": "recent commits",
|
||||
"graphs.chart_zoom_hint": "drag: zoom, shift+drag: pan, double click: reset zoom",
|
||||
"org.org_name_holder": "Organization Name",
|
||||
"org.org_full_name_holder": "Organization Full Name",
|
||||
"org.org_name_helper": "Organization names should be short and memorable.",
|
||||
@@ -3797,6 +3798,16 @@
|
||||
"actions.runs.latest_attempt": "Latest attempt",
|
||||
"actions.runs.triggered_via": "Triggered via %s",
|
||||
"actions.runs.total_duration": "Total duration:",
|
||||
"actions.runs.workflow_dependencies": "Workflow Dependencies",
|
||||
"actions.runs.graph_jobs_count_1": "%d job",
|
||||
"actions.runs.graph_jobs_count_n": "%d jobs",
|
||||
"actions.runs.graph_dependencies_count_1": "%d dependency",
|
||||
"actions.runs.graph_dependencies_count_n": "%d dependencies",
|
||||
"actions.runs.graph_success_rate": "%s success",
|
||||
"actions.runs.graph_zoom_in": "Zoom in (Ctrl/Cmd + scroll on graph)",
|
||||
"actions.runs.graph_zoom_max": "Already at 100% zoom",
|
||||
"actions.runs.graph_zoom_out": "Zoom out (Ctrl/Cmd + scroll on graph)",
|
||||
"actions.runs.graph_reset_view": "Reset view",
|
||||
"actions.workflow.disable": "Disable Workflow",
|
||||
"actions.workflow.disable_success": "Workflow '%s' disabled successfully.",
|
||||
"actions.workflow.enable": "Enable Workflow",
|
||||
|
||||
@@ -134,16 +134,16 @@
|
||||
|
||||
{{if $canWriteProject}}
|
||||
<div class="ui small modal" id="project-column-modal-edit">
|
||||
<div class="header">edit</div>
|
||||
<div class="header">{{ctx.Locale.Tr "repo.projects.column.edit"}}</div>
|
||||
<div class="content">
|
||||
<form class="ui form ignore-dirty" method="post" data-action-base-link="{{$.Link}}">
|
||||
<input class="project-column-id" type="hidden" name="id">
|
||||
<div class="required field">
|
||||
<label class="project-column-title-label" for="project-column-title-input">title</label>
|
||||
<label class="project-column-title-label" for="project-column-title-input">{{ctx.Locale.Tr "repo.projects.column.edit_title"}}</label>
|
||||
<input id="project-column-title-input" name="title" required>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="project-column-color-label" for="project-column-color-input">color</label>
|
||||
<label class="project-column-color-label" for="project-column-color-input">{{ctx.Locale.Tr "repo.projects.column.color"}}</label>
|
||||
<div class="color-picker-combo" data-global-init="initColorPicker">
|
||||
<input maxlength="7" placeholder="#c320f6" id="project-column-color-input" name="color">
|
||||
{{template "repo/issue/label_precolors"}}
|
||||
@@ -151,7 +151,7 @@
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button class="ui cancel button">{{ctx.Locale.Tr "settings.cancel"}}</button>
|
||||
<button type="submit" class="ui primary button project-column-button-save">save</button>
|
||||
<button type="submit" class="ui primary button project-column-button-save">{{ctx.Locale.Tr "save"}}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -39,5 +39,15 @@
|
||||
data-locale-copy-output="{{ctx.Locale.Tr "copy_output"}}"
|
||||
data-locale-logs-always-auto-scroll="{{ctx.Locale.Tr "actions.logs.always_auto_scroll"}}"
|
||||
data-locale-logs-always-expand-running="{{ctx.Locale.Tr "actions.logs.always_expand_running"}}"
|
||||
data-locale-workflow-dependencies="{{ctx.Locale.Tr "actions.runs.workflow_dependencies"}}"
|
||||
data-locale-graph-jobs-count-1="{{ctx.Locale.Tr "actions.runs.graph_jobs_count_1"}}"
|
||||
data-locale-graph-jobs-count-n="{{ctx.Locale.Tr "actions.runs.graph_jobs_count_n"}}"
|
||||
data-locale-graph-dependencies-count-1="{{ctx.Locale.Tr "actions.runs.graph_dependencies_count_1"}}"
|
||||
data-locale-graph-dependencies-count-n="{{ctx.Locale.Tr "actions.runs.graph_dependencies_count_n"}}"
|
||||
data-locale-graph-success-rate="{{ctx.Locale.Tr "actions.runs.graph_success_rate"}}"
|
||||
data-locale-graph-zoom-in="{{ctx.Locale.Tr "actions.runs.graph_zoom_in"}}"
|
||||
data-locale-graph-zoom-max="{{ctx.Locale.Tr "actions.runs.graph_zoom_max"}}"
|
||||
data-locale-graph-zoom-out="{{ctx.Locale.Tr "actions.runs.graph_zoom_out"}}"
|
||||
data-locale-graph-reset-view="{{ctx.Locale.Tr "actions.runs.graph_reset_view"}}"
|
||||
>
|
||||
</div>
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
data-locale-loading-title-failed="{{ctx.Locale.Tr "graphs.component_loading_failed" (ctx.Locale.Tr "graphs.contributors.what")}}"
|
||||
data-locale-loading-info="{{ctx.Locale.Tr "graphs.component_loading_info"}}"
|
||||
data-locale-component-failed-to-load="{{ctx.Locale.Tr "graphs.component_failed_to_load"}}"
|
||||
data-locale-chart-zoom-hint="{{ctx.Locale.Tr "graphs.chart_zoom_hint"}}"
|
||||
>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
@@ -59,6 +59,7 @@ onBeforeUnmount(() => {
|
||||
:jobs="run.jobs"
|
||||
:run-link="run.link"
|
||||
:workflow-id="run.workflowID"
|
||||
:locale="locale"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -270,7 +270,7 @@ export default defineComponent({
|
||||
plugins: {
|
||||
title: {
|
||||
display: type === 'main',
|
||||
text: 'drag: zoom, shift+drag: pan, double click: reset zoom',
|
||||
text: this.locale.chartZoomHint,
|
||||
position: 'top',
|
||||
align: 'center',
|
||||
},
|
||||
|
||||
@@ -4,6 +4,7 @@ import {SvgIcon} from '../svg.ts';
|
||||
import ActionStatusIcon from './ActionStatusIcon.vue';
|
||||
import {localUserSettings} from '../modules/user-settings.ts';
|
||||
import {isPlainClick} from '../utils/dom.ts';
|
||||
import {trN} from '../modules/i18n.ts';
|
||||
import {debounce} from 'throttle-debounce';
|
||||
import type {ActionsJob, ActionsStatus} from '../modules/gitea-actions.ts';
|
||||
import type {ActionRunViewStore} from './ActionRunView.ts';
|
||||
@@ -43,6 +44,7 @@ const props = defineProps<{
|
||||
jobs: ActionsJob[];
|
||||
runLink: string;
|
||||
workflowId: string;
|
||||
locale: Record<string, string>;
|
||||
}>()
|
||||
|
||||
const settingKeyStates = 'actions-graph-states';
|
||||
@@ -344,6 +346,12 @@ const graphMetrics = computed(() => {
|
||||
};
|
||||
})
|
||||
|
||||
const graphStats = computed(() => [
|
||||
trN(props.jobs.length, props.locale.graphJobsCount1, props.locale.graphJobsCountN),
|
||||
trN(edges.value.length, props.locale.graphDependenciesCount1, props.locale.graphDependenciesCountN),
|
||||
props.locale.graphSuccessRate.replace('%s', graphMetrics.value.successRate),
|
||||
].join(' • '))
|
||||
|
||||
const nodeHeight = 52;
|
||||
const verticalSpacing = 90;
|
||||
const margin = 40;
|
||||
@@ -543,27 +551,22 @@ function onNodeClick(job: JobNode, event: MouseEvent) {
|
||||
<template>
|
||||
<div class="workflow-graph" v-if="jobs.length > 0">
|
||||
<div class="graph-header">
|
||||
<h4 class="graph-title">Workflow Dependencies</h4>
|
||||
<div class="graph-stats">
|
||||
{{ jobs.length }} jobs • {{ edges.length }} dependencies
|
||||
<span v-if="graphMetrics">
|
||||
• <span class="graph-metrics">{{ graphMetrics.successRate }} success</span>
|
||||
</span>
|
||||
</div>
|
||||
<h4 class="graph-title">{{ locale.workflowDependencies }}</h4>
|
||||
<div class="graph-stats">{{ graphStats }}</div>
|
||||
<div class="flex-text-block">
|
||||
<button
|
||||
type="button"
|
||||
@click="zoomIn"
|
||||
class="ui compact tiny icon button"
|
||||
:disabled="!canZoomIn"
|
||||
:title="canZoomIn ? 'Zoom in (Ctrl/Cmd + scroll on graph)' : 'Already at 100% zoom'"
|
||||
:title="canZoomIn ? locale.graphZoomIn : locale.graphZoomMax"
|
||||
>
|
||||
<SvgIcon name="octicon-zoom-in" :size="12"/>
|
||||
</button>
|
||||
<button type="button" @click="resetView" class="ui compact tiny icon button" title="Reset view">
|
||||
<button type="button" @click="resetView" class="ui compact tiny icon button" :title="locale.graphResetView">
|
||||
<SvgIcon name="octicon-sync" :size="12"/>
|
||||
</button>
|
||||
<button type="button" @click="zoomOut" class="ui compact tiny icon button" title="Zoom out (Ctrl/Cmd + scroll on graph)">
|
||||
<button type="button" @click="zoomOut" class="ui compact tiny icon button" :title="locale.graphZoomOut">
|
||||
<SvgIcon name="octicon-zoom-out" :size="12"/>
|
||||
</button>
|
||||
</div>
|
||||
@@ -701,11 +704,6 @@ function onNodeClick(job: JobNode, event: MouseEvent) {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.graph-metrics {
|
||||
color: var(--color-primary);
|
||||
font-weight: var(--font-weight-medium);
|
||||
}
|
||||
|
||||
.graph-container {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
|
||||
@@ -20,6 +20,7 @@ export async function initRepoContributors() {
|
||||
loadingTitle: el.getAttribute('data-locale-loading-title'),
|
||||
loadingTitleFailed: el.getAttribute('data-locale-loading-title-failed'),
|
||||
loadingInfo: el.getAttribute('data-locale-loading-info'),
|
||||
chartZoomHint: el.getAttribute('data-locale-chart-zoom-hint'),
|
||||
},
|
||||
});
|
||||
View.mount(el);
|
||||
|
||||
@@ -53,6 +53,16 @@ export function initRepositoryActionView() {
|
||||
logsAlwaysExpandRunning: el.getAttribute('data-locale-logs-always-expand-running'),
|
||||
workflowFile: el.getAttribute('data-locale-workflow-file'),
|
||||
runDetails: el.getAttribute('data-locale-run-details'),
|
||||
workflowDependencies: el.getAttribute('data-locale-workflow-dependencies'),
|
||||
graphJobsCount1: el.getAttribute('data-locale-graph-jobs-count-1'),
|
||||
graphJobsCountN: el.getAttribute('data-locale-graph-jobs-count-n'),
|
||||
graphDependenciesCount1: el.getAttribute('data-locale-graph-dependencies-count-1'),
|
||||
graphDependenciesCountN: el.getAttribute('data-locale-graph-dependencies-count-n'),
|
||||
graphSuccessRate: el.getAttribute('data-locale-graph-success-rate'),
|
||||
graphZoomIn: el.getAttribute('data-locale-graph-zoom-in'),
|
||||
graphZoomMax: el.getAttribute('data-locale-graph-zoom-max'),
|
||||
graphZoomOut: el.getAttribute('data-locale-graph-zoom-out'),
|
||||
graphResetView: el.getAttribute('data-locale-graph-reset-view'),
|
||||
},
|
||||
});
|
||||
view.mount(el);
|
||||
|
||||
15
web_src/js/modules/i18n.test.ts
Normal file
15
web_src/js/modules/i18n.test.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import {trN} from './i18n.ts';
|
||||
import {getCurrentLocale} from '../utils.ts';
|
||||
|
||||
vi.mock('../utils.ts', () => ({getCurrentLocale: vi.fn()}));
|
||||
|
||||
test('trN', () => {
|
||||
vi.mocked(getCurrentLocale).mockReturnValue('en-US');
|
||||
expect(trN(0, '%d job', '%d jobs')).toEqual('0 jobs');
|
||||
expect(trN(1, '%d job', '%d jobs')).toEqual('1 job');
|
||||
expect(trN(2, '%d job', '%d jobs')).toEqual('2 jobs');
|
||||
expect(trN(1000, '%d job', '%d jobs')).toEqual('1000 jobs');
|
||||
// languages without a distinct singular always use the plural form
|
||||
vi.mocked(getCurrentLocale).mockReturnValue('zh-CN');
|
||||
expect(trN(1, '%d job', '%d jobs')).toEqual('1 jobs');
|
||||
});
|
||||
7
web_src/js/modules/i18n.ts
Normal file
7
web_src/js/modules/i18n.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import {getCurrentLocale} from '../utils.ts';
|
||||
|
||||
/** frontend `Locale.TrN`: pick the `_1` or `_n` form for `count` and interpolate `%d` */
|
||||
export function trN(count: number, form1: string, formN: string): string {
|
||||
const form = new Intl.PluralRules(getCurrentLocale()).select(count) === 'one' ? form1 : formN;
|
||||
return form.replace('%d', String(count));
|
||||
}
|
||||
Reference in New Issue
Block a user