mirror of
https://github.com/go-gitea/gitea.git
synced 2026-08-03 15:31:51 +02:00
refactor: introduce trString for frontend (#38741)
Co-authored-by: silverwind <me@silverwind.io> Co-authored-by: delvh <dev.lh@web.de>
This commit is contained in:
co-authored by
silverwind
delvh
parent
cb4c65f035
commit
c2ebe3724f
@@ -1,7 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import WorkflowGraph from './WorkflowGraph.vue';
|
||||
import type {ActionRunViewStore} from "./ActionRunView.ts";
|
||||
import {computed, onBeforeUnmount, onMounted, toRefs} from "vue";
|
||||
import type {ActionRunViewStore} from './ActionRunView.ts';
|
||||
import {computed, onBeforeUnmount, onMounted, toRefs} from 'vue';
|
||||
import {trString} from '../modules/i18n.ts';
|
||||
|
||||
defineOptions({
|
||||
name: 'ActionRunSummaryView',
|
||||
@@ -41,7 +42,7 @@ const triggerUser = computed(() => {
|
||||
|
||||
const triggerLabel = computed(() => {
|
||||
if (isRerun.value) return locale.rerunTriggered;
|
||||
return locale.triggeredVia.replace('%s', run.value.triggerEvent);
|
||||
return trString(locale.triggeredVia, run.value.triggerEvent);
|
||||
});
|
||||
|
||||
const artifactsDisplay = computed(() => props.artifactCount > 0 ? String(props.artifactCount) : '–');
|
||||
|
||||
@@ -9,6 +9,7 @@ import ActionRunJobView from './ActionRunJobView.vue';
|
||||
import type {ActionsJob, ActionsRunAttempt} from '../modules/gitea-actions.ts';
|
||||
import {buildJobsByParentJobID, createActionRunViewStore} from './ActionRunView.ts';
|
||||
import {buildArtifactTooltipHtml} from './ActionRunArtifacts.ts';
|
||||
import {trString} from '../modules/i18n.ts';
|
||||
|
||||
defineOptions({
|
||||
name: 'RepoActionView',
|
||||
@@ -115,7 +116,7 @@ function approveRun() {
|
||||
}
|
||||
|
||||
async function deleteArtifact(name: string) {
|
||||
if (!window.confirm(locale.confirmDeleteArtifact.replace('%s', name))) return;
|
||||
if (!window.confirm(trString(locale.confirmDeleteArtifact, name))) return;
|
||||
await DELETE(buildArtifactLink(name));
|
||||
await store.forceReloadCurrentRun();
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import {showErrorToast} from '../modules/toast.ts';
|
||||
import {GET} from '../modules/fetch.ts';
|
||||
import {pathEscapeSegments} from '../utils/url.ts';
|
||||
import type {GitRefType} from '../types.ts';
|
||||
import {trString} from '../modules/i18n.ts';
|
||||
|
||||
type ListItem = {
|
||||
selected: boolean;
|
||||
@@ -121,6 +122,7 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
methods: {
|
||||
trString,
|
||||
selectItem(item: ListItem) {
|
||||
this.menuVisible = false;
|
||||
if (this.refFormActionTemplate) {
|
||||
@@ -261,11 +263,11 @@ export default defineComponent({
|
||||
<div class="item" v-if="showCreateNewRef" :class="{active: activeItemIndex === filteredItems.length}" :ref="'listItem' + filteredItems.length" @click="createNewRef()">
|
||||
<div v-if="selectedTab === 'tags'">
|
||||
<svg-icon name="octicon-tag" class="tw-mr-1"/>
|
||||
<span v-text="textCreateTag.replace('%s', searchTerm)"/>
|
||||
<span v-text="trString(textCreateTag, searchTerm)"/>
|
||||
</div>
|
||||
<div v-else>
|
||||
<svg-icon name="octicon-git-branch" class="tw-mr-1"/>
|
||||
<span v-text="textCreateBranch.replace('%s', searchTerm)"/>
|
||||
<span v-text="trString(textCreateBranch, searchTerm)"/>
|
||||
</div>
|
||||
<div class="tw-text-xs">
|
||||
{{ textCreateRefFrom.replace('%s', currentRefShortName) }}
|
||||
|
||||
@@ -4,7 +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 {trN, trString} from '../modules/i18n.ts';
|
||||
import {debounce} from '../utils/func.ts';
|
||||
import type {ActionsJob} from '../modules/gitea-actions.ts';
|
||||
import type {ActionRunViewStore} from './ActionRunView.ts';
|
||||
@@ -113,7 +113,7 @@ const successRateLabel = 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', successRateLabel.value),
|
||||
trString(props.locale.graphSuccessRate, successRateLabel.value),
|
||||
].join(' • '));
|
||||
|
||||
const minScale = 0.3;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {createApp} from 'vue';
|
||||
import {translateMonth, translateDay} from '../utils.ts';
|
||||
import {GET} from '../modules/fetch.ts';
|
||||
import {trString} from '../modules/i18n.ts';
|
||||
|
||||
type HeatmapResponse = {
|
||||
heatmapData: Array<[number, number]>; // [[1617235200, 2]] = [unix timestamp, count]
|
||||
@@ -29,7 +30,7 @@ export async function initHeatmap() {
|
||||
});
|
||||
|
||||
const totalFormatted = totalContributions.toLocaleString();
|
||||
const textTotalContributions = el.getAttribute('data-locale-total-contributions')!.replace('%s', totalFormatted);
|
||||
const textTotalContributions = trString(el.getAttribute('data-locale-total-contributions')!, totalFormatted);
|
||||
|
||||
// last heatmap tooltip localization attempt https://github.com/go-gitea/gitea/pull/24131/commits/a83761cbbae3c2e3b4bced71e680f44432073ac8
|
||||
const locale = {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {diffTreeStore, diffTreeStoreSetViewed} from '../modules/diff-file.ts';
|
||||
import {setFileFolding} from './file-fold.ts';
|
||||
import {POST} from '../modules/fetch.ts';
|
||||
import {trString} from '../modules/i18n.ts';
|
||||
|
||||
const {pageData} = window.config;
|
||||
// it is undefined on most pages, fortunately, when it is accessed by the related functions, it exists
|
||||
@@ -15,9 +16,8 @@ function refreshViewedFilesSummary() {
|
||||
const viewedFilesProgress = document.querySelector('#viewed-files-summary')!;
|
||||
viewedFilesProgress.setAttribute('value', String(prReview.numberOfViewedFiles));
|
||||
const summaryLabel = document.querySelector<HTMLElement>('#viewed-files-summary-label')!;
|
||||
summaryLabel.textContent = summaryLabel.getAttribute('data-text-changed-template')!
|
||||
.replace('%[1]d', String(prReview.numberOfViewedFiles))
|
||||
.replace('%[2]d', String(prReview.numberOfFiles));
|
||||
const trText = summaryLabel.getAttribute('data-text-changed-template')!;
|
||||
summaryLabel.textContent = trString(trText, prReview.numberOfViewedFiles, prReview.numberOfFiles);
|
||||
}
|
||||
|
||||
// Initializes a listener for viewed-file checkboxes
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {toggleElem} from '../utils/dom.ts';
|
||||
import {showFomanticModal} from '../modules/fomantic/modal.ts';
|
||||
import {trString} from '../modules/i18n.ts';
|
||||
|
||||
export function initRepoBranchButton() {
|
||||
initRepoCreateBranchButton();
|
||||
@@ -36,7 +37,7 @@ function initRepoRenameBranchButton() {
|
||||
toggleElem(warn, el.getAttribute('data-is-default-branch') === 'true');
|
||||
|
||||
const text = modal.querySelector('[data-rename-branch-to]')!;
|
||||
text.textContent = text.getAttribute('data-rename-branch-to')!.replace('%s', oldBranchName);
|
||||
text.textContent = trString(text.getAttribute('data-rename-branch-to')!, oldBranchName);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type {FomanticInitFunction} from '../../types.ts';
|
||||
import {generateElemId, queryElems} from '../../utils/dom.ts';
|
||||
import {trString} from '../i18n.ts';
|
||||
|
||||
const ariaPatchKey = '_giteaAriaPatchDropdown';
|
||||
const fomanticDropdownFn = $.fn.dropdown;
|
||||
@@ -63,7 +64,7 @@ function updateSelectionLabel(label: HTMLElement) {
|
||||
const deleteIcon = label.querySelector('.delete.icon');
|
||||
if (deleteIcon) {
|
||||
deleteIcon.setAttribute('aria-hidden', 'false');
|
||||
deleteIcon.setAttribute('aria-label', window.config.i18n.remove_label_str.replace('%s', label.getAttribute('data-value')!));
|
||||
deleteIcon.setAttribute('aria-label', trString(window.config.i18n.remove_label_str, label.getAttribute('data-value')!));
|
||||
deleteIcon.setAttribute('role', 'button');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {trN} from './i18n.ts';
|
||||
import {trN, trString} from './i18n.ts';
|
||||
|
||||
test('trN', () => {
|
||||
expect(trN(0, '%d job', '%d jobs', {lang: 'en-US'})).toEqual('0 jobs');
|
||||
@@ -8,3 +8,10 @@ test('trN', () => {
|
||||
// languages without a distinct singular always use the plural form
|
||||
expect(trN(1, '%d job', '%d jobs', {lang: 'zh-CN'})).toEqual('1 jobs');
|
||||
});
|
||||
|
||||
test('trString', () => {
|
||||
expect(trString('from %s to %d', 'main', 12)).toBe('from main to 12');
|
||||
expect(trString('from %[1]s to %[2]d', 'main', 12)).toBe('from main to 12');
|
||||
expect(trString('from %s to %[2]d', 'main', 12)).toBe('from main to 12');
|
||||
expect(trString('from %s to %[3]d', 'main', 12)).toBe('from main to %[3]d');
|
||||
});
|
||||
|
||||
@@ -2,6 +2,21 @@ 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, {lang = getCurrentLocale()}: {lang?: string} = {}): string {
|
||||
const form = new Intl.PluralRules(lang).select(count) === 'one' ? form1 : formN;
|
||||
return form.replace('%d', String(count));
|
||||
const text = new Intl.PluralRules(lang).select(count) === 'one' ? form1 : formN;
|
||||
return trString(text, String(count));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills variables in a translated string (like TrString in Golang)
|
||||
* Only `%s`, `%d` or one-based position like `%[1]s` are supported.
|
||||
*/
|
||||
export function trString(s: string, ...args: Array<string | number>) {
|
||||
// the same behavior (almost) as backend TrString
|
||||
let curIdx = 0;
|
||||
return s.replace(/%%|%(?:\[([1-9]\d*)\])?([sd])/g, (match, indexed: string) => {
|
||||
if (match === '%%') return '%';
|
||||
const argIndex = indexed ? Number(indexed) - 1 : curIdx++;
|
||||
if (argIndex < 0 || argIndex >= args.length) return match;
|
||||
return String(args[argIndex]);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user