Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(transloco): support nested array of scopes #650

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion libs/transloco/src/lib/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ProviderScope, Translation } from './types';
import { NestedArray, ProviderScope, Translation } from './types';
import { flatten as _flatten, unflatten as _unflatten } from 'flat';

export function getValue<T>(obj: T, path: keyof T) {
Expand Down Expand Up @@ -130,3 +130,9 @@ export function unflatten(obj: Translation): Translation {
export function flatten(obj: Translation): Translation {
return _flatten(obj, { safe: true });
}

export function flattenArray<T>(arr: NestedArray<T>): T[] {
// not using "Infinity" to avoid call stack issues if someone passes an array that references itself
// "100 as 1" is used to trick TS to not attempt to resolve that level of nesting
return arr.flat(100 as 1) as T[];
}
4 changes: 2 additions & 2 deletions libs/transloco/src/lib/scope-resolver.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { TranslocoScope, ProviderScope, MaybeArray } from './types';
import { TranslocoScope, ProviderScope } from './types';
import { TranslocoService } from './transloco.service';
import { isScopeObject, toCamelCase } from './helpers';

type ScopeResolverParams = {
inline: string | undefined;
provider: MaybeArray<TranslocoScope>;
provider: TranslocoScope;
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this was done unintentionally, scope resolver is not able to process array of scopes. There's no compilation error only because of isScopeObject type guard, that is also not supposed to handle array of scopes

};

export class ScopeResolver {
Expand Down
115 changes: 73 additions & 42 deletions libs/transloco/src/lib/tests/directive/multi-scope-alias.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,13 @@ import {
} from '@ngneat/spectator';
import { createFactory } from './shared';
import { providersMock, runLoader } from '../mocks';
import { Component } from '@angular/core';
import { Component, Provider } from '@angular/core';
import { TranslocoDirective } from '../../transloco.directive';
import { TRANSLOCO_SCOPE } from '../../transloco-scope';
import { TranslocoModule } from '../../transloco.module';

describe('Scope alias', () => {
let spectator: SpectatorHost<TranslocoDirective>;

const createHost = createFactory([
function getScopeProviders(): Provider[] {
return [
{
provide: TRANSLOCO_SCOPE,
useValue: {
Expand All @@ -31,17 +29,52 @@ describe('Scope alias', () => {
},
multi: true,
},
]);
];
}

function getNestedScopeProviders(): Provider[] {
return [
{
provide: TRANSLOCO_SCOPE,
useValue: [
{
scope: 'admin-page',
alias: 'adminPageAlias',
},
{
scope: 'lazy-page',
alias: 'lazyPage',
},
],
multi: true,
},
];
}

describe('Scope alias', () => {
let spectator: SpectatorHost<TranslocoDirective>;

const createHostWithSeparateScopes = createFactory(getScopeProviders());
const createHostWithNestedScopes = createFactory(getScopeProviders());
const template = `<section *transloco="let t;">
<div>
{{t('adminPageAlias.title')}}<br />
{{t('lazyPage.title')}}
</div>
</section>
`;

it('should support multiple scopes with aliases provided separately', fakeAsync(() => {
spectator = createHostWithSeparateScopes(template);
runLoader();
runLoader();
spectator.detectChanges();
expect(spectator.query('div')).toHaveText('Admin english', false);
expect(spectator.query('div')).toHaveText('Admin Lazy english', false);
}));

it('should support multiple scopes with aliases', fakeAsync(() => {
spectator = createHost(`
<section *transloco="let t;">
<div>
{{t('adminPageAlias.title')}}<br />
{{t('lazyPage.title')}}
</div>
</section>
`);
it('should support nested scopes with aliases', fakeAsync(() => {
spectator = createHostWithNestedScopes(template);
runLoader();
runLoader();
spectator.detectChanges();
Expand All @@ -52,41 +85,39 @@ describe('Scope alias', () => {

@Component({
template: `
<p>{{ 'lazy.title' | transloco }}</p>
<span>{{ 'admin.title' | transloco }}</span>
<p>{{ 'lazyPage.title' | transloco }}</p>
<span>{{ 'adminPageAlias.title' | transloco }}</span>
<h1>{{ 'nested.title' | transloco }}</h1>
`,
})
class TestPipe {}

describe('Scope alias pipe', () => {
let spectator: Spectator<TestPipe>;
const createComponent = createComponentFactory({
component: TestPipe,
imports: [TranslocoModule],
providers: [
providersMock,
{
provide: TRANSLOCO_SCOPE,
useValue: {
scope: 'lazy-page',
alias: 'lazy',
},
multi: true,
},
{
provide: TRANSLOCO_SCOPE,
useValue: {
scope: 'admin-page',
alias: 'admin',
},
multi: true,
},
],
});
const createComponentFactoryWithProviders = (providers: Provider[]) =>
createComponentFactory({
component: TestPipe,
imports: [TranslocoModule],
providers: [providersMock, providers],
});
const createComponentSeparateScopes = createComponentFactoryWithProviders(
getScopeProviders()
);
const createComponentNestedScopes = createComponentFactoryWithProviders(
getNestedScopeProviders()
);

it('should support multiple scope aliases provided separately', fakeAsync(() => {
spectator = createComponentSeparateScopes();
runLoader();
runLoader();
spectator.detectChanges();
expect(spectator.query('p')).toHaveText('Admin Lazy english');
expect(spectator.query('span')).toHaveText('Admin english');
}));

it('should support multiple scope aliasses', fakeAsync(() => {
spectator = createComponent();
it('should support nested scope aliases', fakeAsync(() => {
spectator = createComponentNestedScopes();
runLoader();
runLoader();
spectator.detectChanges();
Expand Down
3 changes: 2 additions & 1 deletion libs/transloco/src/lib/transloco.directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
} from './shared';
import { LangResolver } from './lang-resolver';
import { ScopeResolver } from './scope-resolver';
import { flattenArray } from './helpers';

type TranslateFn = (key: string, params?: HashMap) => any;
interface ViewContext {
Expand Down Expand Up @@ -107,7 +108,7 @@ export class TranslocoDirective implements OnInit, OnDestroy, OnChanges {

return Array.isArray(this.providerScope)
? forkJoin(
(<TranslocoScope[]>this.providerScope).map((providerScope) =>
flattenArray(this.providerScope).map((providerScope) =>
this.resolveScope(lang, providerScope)
)
)
Expand Down
3 changes: 2 additions & 1 deletion libs/transloco/src/lib/transloco.pipe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
} from './shared';
import { LangResolver } from './lang-resolver';
import { ScopeResolver } from './scope-resolver';
import { flattenArray } from './helpers';

@Pipe({
name: 'transloco',
Expand Down Expand Up @@ -79,7 +80,7 @@ export class TranslocoPipe implements PipeTransform, OnDestroy {

return Array.isArray(this.providerScope)
? forkJoin(
(<TranslocoScope[]>this.providerScope).map((providerScope) =>
flattenArray(this.providerScope).map((providerScope) =>
this.resolveScope(lang, providerScope)
)
)
Expand Down
3 changes: 2 additions & 1 deletion libs/transloco/src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ export interface ProviderScope {
loader?: InlineLoader;
alias?: string;
}
export type MaybeArray<T> = T | T[];
export type NestedArray<T> = Array<T> | Array<NestedArray<T>>;
export type MaybeArray<T> = T | NestedArray<T>;
export type TranslocoScope = ProviderScope | string | undefined;
export type InlineLoader = HashMap<() => Promise<Translation>>;
export interface LoadOptions {
Expand Down
2 changes: 1 addition & 1 deletion libs/transloco/tsconfig.lib.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"declarationMap": true,
"inlineSources": true,
"types": [],
"lib": ["dom", "es2018"]
"lib": ["dom", "es2019"]
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to be able to use Array.prototype.flat()

},
"exclude": ["src/test-setup.ts", "**/*.spec.ts"],
"include": ["**/*.ts"]
Expand Down
7 changes: 1 addition & 6 deletions nx.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,7 @@
"default": {
"runner": "nx/tasks-runners/default",
"options": {
"cacheableOperations": [
"build",
"lint",
"test",
"e2e"
],
"cacheableOperations": ["build", "lint", "test", "e2e"],
"parallel": 1
}
}
Expand Down