generator-kodly-react-app 1.0.14 → 1.0.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/generators/app/index.js +4 -0
  2. package/generators/app/templates/.env.dev +1 -1
  3. package/generators/app/templates/.env.sandbox +2 -2
  4. package/generators/app/templates/I18N_MIGRATION.md +28 -35
  5. package/generators/app/templates/STRUCTURE.md +26 -26
  6. package/generators/app/templates/index.html +6 -14
  7. package/generators/app/templates/openapi-ts.config.ts +12 -12
  8. package/generators/app/templates/postcss.config.js +0 -1
  9. package/generators/app/templates/src/app.tsx +20 -20
  10. package/generators/app/templates/src/components/layout/language-switcher.tsx +20 -16
  11. package/generators/app/templates/src/components/layout/page-layout.tsx +31 -0
  12. package/generators/app/templates/src/components/layout/theme-switcher.tsx +15 -14
  13. package/generators/app/templates/src/components/theme/theme-provider.tsx +20 -13
  14. package/generators/app/templates/src/components/ui/button.tsx +15 -14
  15. package/generators/app/templates/src/components/ui/card.tsx +23 -16
  16. package/generators/app/templates/src/components/ui/field.tsx +35 -35
  17. package/generators/app/templates/src/components/ui/form.tsx +66 -30
  18. package/generators/app/templates/src/components/ui/input-group.tsx +24 -24
  19. package/generators/app/templates/src/components/ui/input.tsx +8 -8
  20. package/generators/app/templates/src/components/ui/label.tsx +9 -9
  21. package/generators/app/templates/src/components/ui/select.tsx +0 -1
  22. package/generators/app/templates/src/components/ui/separator.tsx +10 -10
  23. package/generators/app/templates/src/components/ui/textarea.tsx +7 -7
  24. package/generators/app/templates/src/index.css +12 -2
  25. package/generators/app/templates/src/lib/i18n.ts +16 -14
  26. package/generators/app/templates/src/lib/routes.ts +2 -3
  27. package/generators/app/templates/src/lib/utils/error-handler.ts +21 -14
  28. package/generators/app/templates/src/lib/utils.ts +3 -3
  29. package/generators/app/templates/src/locales/index.ts +12 -12
  30. package/generators/app/templates/src/locales/ja/theme.json +0 -1
  31. package/generators/app/templates/src/main.tsx +10 -12
  32. package/generators/app/templates/src/modules/app/layouts/app-layout.tsx +14 -25
  33. package/generators/app/templates/src/modules/app/locales/app-en.json +0 -1
  34. package/generators/app/templates/src/modules/app/locales/app-ja.json +0 -1
  35. package/generators/app/templates/src/modules/auth/components/auth-form.tsx +83 -0
  36. package/generators/app/templates/src/modules/auth/components/forgot-password-form.tsx +27 -52
  37. package/generators/app/templates/src/modules/auth/components/login-form.tsx +42 -72
  38. package/generators/app/templates/src/modules/auth/components/login-link.tsx +12 -0
  39. package/generators/app/templates/src/modules/auth/components/reset-password-form.tsx +46 -92
  40. package/generators/app/templates/src/modules/auth/components/signup-form.tsx +35 -71
  41. package/generators/app/templates/src/modules/auth/contexts/auth-context.tsx +10 -6
  42. package/generators/app/templates/src/modules/auth/hooks/use-auth-hook.ts +51 -36
  43. package/generators/app/templates/src/modules/auth/layouts/auth-layout.tsx +12 -15
  44. package/generators/app/templates/src/modules/auth/locales/auth-en.json +0 -1
  45. package/generators/app/templates/src/modules/auth/locales/auth-ja.json +0 -1
  46. package/generators/app/templates/src/modules/auth/schemas/form-schemas.ts +14 -10
  47. package/generators/app/templates/src/modules/landing/components/auth-hero.tsx +20 -18
  48. package/generators/app/templates/src/modules/landing/components/welcome-hero.tsx +13 -11
  49. package/generators/app/templates/src/modules/landing/landing-page-layout.tsx +1 -24
  50. package/generators/app/templates/src/modules/landing/landing-page.tsx +11 -6
  51. package/generators/app/templates/src/modules/landing/layouts/landing-page-layout.tsx +2 -21
  52. package/generators/app/templates/src/modules/landing/locales/landing-en.json +0 -1
  53. package/generators/app/templates/src/openapi-client-config.ts +1 -1
  54. package/generators/app/templates/src/routeTree.gen.ts +188 -188
  55. package/generators/app/templates/src/router.tsx +2 -2
  56. package/generators/app/templates/src/routes/$.tsx +0 -1
  57. package/generators/app/templates/src/routes/__root.tsx +2 -2
  58. package/generators/app/templates/src/routes/_landing/index.tsx +3 -3
  59. package/generators/app/templates/src/routes/_landing/route.tsx +3 -3
  60. package/generators/app/templates/src/routes/app/index.tsx +6 -6
  61. package/generators/app/templates/src/routes/app/route.tsx +4 -4
  62. package/generators/app/templates/src/routes/auth/forgot-password.tsx +1 -1
  63. package/generators/app/templates/src/routes/auth/login.tsx +3 -3
  64. package/generators/app/templates/src/routes/auth/reset-password.tsx +3 -3
  65. package/generators/app/templates/src/routes/auth/route.tsx +12 -7
  66. package/generators/app/templates/src/routes/auth/signup.tsx +1 -2
  67. package/generators/app/templates/src/sdk/@tanstack/react-query.gen.ts +157 -76
  68. package/generators/app/templates/src/sdk/client/client.gen.ts +35 -37
  69. package/generators/app/templates/src/sdk/client/index.ts +8 -8
  70. package/generators/app/templates/src/sdk/client/types.gen.ts +22 -19
  71. package/generators/app/templates/src/sdk/client/utils.gen.ts +53 -45
  72. package/generators/app/templates/src/sdk/client.gen.ts +18 -5
  73. package/generators/app/templates/src/sdk/core/auth.gen.ts +6 -6
  74. package/generators/app/templates/src/sdk/core/bodySerializer.gen.ts +25 -27
  75. package/generators/app/templates/src/sdk/core/params.gen.ts +12 -12
  76. package/generators/app/templates/src/sdk/core/pathSerializer.gen.ts +40 -41
  77. package/generators/app/templates/src/sdk/core/queryKeySerializer.gen.ts +12 -29
  78. package/generators/app/templates/src/sdk/core/serverSentEvents.gen.ts +26 -24
  79. package/generators/app/templates/src/sdk/core/types.gen.ts +17 -15
  80. package/generators/app/templates/src/sdk/core/utils.gen.ts +18 -18
  81. package/generators/app/templates/src/sdk/index.ts +40 -2
  82. package/generators/app/templates/src/sdk/schemas.gen.ts +108 -166
  83. package/generators/app/templates/src/sdk/sdk.gen.ts +145 -63
  84. package/generators/app/templates/src/sdk/types.gen.ts +90 -84
  85. package/generators/app/templates/src/sdk/zod.gen.ts +57 -74
  86. package/generators/app/templates/src/types/i18n.d.ts +0 -1
  87. package/generators/app/templates/tailwind.config.js +0 -1
  88. package/generators/app/templates/tsconfig.app.json +0 -1
  89. package/generators/app/templates/tsconfig.json +0 -1
  90. package/generators/app/templates/tsr.config.json +4 -0
  91. package/generators/app/templates/vite.config.js +13 -12
  92. package/package.json +2 -4
