Skip to content

Commit

Permalink
Merge branch 'main' into dependabot/npm_and_yarn/ejs-3.1.10
Browse files Browse the repository at this point in the history
  • Loading branch information
rubiin authored Sep 8, 2024
2 parents 365aa95 + 3ea3606 commit b99ee72
Show file tree
Hide file tree
Showing 22 changed files with 455 additions and 153 deletions.
5 changes: 4 additions & 1 deletion docs/guides/nested.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,13 @@ Here is an example of how this looks in your translation files
```

```typescript
i18n.t('test.PAGE_HOME.TITLE', {args: {} })
// => Home to this World

i18n.t('test.PAGE_HOME.SUBTITLE', {args: { username: 'Toon' } })
// => Hello Toon, this is the home page
```

:::tip
The [`formatter`](guides/formatting.md) is applied before doing nested translations. This way you can pass on arguments to your nested translations! 🎉
:::
:::
2 changes: 1 addition & 1 deletion docs/guides/type-safety.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ For now type safety is optional and need to be enabled. We're planning to make a

You can also use the generated types in your DTOs. This way you can reduce the chance of having a typo in your validation messages.

```typescript title="src/craete-user.dto.ts"
```typescript title="src/create-user.dto.ts"
import { I18nTranslations } from './generated/i18n.generated.ts';
import {
IsEmail,
Expand Down
7 changes: 6 additions & 1 deletion docs/quick-start.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -326,11 +326,16 @@ import { I18nContext, I18nService } from 'nestjs-i18n';
export class AppService {
constructor(private readonly i18n: I18nService) {}
getHello(): string {
return this.i18n.t('test.HELLO',{ lang: I18nContext.current().lang });
return this.i18n.t('test.HELLO');
}

getHelloInSpecificLanguage(): string {
return this.i18n.t('test.HELLO',{ lang: "en" });
}
}
```


## Translate options

