0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-16 20:26:19 -05:00

refactor(core): optimize translation strictly partial check (#2333)

This commit is contained in:
Gao Sun 2022-11-07 19:29:19 +08:00 committed by GitHub
parent d42a19133d
commit 494c4ae483
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 30 additions and 43 deletions

View file

@ -40,13 +40,13 @@ jest.mock('@/queries/custom-phrase', () => ({
upsertCustomPhrase: async (customPhrase: CustomPhrase) => upsertCustomPhrase(customPhrase),
}));
const isValidStructure = jest.fn(
const isStrictlyPartial = jest.fn(
(fullTranslation: Translation, partialTranslation: Partial<Translation>) => true
);
jest.mock('@/utils/translation', () => ({
isValidStructure: (fullTranslation: Translation, partialTranslation: Translation) =>
isValidStructure(fullTranslation, partialTranslation),
isStrictlyPartial: (fullTranslation: Translation, partialTranslation: Translation) =>
isStrictlyPartial(fullTranslation, partialTranslation),
}));
const mockFallbackLanguage = trTrTag;
@ -130,13 +130,13 @@ describe('customPhraseRoutes', () => {
});
});
it('should call isValidStructure', async () => {
it('should call isStrictlyPartial', async () => {
await customPhraseRequest.put(`/custom-phrases/${mockLanguageTag}`).send(translation);
expect(isValidStructure).toBeCalledWith(en.translation, translation);
expect(isStrictlyPartial).toBeCalledWith(en.translation, translation);
});
it('should fail when the input translation structure is invalid', async () => {
isValidStructure.mockReturnValueOnce(false);
isStrictlyPartial.mockReturnValueOnce(false);
const response = await customPhraseRequest
.put(`/custom-phrases/${mockLanguageTag}`)
.send(translation);

View file

@ -15,7 +15,7 @@ import {
} from '@/queries/custom-phrase';
import { findDefaultSignInExperience } from '@/queries/sign-in-experience';
import assertThat from '@/utils/assert-that';
import { isValidStructure } from '@/utils/translation';
import { isStrictlyPartial } from '@/utils/translation';
import type { AuthedRouter } from './types';
@ -70,7 +70,7 @@ export default function customPhraseRoutes<T extends AuthedRouter>(router: T) {
const translation = cleanDeepTranslation(body);
assertThat(
isValidStructure(resource.en.translation, translation),
isStrictlyPartial(resource.en.translation, translation),
new RequestError('localization.invalid_translation_structure')
);

View file

@ -1,7 +1,7 @@
import en from '@logto/phrases-ui/lib/locales/en';
import fr from '@logto/phrases-ui/lib/locales/fr';
import { isValidStructure } from '@/utils/translation';
import { isStrictlyPartial } from '@/utils/translation';
const customizedFrTranslation = {
secondary: {
@ -10,15 +10,15 @@ const customizedFrTranslation = {
},
};
describe('isValidStructure', () => {
describe('isStrictlyPartial', () => {
it('should be true when its structure is valid', () => {
expect(isValidStructure(en.translation, fr.translation)).toBeTruthy();
expect(isValidStructure(en.translation, customizedFrTranslation)).toBeTruthy();
expect(isStrictlyPartial(en.translation, fr.translation)).toBeTruthy();
expect(isStrictlyPartial(en.translation, customizedFrTranslation)).toBeTruthy();
});
it('should be true when the structure is partial and the existing key-value pairs are correct', () => {
expect(
isValidStructure(en.translation, {
isStrictlyPartial(en.translation, {
secondary: {
sign_in_with: 'Se connecter avec {{methods, list(type: disjunction;)}}',
// Missing 'secondary.social_bind_with' key-value pair
@ -29,7 +29,7 @@ describe('isValidStructure', () => {
it('should be false when there is an unexpected key-value pair', () => {
expect(
isValidStructure(en.translation, {
isStrictlyPartial(en.translation, {
secondary: {
sign_in_with: 'Se connecter avec {{methods, list(type: disjunction;)}}',
social_bind_with:

View file

@ -1,38 +1,25 @@
import type { Translation } from '@logto/schemas';
// LOG-4385: Refactor me
// eslint-disable-next-line complexity
export const isValidStructure = (fullTranslation: Translation, partialTranslation: Translation) => {
const fullKeys = new Set(Object.keys(fullTranslation));
const partialKeys = Object.keys(partialTranslation);
if (fullKeys.size === 0 || partialKeys.length === 0) {
return true;
}
if (partialKeys.some((key) => !fullKeys.has(key))) {
return false;
}
for (const [key, value] of Object.entries(fullTranslation)) {
const targetValue = partialTranslation[key];
if (targetValue === undefined) {
continue;
}
if (typeof value === 'string') {
if (typeof targetValue === 'string') {
continue;
}
/**
* @param fullTranslation The translation with full keys
* @param partialTranslation The translation to check
* @returns If the flatten keys of `partialTranslation` is a subset of `fullTranslation`
*/
export const isStrictlyPartial = (
fullTranslation: Translation,
partialTranslation: Translation
): boolean => {
return Object.entries(partialTranslation).every(([key, value]) => {
const fullValue = fullTranslation[key];
if (!fullValue) {
return false;
}
if (typeof targetValue === 'string' || !isValidStructure(value, targetValue)) {
return false;
if (typeof fullValue === 'object' && typeof value === 'object') {
return isStrictlyPartial(fullValue, value);
}
}
return true;
return typeof fullValue === typeof value;
});
};