@@ -4,12 +4,7 @@
4
4
  * JSON-friendly union that mirrors what Pinia Colada can hash.
5
5
  */
6
6
  export type JsonValue =
7
- | null
8
- | string
9
- | number
10
- | boolean
11
- | JsonValue[]
12
- | { [key: string]: JsonValue };
7
+ null | string | number | boolean | JsonValue[] | { [key: string]: JsonValue };
13
8
 
14
9
  /**
15
10
  * Replacer that converts non-JSON values (bigint, Date, etc.) to safe substitutes.
@@ -17,12 +12,12 @@ export type JsonValue =
17
12
  export const queryKeyJsonReplacer = (_key: string, value: unknown) => {
18
13
  if (
19
14
  value === undefined ||
20
- typeof value === 'function' ||
21
- typeof value === 'symbol'
15
+ typeof value === "function" ||
16
+ typeof value === "symbol"
22
17
  ) {
23
18
  return undefined;
24
19
  }
25
- if (typeof value === 'bigint') {
20
+ if (typeof value === "bigint") {
26
21
  return value.toString();
27
22
  }
28
23
  if (value instanceof Date) {
@@ -50,7 +45,7 @@ export const stringifyToJsonValue = (input: unknown): JsonValue | undefined => {
50
45
  * Detects plain objects (including objects with a null prototype).
51
46
  */
