enhance: ユーザーのロール一覧画面の多言語対応

This commit is contained in:
kakkokari-gtyih 2025-07-07 19:03:31 +09:00
parent 7f4280f9d5
commit a0a0c8a4ed
8 changed files with 284 additions and 238 deletions

View File

@ -107,7 +107,7 @@ import FormSlot from '@/components/form/slot.vue';
import { i18n } from '@/i18n.js';
import { instance } from '@/instance.js';
import { deepClone } from '@/utility/clone.js';
import type { RolePolicySettingsRecord } from './roles.policy-editor.def.js';
import type { RolePolicySettingsRecord } from '@/utility/role-policy.js';
const emit = defineEmits<{
(ev: 'update:modelValue', v: any): void;

View File

@ -1,190 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { i18n } from '@/i18n.js';
import XUploadableFileTypesCaption from './roles.policy-editor.uploadableFileTypesCaption.vue';
import type {
RolePolicyEditorDef,
GetRolePolicyEditorValuesType,
RolePolicyValueRecord as _RolePolicyValueRecord,
RolePolicySettingsRecord as _RolePolicySettingsRecord,
} from '@/types/role-policy-editor.js';
export const rolePolicyEditorDef = {
rateLimitFactor: {
type: 'range',
folderLabel: i18n.ts._role._options.rateLimitFactor,
folderSuffix: (value) => `${Math.round(value * 100)}%`,
min: 0.3,
max: 3,
step: 0.1,
textConverter: (value) => `${Math.round(value * 100)}%`,
inputCaption: i18n.ts._role._options.descriptionOfRateLimitFactor,
},
gtlAvailable: {
type: 'boolean',
folderLabel: i18n.ts._role._options.gtlAvailable,
},
ltlAvailable: {
type: 'boolean',
folderLabel: i18n.ts._role._options.ltlAvailable,
},
canPublicNote: {
type: 'boolean',
folderLabel: i18n.ts._role._options.canPublicNote,
},
chatAvailability: {
type: 'enum',
folderLabel: i18n.ts._role._options.chatAvailability,
enum: [
{ label: i18n.ts.enabled, value: 'available' },
{ label: i18n.ts.readonly, value: 'readonly' },
{ label: i18n.ts.disabled, value: 'unavailable' },
],
},
mentionLimit: {
type: 'number',
folderLabel: i18n.ts._role._options.mentionMax,
},
canInvite: {
type: 'boolean',
folderLabel: i18n.ts._role._options.canInvite,
},
inviteLimit: {
type: 'number',
folderLabel: i18n.ts._role._options.inviteLimit,
},
inviteLimitCycle: {
type: 'number',
folderLabel: i18n.ts._role._options.inviteLimitCycle,
folderSuffix: (value) => `${value} ${i18n.ts._time.minute}`,
inputSuffix: i18n.ts._time.minute,
},
inviteExpirationTime: {
type: 'number',
folderLabel: i18n.ts._role._options.inviteExpirationTime,
folderSuffix: (value) => `${value} ${i18n.ts._time.minute}`,
inputSuffix: i18n.ts._time.minute,
},
canManageAvatarDecorations: {
type: 'boolean',
folderLabel: i18n.ts._role._options.canManageAvatarDecorations,
},
canManageCustomEmojis: {
type: 'boolean',
folderLabel: i18n.ts._role._options.canManageCustomEmojis,
},
canSearchNotes: {
type: 'boolean',
folderLabel: i18n.ts._role._options.canSearchNotes,
},
canUseTranslator: {
type: 'boolean',
folderLabel: i18n.ts._role._options.canUseTranslator,
},
driveCapacityMb: {
type: 'number',
folderLabel: i18n.ts._role._options.driveCapacity,
folderSuffix: (value) => `${value} MB`,
inputSuffix: 'MB',
},
maxFileSizeMb: {
type: 'number',
folderLabel: i18n.ts._role._options.maxFileSize,
folderSuffix: (value) => `${value} MB`,
inputSuffix: 'MB',
},
uploadableFileTypes: {
type: 'string',
multiline: true,
folderLabel: i18n.ts._role._options.uploadableFileTypes,
folderSuffix: '...',
inputCaption: XUploadableFileTypesCaption,
},
alwaysMarkNsfw: {
type: 'boolean',
folderLabel: i18n.ts._role._options.alwaysMarkNsfw,
},
canUpdateBioMedia: {
type: 'boolean',
folderLabel: i18n.ts._role._options.canUpdateBioMedia,
},
pinLimit: {
type: 'number',
folderLabel: i18n.ts._role._options.pinMax,
},
antennaLimit: {
type: 'number',
folderLabel: i18n.ts._role._options.antennaMax,
},
wordMuteLimit: {
type: 'number',
folderLabel: i18n.ts._role._options.wordMuteMax,
inputSuffix: 'chars',
},
webhookLimit: {
type: 'number',
folderLabel: i18n.ts._role._options.webhookMax,
},
clipLimit: {
type: 'number',
folderLabel: i18n.ts._role._options.clipMax,
},
noteEachClipsLimit: {
type: 'number',
folderLabel: i18n.ts._role._options.noteEachClipsMax,
},
userListLimit: {
type: 'number',
folderLabel: i18n.ts._role._options.userListMax,
},
userEachUserListsLimit: {
type: 'number',
folderLabel: i18n.ts._role._options.userEachUserListsMax,
},
canHideAds: {
type: 'boolean',
folderLabel: i18n.ts._role._options.canHideAds,
},
avatarDecorationLimit: {
type: 'number',
folderLabel: i18n.ts._role._options.avatarDecorationLimit,
min: 0,
max: 16,
},
canImportAntennas: {
type: 'boolean',
folderLabel: i18n.ts._role._options.canImportAntennas,
},
canImportBlocking: {
type: 'boolean',
folderLabel: i18n.ts._role._options.canImportBlocking,
},
canImportFollowing: {
type: 'boolean',
folderLabel: i18n.ts._role._options.canImportFollowing,
},
canImportMuting: {
type: 'boolean',
folderLabel: i18n.ts._role._options.canImportMuting,
},
canImportUserLists: {
type: 'boolean',
folderLabel: i18n.ts._role._options.canImportUserLists,
},
noteDraftLimit: {
type: 'number',
folderLabel: i18n.ts._role._options.noteDraftLimit,
min: 0,
},
} satisfies RolePolicyEditorDef;
export type RolePolicyValueRecord = _RolePolicyValueRecord<typeof rolePolicyEditorDef>;
export type RolePolicySettingsRecord = _RolePolicySettingsRecord<typeof rolePolicyEditorDef>;
export type RolePolicyRecord = {
[K in keyof typeof rolePolicyEditorDef]: GetRolePolicyEditorValuesType<typeof rolePolicyEditorDef[K]>;
};

View File

@ -90,8 +90,8 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkSwitch>
</template>
<script lang="ts" setup generic="D extends RolePolicyEditorItem">
import type { RolePolicyEditorItem, GetRolePolicyEditorValuesType } from '@/types/role-policy-editor.js';
<script lang="ts" setup generic="D extends RolePolicyDefItem">
import type { RolePolicyDefItem, GetRolePolicyEditorValuesType } from '@/types/role-policy-editor.js';
import { i18n } from '@/i18n.js';
import MkInput from '@/components/MkInput.vue';
@ -108,7 +108,7 @@ const props = defineProps<{
const model = defineModel<GetRolePolicyEditorValuesType<D>>({ required: true });
function assertModelType<T extends RolePolicyEditorItem['type']>(m: unknown, type: T): m is GetRolePolicyEditorValuesType<Extract<RolePolicyEditorItem, { type: T }>> {
function assertModelType<T extends RolePolicyDefItem['type']>(m: unknown, type: T): m is GetRolePolicyEditorValuesType<Extract<RolePolicyDefItem, { type: T }>> {
return props.def.type === type;
}
</script>

View File

@ -10,13 +10,10 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkInput>
<MkFolder v-for="(def, key) in filteredDefs" :key="key">
<template #label>{{ def.folderLabel }}</template>
<template #label>{{ def.displayLabel }}</template>
<template #suffix>
<span v-if="withUseDefault && model[key].useDefault">{{ i18n.ts._role.useBaseValue }}</span>
<span v-else-if="'folderSuffix' in def">{{ typeof def.folderSuffix === 'string' ? def.folderSuffix : (def as RolePolicyEditorItemBaseFolderSuffixGetter).folderSuffix(model[key].value) }}</span>
<span v-else-if="def.type === 'boolean'">{{ model[key].value === true ? i18n.ts.yes : i18n.ts.no }}</span>
<span v-else-if="def.type === 'enum'">{{ def.enum.find((v) => v.value === model[key].value)?.label ?? model[key].value }}</span>
<span v-else>{{ model[key].value }}</span>
<span v-else>{{ getPolicyDisplayValue(def, model[key].value) }}</span>
<span v-if="withPriority" :class="$style.priorityIndicator"><i :class="getPriorityIcon(model[key].priority)"></i></span>
</template>
@ -57,8 +54,8 @@ import MkFolder from '@/components/MkFolder.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import MkRange from '@/components/MkRange.vue';
import XForm from './roles.policy-editor.form.vue';
import { rolePolicyEditorDef } from './roles.policy-editor.def.js';
import type { GetRolePolicyEditorValuesType, RolePolicyEditorDef, RolePolicyEditorItemBaseFolderSuffixGetter } from '@/types/role-policy-editor.js';
import { rolePolicyDef, getPolicyDisplayValue } from '@/utility/role-policy.js';
import type { GetRolePolicyEditorValuesType, RolePolicyDefItem } from '@/types/role-policy-editor.js';
const props = withDefaults(defineProps<{
withUseDefault: UD;
@ -75,14 +72,14 @@ type RemoveNever<T> = {
};
type RolePolicyEditorValueItem = {
value: GetRolePolicyEditorValuesType<typeof rolePolicyEditorDef[keyof typeof rolePolicyEditorDef]>;
value: GetRolePolicyEditorValuesType<typeof rolePolicyDef[keyof typeof rolePolicyDef]>;
} & RemoveNever<
(UD extends true ? { useDefault: boolean } : { useDefault: never })
& (WP extends true ? { priority: 0 | 1 | 2 } : { priority: never })
>;
type RolePolicyEditorValue = {
[K in keyof typeof rolePolicyEditorDef]: RolePolicyEditorValueItem;
[K in keyof typeof rolePolicyDef]: RolePolicyEditorValueItem;
};
const model = defineModel<RolePolicyEditorValue>({ required: true });
@ -95,15 +92,15 @@ function matchQuery(keywords: string[]) {
}
const filteredDefs = computed(() => {
if (!props.withSearchBar) return rolePolicyEditorDef;
if (!props.withSearchBar) return rolePolicyDef;
return Object.fromEntries(
Object.entries(rolePolicyEditorDef as RolePolicyEditorDef).filter(([key, def]) => {
Object.entries(rolePolicyDef).filter(([key, def]) => {
if (searchQuery.value.trim().length === 0) return true;
const matchTerms = [
key,
def.folderLabel,
...(def.searchTerms ?? []),
def.displayLabel,
...((def as RolePolicyDefItem).searchTerms ? (def as RolePolicyDefItem).searchTerms ?? [] : []),
];
return matchQuery(matchTerms);
}),

View File

@ -41,7 +41,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { computed, ref } from 'vue';
import type { RolePolicyRecord, RolePolicyValueRecord } from './roles.policy-editor.def.js';
import type { RolePolicyRecord, RolePolicyValueRecord } from '@/utility/role-policy.js';
import MkFolder from '@/components/MkFolder.vue';
import MkButton from '@/components/MkButton.vue';
import XPolicyEditor from './roles.policy-editor.vue';

View File

@ -39,9 +39,10 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #label><SearchLabel>{{ i18n.ts._role.policies }}</SearchLabel></template>
<div class="_gaps_s">
<div v-for="policy in Object.keys($i.policies)" :key="policy">
{{ policy }} ... {{ $i.policies[policy] }}
</div>
<MkKeyValue v-for="(value, pKey) in policiesKv" :key="pKey">
<template #key>{{ value.label }}</template>
<template #value>{{ value.value }}</template>
</MkKeyValue>
</div>
</MkFolder>
</SearchMarker>
@ -151,16 +152,15 @@ import MkKeyValue from '@/components/MkKeyValue.vue';
import MkButton from '@/components/MkButton.vue';
import FormSlot from '@/components/form/slot.vue';
import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { ensureSignin } from '@/i.js';
import { i18n } from '@/i18n.js';
import { definePage } from '@/page.js';
import { reloadAsk } from '@/utility/reload-ask.js';
import FormSection from '@/components/form/section.vue';
import { prefer } from '@/preferences.js';
import MkRolePreview from '@/components/MkRolePreview.vue';
import { signout } from '@/signout.js';
import { migrateOldSettings } from '@/pref-migrate.js';
import { rolePolicyDef, getPolicyDisplayValue } from '@/utility/role-policy.js';
import { hideAllTips as _hideAllTips, resetAllTips as _resetAllTips } from '@/tips.js';
const $i = ensureSignin();
@ -172,6 +172,22 @@ const devMode = prefer.model('devMode');
const stackingRouterView = prefer.model('experimental.stackingRouterView');
const enableFolderPageView = prefer.model('experimental.enableFolderPageView');
const policiesKv = computed(() => {
return Object.fromEntries(
Object.entries($i.policies).map(([key, def]) => {
const _key = key as keyof typeof $i.policies | keyof typeof rolePolicyDef;
const value = getPolicyDisplayValue(rolePolicyDef[_key], def, true);
return [_key, {
label: rolePolicyDef[_key]?.displayLabel ?? _key,
value,
}];
}),
) as Record<keyof typeof $i.policies, {
label: string;
value: string;
}>;
});
watch(skipNoteRender, async () => {
await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
});

View File

@ -5,24 +5,24 @@
import type { Component } from 'vue';
import type { ROLE_POLICIES } from '@@/js/const.js';
interface RolePolicyEditorItemBase<T = unknown> {
interface RolePolicyDefItemBase<T extends unknown = any> {
searchTerms?: string[];
type: string;
folderLabel: string;
folderSuffix?: string | ((value: T) => string);
displayLabel: string;
displayValue?: string | ((value: T, full?: boolean) => string);
inputLabel?: string;
inputCaption?: string | Component;
}
export type RolePolicyEditorItemBaseFolderSuffixGetter = {
folderSuffix: (value: boolean | number | string) => string;
export type RolePolicyDefItemBaseDisplayValueGetter = {
displayValue: (value: boolean | number | string, full?: boolean) => string;
};
interface RolePolicyEditorItemBoolean extends RolePolicyEditorItemBase<boolean> {
interface RolePolicyDefItemBoolean extends RolePolicyDefItemBase<boolean> {
type: 'boolean';
}
interface RolePolicyEditorItemNumber extends RolePolicyEditorItemBase<number> {
interface RolePolicyDefItemNumber extends RolePolicyDefItemBase<number> {
type: 'number';
min?: number;
max?: number;
@ -30,7 +30,7 @@ interface RolePolicyEditorItemNumber extends RolePolicyEditorItemBase<number> {
inputSuffix?: string;
}
interface RolePolicyEditorItemRange extends RolePolicyEditorItemBase<number> {
interface RolePolicyDefItemRange extends RolePolicyDefItemBase<number> {
type: 'range';
min: number;
max: number;
@ -40,12 +40,12 @@ interface RolePolicyEditorItemRange extends RolePolicyEditorItemBase<number> {
inputSuffix?: string;
}
interface RolePolicyEditorItemString extends RolePolicyEditorItemBase<string> {
interface RolePolicyDefItemString extends RolePolicyDefItemBase<string> {
type: 'string';
multiline?: boolean;
}
interface RolePolicyEditorItemEnum extends RolePolicyEditorItemBase<string> {
interface RolePolicyDefItemEnum extends RolePolicyDefItemBase<string> {
type: 'enum';
enum: {
label: string;
@ -53,30 +53,30 @@ interface RolePolicyEditorItemEnum extends RolePolicyEditorItemBase<string> {
}[];
}
export type RolePolicyEditorItem =
RolePolicyEditorItemBoolean |
RolePolicyEditorItemNumber |
RolePolicyEditorItemRange |
RolePolicyEditorItemString |
RolePolicyEditorItemEnum;
export type RolePolicyDefItem =
RolePolicyDefItemBoolean |
RolePolicyDefItemNumber |
RolePolicyDefItemRange |
RolePolicyDefItemString |
RolePolicyDefItemEnum;
export type RolePolicyEditorDef = Record<typeof ROLE_POLICIES[number], RolePolicyEditorItem>;
export type RolePolicyDef = Record<typeof ROLE_POLICIES[number], RolePolicyDefItem>;
export type GetRolePolicyEditorValuesType<T extends RolePolicyEditorItem> =
T extends RolePolicyEditorItemBoolean ? boolean :
T extends RolePolicyEditorItemNumber ? number :
T extends RolePolicyEditorItemRange ? number :
T extends RolePolicyEditorItemString ? string :
T extends RolePolicyEditorItemEnum ? T['enum'][number]['value'] :
export type GetRolePolicyEditorValuesType<T extends RolePolicyDefItem> =
T extends RolePolicyDefItemBoolean ? boolean :
T extends RolePolicyDefItemNumber ? number :
T extends RolePolicyDefItemRange ? number :
T extends RolePolicyDefItemString ? string :
T extends RolePolicyDefItemEnum ? T['enum'][number]['value'] :
never;
export type RolePolicyValueRecord<T extends Record<string, RolePolicyEditorItem>> = {
export type RolePolicyValueRecord<T extends Record<string, RolePolicyDefItem>> = {
[K in keyof T]: {
value: GetRolePolicyEditorValuesType<T[K]>;
};
};
export type RolePolicySettingsRecord<T extends Record<string, RolePolicyEditorItem>> = {
export type RolePolicySettingsRecord<T extends Record<string, RolePolicyDefItem>> = {
[K in keyof T]: {
value: GetRolePolicyEditorValuesType<T[K]>;
useDefault: boolean;

View File

@ -0,0 +1,223 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { defineAsyncComponent } from 'vue';
import { i18n } from '@/i18n.js';
import type {
RolePolicyDef,
GetRolePolicyEditorValuesType,
RolePolicyValueRecord as _RolePolicyValueRecord,
RolePolicySettingsRecord as _RolePolicySettingsRecord,
} from '@/types/role-policy-editor.js';
export const rolePolicyDef = {
rateLimitFactor: {
type: 'range',
displayLabel: i18n.ts._role._options.rateLimitFactor,
displayValue: (value) => `${Math.round(value * 100)}%`,
min: 0.3,
max: 3,
step: 0.1,
textConverter: (value) => `${Math.round(value * 100)}%`,
inputCaption: i18n.ts._role._options.descriptionOfRateLimitFactor,
},
gtlAvailable: {
type: 'boolean',
displayLabel: i18n.ts._role._options.gtlAvailable,
},
ltlAvailable: {
type: 'boolean',
displayLabel: i18n.ts._role._options.ltlAvailable,
},
canPublicNote: {
type: 'boolean',
displayLabel: i18n.ts._role._options.canPublicNote,
},
chatAvailability: {
type: 'enum',
displayLabel: i18n.ts._role._options.chatAvailability,
enum: [
{ label: i18n.ts.enabled, value: 'available' as const },
{ label: i18n.ts.readonly, value: 'readonly' as const },
{ label: i18n.ts.disabled, value: 'unavailable' as const },
],
},
mentionLimit: {
type: 'number',
displayLabel: i18n.ts._role._options.mentionMax,
},
canInvite: {
type: 'boolean',
displayLabel: i18n.ts._role._options.canInvite,
},
inviteLimit: {
type: 'number',
displayLabel: i18n.ts._role._options.inviteLimit,
},
inviteLimitCycle: {
type: 'number',
displayLabel: i18n.ts._role._options.inviteLimitCycle,
displayValue: (value) => `${value} ${i18n.ts._time.minute}`,
inputSuffix: i18n.ts._time.minute,
},
inviteExpirationTime: {
type: 'number',
displayLabel: i18n.ts._role._options.inviteExpirationTime,
displayValue: (value) => `${value} ${i18n.ts._time.minute}`,
inputSuffix: i18n.ts._time.minute,
},
canManageAvatarDecorations: {
type: 'boolean',
displayLabel: i18n.ts._role._options.canManageAvatarDecorations,
},
canManageCustomEmojis: {
type: 'boolean',
displayLabel: i18n.ts._role._options.canManageCustomEmojis,
},
canSearchNotes: {
type: 'boolean',
displayLabel: i18n.ts._role._options.canSearchNotes,
},
canUseTranslator: {
type: 'boolean',
displayLabel: i18n.ts._role._options.canUseTranslator,
},
driveCapacityMb: {
type: 'number',
displayLabel: i18n.ts._role._options.driveCapacity,
displayValue: (value) => `${value} MB`,
inputSuffix: 'MB',
},
maxFileSizeMb: {
type: 'number',
displayLabel: i18n.ts._role._options.maxFileSize,
displayValue: (value) => `${value} MB`,
inputSuffix: 'MB',
},
uploadableFileTypes: {
type: 'string',
multiline: true,
displayLabel: i18n.ts._role._options.uploadableFileTypes,
displayValue: (value, full) => full === true ? Array.isArray(value) ? value.join(', ') : value : '...',
inputCaption: defineAsyncComponent(() => import('@/pages/admin/roles.policy-editor.uploadableFileTypesCaption.vue')),
},
alwaysMarkNsfw: {
type: 'boolean',
displayLabel: i18n.ts._role._options.alwaysMarkNsfw,
},
canUpdateBioMedia: {
type: 'boolean',
displayLabel: i18n.ts._role._options.canUpdateBioMedia,
},
pinLimit: {
type: 'number',
displayLabel: i18n.ts._role._options.pinMax,
},
antennaLimit: {
type: 'number',
displayLabel: i18n.ts._role._options.antennaMax,
},
wordMuteLimit: {
type: 'number',
displayLabel: i18n.ts._role._options.wordMuteMax,
inputSuffix: 'chars',
},
webhookLimit: {
type: 'number',
displayLabel: i18n.ts._role._options.webhookMax,
},
clipLimit: {
type: 'number',
displayLabel: i18n.ts._role._options.clipMax,
},
noteEachClipsLimit: {
type: 'number',
displayLabel: i18n.ts._role._options.noteEachClipsMax,
},
userListLimit: {
type: 'number',
displayLabel: i18n.ts._role._options.userListMax,
},
userEachUserListsLimit: {
type: 'number',
displayLabel: i18n.ts._role._options.userEachUserListsMax,
},
canHideAds: {
type: 'boolean',
displayLabel: i18n.ts._role._options.canHideAds,
},
avatarDecorationLimit: {
type: 'number',
displayLabel: i18n.ts._role._options.avatarDecorationLimit,
min: 0,
max: 16,
},
canImportAntennas: {
type: 'boolean',
displayLabel: i18n.ts._role._options.canImportAntennas,
},
canImportBlocking: {
type: 'boolean',
displayLabel: i18n.ts._role._options.canImportBlocking,
},
canImportFollowing: {
type: 'boolean',
displayLabel: i18n.ts._role._options.canImportFollowing,
},
canImportMuting: {
type: 'boolean',
displayLabel: i18n.ts._role._options.canImportMuting,
},
canImportUserLists: {
type: 'boolean',
displayLabel: i18n.ts._role._options.canImportUserLists,
},
noteDraftLimit: {
type: 'number',
displayLabel: i18n.ts._role._options.noteDraftLimit,
min: 0,
},
} satisfies RolePolicyDef;
export type RolePolicyValueRecord = _RolePolicyValueRecord<typeof rolePolicyDef>;
export type RolePolicySettingsRecord = _RolePolicySettingsRecord<typeof rolePolicyDef>;
export type RolePolicyRecord = {
[K in keyof typeof rolePolicyDef]: GetRolePolicyEditorValuesType<typeof rolePolicyDef[K]>;
};
type RolePolicyDefDef = typeof rolePolicyDef[keyof typeof rolePolicyDef];
export function getPolicyDisplayValue<D extends RolePolicyDefDef, T = GetRolePolicyEditorValuesType<D>>(
policyDef: D,
value: T,
full = false,
): string {
if ('displayValue' in policyDef && policyDef.displayValue != null) {
if (typeof policyDef.displayValue === 'string') {
return policyDef.displayValue;
} else {
return policyDef.displayValue(value as never, full);
}
}
if (policyDef.type === 'enum') {
const enumItem = policyDef.enum.find(item => item.value === value);
if (enumItem) {
return enumItem.label;
} else {
return value as string;
}
} else if (policyDef.type === 'boolean') {
return value ? i18n.ts.yes : i18n.ts.no;
} else if (typeof value === 'number') {
return value.toString();
} else if (typeof value === 'string') {
return value;
} else {
return i18n.ts.unknown;
}
}