```typescript
Expand Down
205 changes: 98 additions & 107 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
{
"name": "nestjs-i18n",
"version": "10.4.5",
"version": "10.4.7",
"homepage": "https://nestjs-i18n.com",
"description": "The i18n module for Nest.",
"author": "Toon van Strijp",
"license": "MIT",
"engines": {
"node": ">=16"
"node": ">=18"
},
"keywords": [
"nestjs",
Expand Down
8 changes: 4 additions & 4 deletions src/filters/i18n-validation-exception.filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,18 +90,18 @@ export class I18nValidationExceptionFilter implements ExceptionFilter {
protected buildResponseBody(
host: ArgumentsHost,
exc: I18nValidationException,
errors: string[] | I18nValidationError[] | object,
error: string[] | I18nValidationError[] | object,
) {
if ('responseBodyFormatter' in this.options) {
return this.options.responseBodyFormatter(host, exc, errors);
return this.options.responseBodyFormatter(host, exc, error);
} else {
return {
statusCode:
this.options.errorHttpStatusCode === undefined
? exc.getStatus()
: this.options.errorHttpStatusCode,
message: exc.getResponse(),
errors,
message: error,
error: exc.getResponse(),
};
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/i18n.context.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ArgumentsHost } from '@nestjs/common';
import { AsyncLocalStorage } from 'async_hooks';
import { I18nTranslator, I18nValidationError } from './interfaces';
import { I18nOptions, I18nTranslator, I18nValidationError } from './interfaces';
import { I18nService, TranslateOptions } from './services/i18n.service';
import { Path, PathValue } from './types';
import { getContextObject } from './utils';
Expand All @@ -16,7 +16,7 @@ export class I18nContext<K = Record<string, unknown>>
return this;
}

constructor(readonly lang: string, readonly service: I18nService<K>) {}
constructor(readonly lang: string, readonly service: I18nService<K>, readonly i18nOptions: I18nOptions,) {}

public translate<P extends Path<K> = any, R = PathValue<K, P>>(
key: P,
Expand Down Expand Up @@ -64,7 +64,7 @@ export class I18nContext<K = Record<string, unknown>>
const i18n = this.storage.getStore() as I18nContext<K> | undefined;

if (!i18n && !!context) {
return getContextObject(context)?.i18nContext;
return getContextObject(i18n.i18nOptions,context)?.i18nContext;
}

return i18n;
Expand Down
4 changes: 2 additions & 2 deletions src/interceptors/i18n-language.interceptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export class I18nLanguageInterceptor implements NestInterceptor {
const i18nContext = I18nContext.current();
let language = null;

const ctx = getContextObject(context);
const ctx = getContextObject(this.i18nOptions,context);

// Skip interceptor if language is already resolved (in case of http middleware) or when ctx is undefined (unsupported context)
if (ctx === undefined || !!ctx.i18nLang) {
Expand Down Expand Up @@ -68,7 +68,7 @@ export class I18nLanguageInterceptor implements NestInterceptor {
}

if (!i18nContext) {
ctx.i18nContext = new I18nContext(ctx.i18nLang, this.i18nService);
ctx.i18nContext = new I18nContext(ctx.i18nLang, this.i18nService, this.i18nOptions);

if (!this.i18nOptions.skipAsyncHook) {
return new Observable((observer) => {
Expand Down
8 changes: 6 additions & 2 deletions src/middlewares/i18n.middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export class I18nMiddleware implements NestMiddleware {
req.app.locals.i18nLang = req.i18nLang;
}

req.i18nContext = new I18nContext(req.i18nLang, this.i18nService);
req.i18nContext = new I18nContext(req.i18nLang, this.i18nService, this.i18nOptions);

if (this.i18nOptions.skipAsyncHook) {
next();
Expand All @@ -92,7 +92,11 @@ export class I18nMiddleware implements NestMiddleware {
class MiddlewareHttpContext
implements ExecutionContext, ArgumentsHost, HttpArgumentsHost
{
constructor(private req: any, private res: any, private next: any) {}
constructor(
private req: any,
private res: any,
private next: any,
) {}

getClass<T = any>(): Type<T> {
throw ExecutionContextMethodNotImplemented;
Expand Down
9 changes: 7 additions & 2 deletions src/services/i18n.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ import {
take,
takeUntil,
} from 'rxjs';
import { I18nOptions, I18nTranslation, I18nValidationError } from '..';
import {
I18nContext,
I18nOptions,
I18nTranslation,
I18nValidationError,
} from '..';
import {
I18N_LANGUAGES,
I18N_LANGUAGES_SUBJECT,
Expand Down Expand Up @@ -75,7 +80,7 @@ export class I18nService<K = Record<string, unknown>>
options?: TranslateOptions,
): IfAnyOrNever<R, string, R> {
options = {
lang: this.i18nOptions.fallbackLanguage,
lang: I18nContext.current()?.lang || this.i18nOptions.fallbackLanguage,
...options,
};

Expand Down
6 changes: 5 additions & 1 deletion src/utils/context.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { ArgumentsHost, ExecutionContext, Logger } from '@nestjs/common';
import { I18nOptions } from '..';

const logger = new Logger('I18nService');

export function getContextObject(
i18nOptions: I18nOptions,
context?: ExecutionContext | ArgumentsHost,
): any {
const contextType = context?.getType<string>() ?? 'undefined';
Expand All @@ -17,6 +19,8 @@ export function getContextObject(
case 'rmq':
return context.getArgs()[1];
default:
logger.warn(`context type: ${contextType} not supported`);
if(i18nOptions.logging){
logger.warn(`context type: ${contextType} not supported`);
}
}
}
2 changes: 1 addition & 1 deletion src/utils/typescript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export const convertObjectToTypeDefinition = async (
return [];
};

const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed, });
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });

export const createTypesFile = async (object: any) => {
const sourceFile = ts.createSourceFile(
Expand Down
8 changes: 8 additions & 0 deletions tests/app/cats/cat.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@ export class CatResolver {
return cat;
}

@Query('catUsingService')
async getCatUsingService(@Args('id') id: number) {
const cat = await this.catService.findById(id);
// we manually overwrite this property to indicate a value that is translated!
cat.description = this.i18nService.translate('test.cat');
return cat;
}

@Mutation('createCat')
async create(@Args('createCatInput') args: CreateCatInput): Promise<any> {
await this.pubSub.publish('catAdded', { catAdded: args.name });
Expand Down
3 changes: 2 additions & 1 deletion tests/app/cats/cat.types.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ type Query {
cats: [Cat]
cat(id: Int!): Cat
catUsingContext(id: Int!): Cat
catUsingService(id: Int!): Cat
}

type Mutation {
Expand All @@ -23,4 +24,4 @@ type Subscription {
input CreateCatInput {
name: String
age: Int
}
}
29 changes: 28 additions & 1 deletion tests/app/controllers/hello.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ import { CreateUserDto } from '../dto/create-user.dto';
import { TestException, TestExceptionFilter } from '../filter/test.filter';
import { TestGuard } from '../guards/test.guard';
import { Hero, HeroById } from '../interfaces/hero.interface';
import { exampleErrorFormatter, exampleResponseBodyFormatter } from '../examples/example.functions';
import {
exampleErrorFormatter,
exampleResponseBodyFormatter,
} from '../examples/example.functions';
import { TestInterceptor } from '../interceptors/test.interceptor';

@Controller('hello')
Expand All @@ -35,6 +38,11 @@ export class HelloController {
return this.i18n.translate('test.HELLO', { lang });
}

@Get('/no-lang-for-service')
helloNoLangForService(): any {
return this.i18n.translate('test.HELLO');
}

@Get('/typed')
helloTyped(@I18nLang() lang: string): string {
return this.i18n.translate('test.HELLO', { lang });
Expand All @@ -59,6 +67,11 @@ export class HelloController {
return this.i18n.t('test.HELLO', { lang });
}

@Get('/short/no-lang-for-service')
helloShortNoLangForService(): any {
return this.i18n.t('test.HELLO');
}

@Get('/short/typed')
helloShortTyped(@I18nLang() lang: string): string {
return this.i18n.t('test.HELLO', { lang });
Expand Down Expand Up @@ -228,4 +241,18 @@ export class HelloController {
];
return items.find(({ id }) => id === data.id);
}

@GrpcMethod('HeroesService', 'FindOneTranslatedWithService')
findOneTranslatedWithService(@Payload() data: HeroById): Hero {
const items = [
{
id: 1,
name: this.i18n.t('test.set-up-password.heading', {
args: { username: 'John' },
}),
},
{ id: 2, name: 'Doe' },
];
return items.find(({ id }) => id === data.id);
}
}
1 change: 1 addition & 0 deletions tests/app/hero.proto
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package hero;

service HeroesService {
rpc FindOne (HeroById) returns (Hero) {}
rpc FindOneTranslatedWithService (HeroById) returns (Hero) {}
}

message HeroById {
Expand Down
4 changes: 4 additions & 0 deletions tests/app/interfaces/hero.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,8 @@ export interface HeroById {

export interface HeroService {
findOne(data: HeroById, metadata: Metadata): Observable<Hero>;
findOneTranslatedWithService(
data: HeroById,
metadata: Metadata,
): Observable<Hero>;
}
Loading

0 comments on commit b99ee72

Please sign in to comment.