Skip to content

Commit

Permalink
🔀 Merge pull request #1378 from jovotech/v4/dev
Browse files Browse the repository at this point in the history
🔖 Prepare latest release
  • Loading branch information
jankoenig authored Jul 21, 2022
2 parents 30258bb + 90868ca commit 3253304
Show file tree
Hide file tree
Showing 11 changed files with 211 additions and 5 deletions.
25 changes: 21 additions & 4 deletions docs/i18n.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,16 @@ const app = new App({
You can add any configuration that is available for `i18next`. [You can find all configuration options here](https://www.i18next.com/overview/configuration-options). The default is:

```typescript
interpolation: { // https://www.i18next.com/translation-function/interpolation
escapeValue: false,
},
returnObjects: true, // https://www.i18next.com/translation-function/objects-and-arrays
{
i18n: {
interpolation: { // https://www.i18next.com/translation-function/interpolation
escapeValue: false,
skipOnVariables: false, // Added for backwards compatibility, see https://www.i18next.com/misc/migration-guide#skiponvariables-true
},
returnObjects: true, // https://www.i18next.com/translation-function/objects-and-arrays
compatibilityJSON: 'v3', // Added for backwards compatibility, see https://www.i18next.com/misc/migration-guide#json-format-v4-pluralization
}
}
```

The most relevant option is the `resources` object that contains references to all language files. The example below imports a file for the `en` locale and adds it as resource:
Expand Down Expand Up @@ -131,6 +137,17 @@ Import all language resource files as shown in the example above and add them to
}
```

`i18next` now supports a new [`v4` JSON format](https://www.i18next.com/misc/json-format#i-18-next-json-v4). For backwards compatibility, Jovo projects use the `v3` format by default (see all default options in the [configuration section](#configuration)):

```typescript
{
i18n: {
compatibilityJSON: 'v3', // Added for backwards compatibility, see https://www.i18next.com/misc/migration-guide#json-format-v4-pluralization
// ...
}
}
```

`i18next` provides many features to structure your content. You can find more information in their official documentation about [interpolation](https://www.i18next.com/translation-function/interpolation), [formatting](https://www.i18next.com/translation-function/formatting), [plurals](https://www.i18next.com/translation-function/plurals), and more.

In the next sections, we'll look specifically into [parameters](#parameters), [randomization](#arrays-and-randomization), [nested objects](#nested-objects), and [platform specific translations](#platform-specific-translations).
Expand Down
1 change: 1 addition & 0 deletions docs/unit-testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -554,5 +554,6 @@ test('...', async () => {
Currently, you can modify the following properties that will be merged into the `jovo` instance:
- `testSuite.$user.data`
- `testSuite.$user.isNew`
- `testSuite.$session`
- `testSuite.$request`
2 changes: 1 addition & 1 deletion framework/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"@jovotech/output": "^4.2.15",
"axios": "^0.21.1",
"chalk": "^4.1.0",
"i18next": "^20.3.1",
"i18next": "^21.8.11",
"json-colorizer": "^2.2.2",
"lodash.clonedeep": "^4.5.0",
"lodash.get": "^4.4.2",
Expand Down
2 changes: 2 additions & 0 deletions framework/src/I18Next.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,10 @@ export class I18Next extends Plugin<I18NextConfig> {
return {
interpolation: {
escapeValue: false,
skipOnVariables: false, // Added for backwards compatibility, @see https://www.i18next.com/misc/migration-guide#skiponvariables-true
},
returnObjects: true,
compatibilityJSON: 'v3', // Added for backwards compatibility, @see https://www.i18next.com/misc/migration-guide#json-format-v4-pluralization
};
}

Expand Down
1 change: 1 addition & 0 deletions framework/src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export interface StoredElementHistory extends StoredElement {
size?: number;
asr?: StoredElement | boolean;
state?: StoredElement | boolean;
input?: StoredElement | boolean;
output?: StoredElement | boolean;
nlu?: StoredElement | boolean;
request?: StoredElement | boolean;
Expand Down
3 changes: 3 additions & 0 deletions framework/src/testsuite/TestSuite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,9 @@ export class TestSuite<PLATFORM extends Platform = TestPlatform> extends Plugin<
jovo.$input = this.requestOrInput;
jovo.$entities = jovo.getEntityMap();
}

jovo.$user.isNew = this.$user.isNew;

_merge(jovo.$user.data, this.$user.data);
_merge(jovo.$session, this.$session);
_merge(jovo.$request, this.$request);
Expand Down
3 changes: 3 additions & 0 deletions output/src/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,9 @@ export function mergeInstances<D extends object, S extends any[]>(
...sources.map((source) => instanceToObject(source)),
// eslint-disable-next-line @typescript-eslint/ban-types, @typescript-eslint/no-explicit-any
(value: any, srcValue: any, key: string, object: any) => {
if (Array.isArray(srcValue) && Array.isArray(value)) {
return srcValue.concat(value);
}
if (typeof srcValue === 'undefined') {
_unset(object, key);
}
Expand Down
22 changes: 22 additions & 0 deletions output/test/utilities.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,4 +139,26 @@ describe('mergeInstances', () => {

expect(mergeInstances(b, a)).toEqual(merged);
});

test('test merging of nested arrays', () => {
const a = {
obj: {
arr: ['a', 'b'],
},
};

const b = {
obj: {
arr: ['c', 'd'],
},
};

const merged = {
obj: {
arr: ['a', 'b', 'c', 'd'],
},
};

expect(mergeInstances(b, a)).toEqual(merged);
});
});
59 changes: 59 additions & 0 deletions platforms/platform-alexa/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ this.$alexa.$user;
The following features are offered by the Alexa user property:

- [User Profile](#user-profile)
- [Person Profile](#person-profile)
- [Account Linking](#account-linking)

#### User Profile
Expand Down Expand Up @@ -320,6 +321,64 @@ new AlexaCli({
});
```

#### Person Profile

You can call the [Alexa Person Profile API](https://developer.amazon.com/en-US/docs/alexa/custom-skills/request-recognized-speaker-contact-information.html) by using the following methods:

```typescript
await this.$alexa.$user.getSpeakerName();
// Result: string

await this.$alexa.$user.getSpeakerGivenName();
// Result: string

await this.$alexa.$user.getSpeakerMobileNumber();
// Result: { countryCode: string; mobileNumber: string; }
```

Below is an example `getName` handler:

```typescript
import { AskForPermissionConsentCardOutput } from '@jovotech/platform-alexa';
// ...

async getName() {
try {
const name = await this.$alexa.$user.getSpeakerName();
return this.$send({ message: `Your name is ${name}` });
} catch(error) {
if (error.code === 'NO_USER_PERMISSION') {
return this.$send(AskForPermissionConsentCardOutput, {
message: 'Please grant access to your name.',
permissions: 'alexa::profile:name:read',
listen: false,
});
} else {
// ...
}
}
},
```

For a Skill to be able to request information from the Person Profile API, the permissions need to be added to the Skill manifest, either in the Alexa Developer Console or the `skill.json` file. The latter can be done by using the [`files` property of the Alexa project config](./project-config.md#files):

```js
new AlexaCli({
files: {
'skill-package/skill.json': {
manifest: {
permissions: [
'alexa::person_id:read',
'alexa::profile:name:read',
// ...
],
},
},
},
// ...
});
```

#### Account Linking

Account linking enables you to connect your Alexa Skill users to other systems.
Expand Down
30 changes: 30 additions & 0 deletions platforms/platform-alexa/src/AlexaUser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ import { Alexa } from './Alexa';

import { AlexaRequest } from './AlexaRequest';
import { CustomerProfileApiResponse, ProfileProperty, sendCustomerProfileApiRequest } from './api';
import {
PersonProfileApiResponse,
PersonProfileProperty,
sendPersonProfileApiRequest,
} from './api/PersonProfileApi';
import {
AbsoluteReminder,
deleteReminder,
Expand Down Expand Up @@ -44,6 +49,31 @@ export class AlexaUser extends JovoUser<Alexa> {
return await this.getProfileProperty(ProfileProperty.GIVEN_NAME);
}

async getSpeakerName(): Promise<string | undefined> {
return await this.getPersonProfileProperty(PersonProfileProperty.NAME);
}

async getSpeakerGivenName(): Promise<string | undefined> {
return await this.getPersonProfileProperty(PersonProfileProperty.GIVEN_NAME);
}

async getSpeakerMobileNumber(): Promise<
{ countryCode: string; mobileNumber: string } | undefined
> {
return await this.getPersonProfileProperty(PersonProfileProperty.MOBILE_NUMBER);
}

private async getPersonProfileProperty<PROPERTY extends PersonProfileProperty>(
property: PROPERTY,
): Promise<PersonProfileApiResponse<PROPERTY> | undefined> {
const request: AlexaRequest = this.jovo.$request;
return sendPersonProfileApiRequest(
property,
request.getApiEndpoint(),
request.getApiAccessToken(),
);
}

private async getProfileProperty<PROPERTY extends ProfileProperty>(
property: PROPERTY,
): Promise<CustomerProfileApiResponse<PROPERTY> | undefined> {
Expand Down
68 changes: 68 additions & 0 deletions platforms/platform-alexa/src/api/PersonProfileApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { JovoError } from '@jovotech/framework';
import { AlexaApiError, AlexaApiErrorCode, AlexaApiOptions, sendApiRequest } from './AlexaApi';

export enum PersonProfileProperty {
NAME = 'name',
GIVEN_NAME = 'givenName',
MOBILE_NUMBER = 'mobileNumber',
}

/**
* Determines the response type for the Person Profile API.
* For mobileNumber, returns the mobileNumber with the respective countryCode.
* Otherwise just returns the result string.
*/
export type PersonProfileApiResponse<PROPERTY extends PersonProfileProperty> =
PROPERTY extends PersonProfileProperty.MOBILE_NUMBER
? { [PersonProfileProperty.MOBILE_NUMBER]: string; countryCode: string }
: string;

/**
* Sends a request to Amazon's Person Profile API for getting profile information
* @param profileProperty - The profile property which determines the final API endpoint url
* @param apiEndpoint - API endpoint, differs on the geographic location of the skill
* @param permissionToken - Token to authorize the request
* @see {@link https://developer.amazon.com/en-US/docs/alexa/custom-skills/request-recognized-speaker-contact-information.html Request Recognized Speaker Contact Information}
*/
export async function sendPersonProfileApiRequest<PROPERTY extends PersonProfileProperty>(
profileProperty: PROPERTY,
apiEndpoint: string,
permissionToken: string,
): Promise<PersonProfileApiResponse<PROPERTY>> {
const options: AlexaApiOptions = {
endpoint: apiEndpoint,
path: `/v2/persons/~current/profile/${profileProperty}`,
permissionToken,
};

try {
const response = await sendApiRequest<PersonProfileApiResponse<PROPERTY>>(options);
return response.data;
} catch (error) {
if (error.isAxiosError) {
const { message, code } = error.response.data;
let errorCode: AlexaApiErrorCode = AlexaApiErrorCode.ERROR;

// User needs to grant access in app
if (
message === 'The authentication token is not valid.' ||
message === 'Access to this resource has not yet been requested.' ||
(code === 'ACCESS_DENIED' && message === 'Access denied with reason: ACCESS_NOT_REQUESTED')
) {
errorCode = AlexaApiErrorCode.NO_USER_PERMISSION;
}

// Dev needs to set correct permissions in ASK console
if (
message === 'Access to this resource cannot be requested.' ||
(code === 'ACCESS_DENIED' && message === 'Access denied with reason: FORBIDDEN')
) {
errorCode = AlexaApiErrorCode.NO_SKILL_PERMISSION;
}

throw new AlexaApiError({ message, code: errorCode });
}

throw new JovoError({ message: error.message });
}
}

0 comments on commit 3253304

Please sign in to comment.