52
47
  const isPlainObject = (value: unknown): value is Record<string, unknown> => {
53
- if (value === null || typeof value !== 'object') {
48
+ if (value === null || typeof value !== "object") {
54
49
  return false;
55
50
  }
56
51
  const prototype = Object.getPrototypeOf(value as object);
@@ -94,35 +89,23 @@ export const serializeQueryKeyValue = (
94
89
  }
95
90
 
96
91
  if (
97
- typeof value === 'string' ||
98
- typeof value === 'number' ||
99
- typeof value === 'boolean'
92
+ typeof value === "string" ||
93
+ typeof value === "number" ||
94
+ typeof value === "boolean"
100
95
  ) {
101
96
  return value;
102
97
  }
103
98
 
104
- if (
105
- value === undefined ||
106
- typeof value === 'function' ||
107
- typeof value === 'symbol'
108
- ) {
109
- return undefined;
110
- }
111
-
112
- if (typeof value === 'bigint') {
113
- return value.toString();
114
- }
115
-
116
- if (value instanceof Date) {
117
- return value.toISOString();
118
- }
99
+ const scalar = queryKeyJsonReplacer("", value);
100
+ if (scalar !== value || value === undefined)
101
+ return scalar as JsonValue | undefined;
119
102
 
120
103
  if (Array.isArray(value)) {
121
104
  return stringifyToJsonValue(value);
122
105
  }
123
106
 
124
107
  if (
125
- typeof URLSearchParams !== 'undefined' &&
108
+ typeof URLSearchParams !== "undefined" &&
126
109
  value instanceof URLSearchParams
127
110
  ) {
128
111
  return serializeSearchParams(value);
@@ -1,12 +1,12 @@
1
1
  // This file is auto-generated by @hey-api/openapi-ts
2
2
 
3
- import type { Config } from './types.gen';
3
+ import type { Config } from "./types.gen";
4
4
 
5
5
  export type ServerSentEventsOptions<TData = unknown> = Omit<
6
6
  RequestInit,
7
- 'method'
7
+ "method"
8
8
  > &
9
- Pick<Config, 'method' | 'responseTransformer' | 'responseValidator'> & {
9
+ Pick<Config, "method" | "responseTransformer" | "responseValidator"> & {
10
10
  /**
11
11
  * Fetch API implementation. You can use this option to provide a custom
12
12
  * fetch instance.
@@ -35,7 +35,7 @@ export type ServerSentEventsOptions<TData = unknown> = Omit<
35
35
  * @returns Nothing (void).
36
36
  */
37
37
  onSseEvent?: (event: StreamEvent<TData>) => void;
38
- serializedBody?: RequestInit['body'];
38
+ serializedBody?: RequestInit["body"];
39
39
  /**
40
40
  * Default retry delay in milliseconds.
41
41
  *
@@ -121,12 +121,12 @@ export const createSseClient = <TData = unknown>({
121
121
  : new Headers(options.headers as Record<string, string> | undefined);
122
122
 
123
123
  if (lastEventId !== undefined) {
124
- headers.set('Last-Event-ID', lastEventId);
124
+ headers.set("Last-Event-ID", lastEventId);
125
125
  }
126
126
 
127
127
  try {
128
128
  const requestInit: RequestInit = {
129
- redirect: 'follow',
129
+ redirect: "follow",
130
130
  ...options,
131
131
  body: options.serializedBody,
132
132
  headers,
@@ -146,13 +146,13 @@ export const createSseClient = <TData = unknown>({
146
146
  `SSE failed: ${response.status} ${response.statusText}`,
147
147
  );
148
148
 
149
- if (!response.body) throw new Error('No body in SSE response');
149
+ if (!response.body) throw new Error("No body in SSE response");
150
150
 
151
151
  const reader = response.body
152
152
  .pipeThrough(new TextDecoderStream())
153
153
  .getReader();
154
154
 
155
- let buffer = '';
155
+ let buffer = "";
156
156
 
157
157
  const abortHandler = () => {
158
158
  try {
@@ -162,7 +162,7 @@ export const createSseClient = <TData = unknown>({
162
162
  }
163
163
  };
164
164
 
165
- signal.addEventListener('abort', abortHandler);
165
+ signal.addEventListener("abort", abortHandler);
166
166
 
167
167
  try {
168
168
  while (true) {
@@ -170,26 +170,26 @@ export const createSseClient = <TData = unknown>({
170
170
  if (done) break;
171
171
  buffer += value;
172
172
  // Normalize line endings: CRLF -> LF, then CR -> LF
173
- buffer = buffer.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
173
+ buffer = buffer.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
174
174
 
175
- const chunks = buffer.split('\n\n');
176
- buffer = chunks.pop() ?? '';
175
+ const chunks = buffer.split("\n\n");
176
+ buffer = chunks.pop() ?? "";
177
177
 
178
178
  for (const chunk of chunks) {
179
- const lines = chunk.split('\n');
179
+ const lines = chunk.split("\n");
180
180
  const dataLines: Array<string> = [];
181
181
  let eventName: string | undefined;
182
182
 
183
183
  for (const line of lines) {
184
- if (line.startsWith('data:')) {
185
- dataLines.push(line.replace(/^data:\s*/, ''));
186
- } else if (line.startsWith('event:')) {
187
- eventName = line.replace(/^event:\s*/, '');
188
- } else if (line.startsWith('id:')) {
189
- lastEventId = line.replace(/^id:\s*/, '');
190
- } else if (line.startsWith('retry:')) {
184
+ if (line.startsWith("data:")) {
185
+ dataLines.push(line.replace(/^data:\s*/, ""));
186
+ } else if (line.startsWith("event:")) {
187
+ eventName = line.replace(/^event:\s*/, "");
188
+ } else if (line.startsWith("id:")) {
189
+ lastEventId = line.replace(/^id:\s*/, "");
190
+ } else if (line.startsWith("retry:")) {
191
191
  const parsed = Number.parseInt(
192
- line.replace(/^retry:\s*/, ''),
192
+ line.replace(/^retry:\s*/, ""),
193
193
  10,
194
194
  );
195
195
  if (!Number.isNaN(parsed)) {
@@ -202,7 +202,7 @@ export const createSseClient = <TData = unknown>({
202
202
  let parsedJson = false;
203
203
 
204
204
  if (dataLines.length) {
205
- const rawData = dataLines.join('\n');
205
+ const rawData = dataLines.join("\n");
206
206
  try {
207
207
  data = JSON.parse(rawData);
208
208
  parsedJson = true;
@@ -229,12 +229,14 @@ export const createSseClient = <TData = unknown>({
229
229
  });
230
230
 
231
231
  if (dataLines.length) {
232
- yield data as any;
232
+ yield data as TData extends Record<string, unknown>
233
+ ? TData[keyof TData]
234
+ : TData;
233
235
  }
234
236
  }
235
237
  }
236
238
  } finally {
237
- signal.removeEventListener('abort', abortHandler);
239
+ signal.removeEventListener("abort", abortHandler);
238
240
  reader.releaseLock();
239
241
  }
240
242
 
@@ -1,22 +1,22 @@
1
1
  // This file is auto-generated by @hey-api/openapi-ts
2
2
 
3
- import type { Auth, AuthToken } from './auth.gen';
3
+ import type { Auth, AuthToken } from "./auth.gen";
4
4
  import type {
5
5
  BodySerializer,
6
6
  QuerySerializer,
7
7
  QuerySerializerOptions,
8
- } from './bodySerializer.gen';
8
+ } from "./bodySerializer.gen";
9
9
 
10
10
  export type HttpMethod =
11
- | 'connect'
12
- | 'delete'
13
- | 'get'
14
- | 'head'
15
- | 'options'
16
- | 'patch'
17
- | 'post'
18
- | 'put'
19
- | 'trace';
11
+ | "connect"
12
+ | "delete"
13
+ | "get"
14
+ | "head"
15
+ | "options"
16
+ | "patch"
17
+ | "post"
18
+ | "put"
19
+ | "trace";
20
20
 
21
21
  export type Client<
22
22
  RequestFn = never,
@@ -56,7 +56,7 @@ export interface Config {
56
56
  * {@link https://developer.mozilla.org/docs/Web/API/Headers/Headers#init See more}
57
57
  */
58
58
  headers?:
59
- | RequestInit['headers']
59
+ | RequestInit["headers"]
60
60
  | Record<
61
61
  string,
62
62
  | string
@@ -112,7 +112,9 @@ type IsExactlyNeverOrNeverUndefined<T> = [T] extends [never]
112
112
  : false;
113
113
 
114
114
  export type OmitNever<T extends Record<string, unknown>> = {
115
- [K in keyof T as IsExactlyNeverOrNeverUndefined<T[K]> extends true
116
- ? never
117
- : K]: T[K];
115
+ [
116
+ K in keyof T as IsExactlyNeverOrNeverUndefined<T[K]> extends true
117
+ ? never
118
+ : K
119
+ ]: T[K];
118
120
  };
@@ -1,12 +1,12 @@
1
1
  // This file is auto-generated by @hey-api/openapi-ts
2
2
 
3
- import type { BodySerializer, QuerySerializer } from './bodySerializer.gen';
3
+ import type { BodySerializer, QuerySerializer } from "./bodySerializer.gen";
4
4
  import {
5
5
  type ArraySeparatorStyle,
6
6
  serializeArrayParam,
7
7
  serializeObjectParam,
8
8
  serializePrimitiveParam,
9
- } from './pathSerializer.gen';
9
+ } from "./pathSerializer.gen";
10
10
 
11
11
  export interface PathSerializer {
12
12
  path: Record<string, unknown>;
@@ -22,19 +22,19 @@ export const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => {
22
22
  for (const match of matches) {
23
23
  let explode = false;
24
24
  let name = match.substring(1, match.length - 1);
25
- let style: ArraySeparatorStyle = 'simple';
25
+ let style: ArraySeparatorStyle = "simple";
26
26
 
27
- if (name.endsWith('*')) {
27
+ if (name.endsWith("*")) {
28
28
  explode = true;
29
29
  name = name.substring(0, name.length - 1);
30
30
  }
31
31
 
32
- if (name.startsWith('.')) {
32
+ if (name.startsWith(".")) {
33
33
  name = name.substring(1);
34
- style = 'label';
35
- } else if (name.startsWith(';')) {
34
+ style = "label";
35
+ } else if (name.startsWith(";")) {
36
36
  name = name.substring(1);
37
- style = 'matrix';
37
+ style = "matrix";
38
38
  }
39
39
 
40
40
  const value = path[name];
@@ -51,7 +51,7 @@ export const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => {
51
51
  continue;
52
52
  }
53
53
 
54
- if (typeof value === 'object') {
54
+ if (typeof value === "object") {
55
55
  url = url.replace(
56
56
  match,
57
57
  serializeObjectParam({
@@ -65,7 +65,7 @@ export const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => {
65
65
  continue;
66
66
  }
67
67
 
68
- if (style === 'matrix') {
68
+ if (style === "matrix") {
69
69
  url = url.replace(
70
70
  match,
71
71
  `;${serializePrimitiveParam({
@@ -77,7 +77,7 @@ export const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => {
77
77
  }
78
78
 
79
79
  const replaceValue = encodeURIComponent(
80
- style === 'label' ? `.${value as string}` : (value as string),
80
+ style === "label" ? `.${value as string}` : (value as string),
81
81
  );
82
82
  url = url.replace(match, replaceValue);
83
83
  }
@@ -98,13 +98,13 @@ export const getUrl = ({
98
98
  querySerializer: QuerySerializer;
99
99
  url: string;
100
100
  }) => {
101
- const pathUrl = _url.startsWith('/') ? _url : `/${_url}`;
102
- let url = (baseUrl ?? '') + pathUrl;
101
+ const pathUrl = _url.startsWith("/") ? _url : `/${_url}`;
102
+ let url = (baseUrl ?? "") + pathUrl;
103
103
  if (path) {
104
104
  url = defaultPathSerializer({ path, url });
105
105
  }
106
- let search = query ? querySerializer(query) : '';
107
- if (search.startsWith('?')) {
106
+ let search = query ? querySerializer(query) : "";
107
+ if (search.startsWith("?")) {
108
108
  search = search.substring(1);
109
109
  }
110
110
  if (search) {
@@ -122,15 +122,15 @@ export function getValidRequestBody(options: {
122
122
  const isSerializedBody = hasBody && options.bodySerializer;
123
123
 
124
124
  if (isSerializedBody) {
125
- if ('serializedBody' in options) {
125
+ if ("serializedBody" in options) {
126
126
  const hasSerializedBody =
127
- options.serializedBody !== undefined && options.serializedBody !== '';
127
+ options.serializedBody !== undefined && options.serializedBody !== "";
128
128
 
129
129
  return hasSerializedBody ? options.serializedBody : null;
130
130
  }
131
131
 
132
132
  // not all clients implement a serializedBody property (i.e. client-axios)
133
- return options.body !== '' ? options.body : null;
133
+ return options.body !== "" ? options.body : null;
134
134
  }
135
135
 
136
136
  // plain/text body
@@ -1,4 +1,42 @@
1
1
  // This file is auto-generated by @hey-api/openapi-ts
2
2
 
3
- export { loginUserAuth, logoutUserAuth, type Options, registerUserAuth, resetPasswordUserAuth, sendOtpUserAuth, validateTokenUserAuth } from './sdk.gen';
4
- export type { AuthResponseDtoUserDetailDto, ClientOptions, LoginUserAuthData, LoginUserAuthResponse, LoginUserAuthResponses, LogoutUserAuthData, LogoutUserAuthResponse, LogoutUserAuthResponses, ModuleDefinition, ModulePermission, PermissionDefinition, RegisterUserAuthData, RegisterUserAuthResponse, RegisterUserAuthResponses, ResetPasswordUserAuthData, ResetPasswordUserAuthResponse, ResetPasswordUserAuthResponses, RestResponseAuthResponseDtoUserDetailDto, RestResponseString, SendOtpUserAuthData, SendOtpUserAuthResponse, SendOtpUserAuthResponses, UserDetailDto, UserLoginDto, UserResetPasswordDto, UserSignupDto, ValidateTokenUserAuthData, ValidateTokenUserAuthResponse, ValidateTokenUserAuthResponses } from './types.gen';
3
+ export {
4
+ loginUserAuth,
5
+ logoutUserAuth,
6
+ type Options,
7
+ registerUserAuth,
8
+ resetPasswordUserAuth,
9
+ sendOtpUserAuth,
10
+ validateTokenUserAuth,
11
+ } from "./sdk.gen";
12
+ export type {
13
+ AuthResponseDtoUserDetailDto,
14
+ ClientOptions,
15
+ LoginUserAuthData,
16
+ LoginUserAuthResponse,
17
+ LoginUserAuthResponses,
18
+ LogoutUserAuthData,
19
+ LogoutUserAuthResponse,
20
+ LogoutUserAuthResponses,
21
+ ModuleDefinition,
22
+ ModulePermission,
23
+ PermissionDefinition,
24
+ RegisterUserAuthData,
25
+ RegisterUserAuthResponse,
26
+ RegisterUserAuthResponses,
27
+ ResetPasswordUserAuthData,
28
+ ResetPasswordUserAuthResponse,
29
+ ResetPasswordUserAuthResponses,
30
+ RestResponseAuthResponseDtoUserDetailDto,
31
+ RestResponseString,
32
+ SendOtpUserAuthData,
33
+ SendOtpUserAuthResponse,
34
+ SendOtpUserAuthResponses,
35
+ UserDetailDto,
36
+ UserLoginDto,
37
+ UserResetPasswordDto,
38
+ UserSignupDto,
39
+ ValidateTokenUserAuthData,
40
+ ValidateTokenUserAuthResponse,
41
+ ValidateTokenUserAuthResponses,
42
+ } from "./types.gen";