feat(client): support filtered application language resources (#10175)

This commit is contained in:
YANG QIA
2026-07-24 10:06:08 +08:00
committed by GitHub
parent 67322c9752
commit 7b8fda4ab9
4 changed files with 110 additions and 2 deletions
+16 -1
View File
@@ -13,7 +13,22 @@ export default {
tags: ['app'],
summary: 'Get the current application language',
description: 'Return the current locale used by the server.',
parameters: [],
parameters: [
{
name: 'locale',
in: 'query',
required: false,
schema: { type: 'string' },
description: 'Requested application locale. The server validates it against enabled languages.',
},
{
name: 'ns',
in: 'query',
required: false,
schema: { type: 'string' },
description: 'Comma-separated resource namespaces to return. Omit it to preserve the full legacy payload.',
},
],
responses: {
200: {
description: 'OK',
@@ -0,0 +1,54 @@
/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { describe, expect, it } from 'vitest';
import { filterLocaleResources, parseLocaleResourceNamespaces } from '../localeResources';
describe('locale resource filtering', () => {
it('accepts comma-separated and repeated namespace query values', () => {
expect(parseLocaleResourceNamespaces(['lm-collections,plugin-a', ' plugin-a , plugin-b '])).toEqual([
'lm-collections',
'plugin-a',
'plugin-b',
]);
});
it('preserves the legacy full payload when ns is omitted', () => {
const payload = {
resources: {
client: { Save: 'Save' },
'lm-collections': { Users: 'Users' },
},
antd: { locale: 'en' },
};
expect(filterLocaleResources(payload, undefined)).toBe(payload);
});
it('filters only the resources map and preserves other locale payloads', () => {
const payload = {
resources: {
client: { Save: 'Save' },
'lm-collections': { Users: 'Users' },
'plugin-a': { Title: 'Title' },
},
antd: { locale: 'en' },
cron: { every: 'every' },
};
expect(filterLocaleResources(payload, 'lm-collections,plugin-a,missing')).toEqual({
resources: {
'lm-collections': { Users: 'Users' },
'plugin-a': { Title: 'Title' },
},
antd: { locale: 'en' },
cron: { every: 'every' },
});
});
});
@@ -0,0 +1,38 @@
/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
export function parseLocaleResourceNamespaces(value: unknown): string[] {
const values = Array.isArray(value) ? value : [value];
return [
...new Set(
values
.flatMap((item) => (typeof item === 'string' ? item.split(',') : []))
.map((item) => item.trim())
.filter(Boolean),
),
];
}
export function filterLocaleResources<T extends { resources?: Record<string, unknown> }>(
payload: T,
namespaceQuery: unknown,
): T {
const namespaces = parseLocaleResourceNamespaces(namespaceQuery);
if (!namespaces.length) return payload;
const resources = payload.resources || {};
return {
...payload,
resources: Object.fromEntries(
namespaces
.filter((namespace) => Object.prototype.hasOwnProperty.call(resources, namespace))
.map((namespace) => [namespace, resources[namespace]]),
),
};
}
@@ -17,6 +17,7 @@ import { listAppPortals } from './appPortals';
import { getAntdLocale } from './antd';
import { getCronLocale } from './cron';
import { getCronstrueLocale } from './cronstrue';
import { filterLocaleResources } from './localeResources';
async function getLang(ctx) {
const SystemSetting = ctx.db.getRepository('systemSettings');
@@ -115,7 +116,7 @@ export class PluginClientServer extends Plugin {
},
async getLang(ctx, next) {
const lang = await getLang(ctx);
const resources = await ctx.app.localeManager.get(lang);
const resources = filterLocaleResources(await ctx.app.localeManager.get(lang), ctx.request.query.ns);
ctx.body = {
lang,
...resources,