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

Global key support #170

Merged
merged 1 commit into from
Oct 30, 2023
Merged
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
27 changes: 27 additions & 0 deletions .changeset/lemon-mails-battle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
'@vocab/phrase': minor
'@vocab/core': minor
---

`vocab push` and `vocab pull` can support global keys mapping. When you want certain translations to use a specific/custom key in Phrase, add the `globalKey` to the structure.

**EXAMPLE USAGE**:

```jsonc
// translations.json
{
"Hello": {
"message": "Hello",
"globalKey": "hello"
},
"Goodbye": {
"message": "Goodbye",
"globalKey": "app.goodbye.label"
}
}
```

In the above example,

- `vocab push` will push the `hello` and `app.goodbye.label` keys to Phrase.
- `vocab pull` will pull translations from Phrase and map them to the `hello` and `app.goodbye.label` keys.
14 changes: 14 additions & 0 deletions .changeset/many-apricots-try.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
'@vocab/cli': minor
---

Error on no translation for global key

By default, `vocab pull` will not error if a translation is missing in Phrase for a translation with a global key.
If you want to throw an error in this situation, pass the `--error-on-no-global-key-translation` flag:

**EXAMPLE USAGE**:

```sh
vocab pull --error-on-no-global-key-translation
```
20 changes: 20 additions & 0 deletions .changeset/slimy-dingos-begin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
'@vocab/phrase': minor
---

Add an optional `errorOnNoGlobalKeyTranslation` flag to `pull` function. If set to `true`, it will error if a translation is missing in Phrase for a translation with a global key.

**EXAMPLE USAGE**:

```js
import { pull } from '@vocab/phrase';

const vocabConfig = {
devLanguage: 'en',
language: ['en', 'fr'],
};

await pull({ branch: 'myBranch', errorOnNoGlobalKeyTranslation: true }, vocabConfig);
```


32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,38 @@ Tags on keys in other languages will be ignored.
[tags]: https://support.phrase.com/hc/en-us/articles/5822598372252-Tags-Strings-
[configuration]: #Configuration

#### Global key

`vocab push` and `vocab pull` can support global keys mapping. When you want certain translations to use a specific/custom key in Phrase, add the `globalKey` to the structure.

```jsonc
// translations.json
{
"Hello": {
"message": "Hello",
"globalKey": "hello"
},
"Goodbye": {
"message": "Goodbye",
"globalKey": "app.goodbye.label"
}
}
```

In the above example,

- `vocab push` will push the `hello` and `app.goodbye.label` keys to Phrase.
- `vocab pull` will pull translations from Phrase and map them to the `hello` and `app.goodbye.label` keys.

##### Error on no translation for global key

By default, `vocab pull` will not error if a translation is missing in Phrase for a translation with a global key.
If you want to throw an error in this situation, pass the `--error-on-no-global-key-translation` flag:

```sh
vocab pull --error-on-no-global-key-translation
```

## Troubleshooting

### Problem: Passed locale is being ignored or using en-US instead
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,8 @@
},
"world": {
"message": "monde"
},
"profile": {
"message": "profil"
askoufis marked this conversation as resolved.
Show resolved Hide resolved
}
}
7 changes: 7 additions & 0 deletions fixtures/phrase/src/mytranslations.vocab/translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,12 @@
},
"world": {
"message": "world"
},
"thanks": {
"message": "Thanks",
"globalKey": "app.thanks.label"
},
"profile": {
"message": "profil"
}
}
11 changes: 10 additions & 1 deletion packages/cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,16 @@ yargs(process.argv.slice(2))
})
.command({
command: 'pull',
builder: () => yargs.options({ branch: branchDefinition }),
builder: () =>
yargs.options({
branch: branchDefinition,
'error-on-no-global-key-translation': {
type: 'boolean',
describe:
'Throw an error when there is no translation for a global key',
default: false,
},
}),
handler: async (options) => {
await pull(options, config!);
},
Expand Down
11 changes: 11 additions & 0 deletions packages/core/src/load-translations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,17 @@ export async function loadAllTranslations(
);
}
keys.add(uniqueKey);

const globalKey =
loadedTranslation.languages[config.devLanguage][key].globalKey;
if (globalKey) {
if (keys.has(globalKey)) {
throw new Error(
`Duplicate keys found. Key with global key ${globalKey} and key ${key} was found multiple times`,
);
}
keys.add(globalKey);
}
}
}
return result;
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ export interface TranslationData {
message: TranslationMessage;
description?: string;
tags?: Tags;
globalKey?: string;
}

export type TranslationsByKey<Key extends TranslationKey = string> = Record<
Expand Down
78 changes: 77 additions & 1 deletion packages/phrase/src/pull-translations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,13 @@ const devLanguage = 'en';
function runPhrase(options: {
languages: LanguageTarget[];
generatedLanguages: GeneratedLanguageTarget[];
errorOnNoGlobalKeyTranslation?: boolean;
}) {
return pull(
{ branch: 'tester' },
{
branch: 'tester',
errorOnNoGlobalKeyTranslation: options.errorOnNoGlobalKeyTranslation,
},
{
...options,
devLanguage,
Expand All @@ -41,11 +45,17 @@ describe('pull translations', () => {
'hello.mytranslations': {
message: 'Hi there',
},
'app.thanks.label': {
message: 'Thank you.',
},
},
fr: {
'hello.mytranslations': {
message: 'merci',
},
'app.thanks.label': {
message: 'Merci.',
},
},
}),
);
Expand Down Expand Up @@ -98,6 +108,13 @@ describe('pull translations', () => {
"greeting",
],
},
"profile": {
"message": "profil",
},
"thanks": {
"globalKey": "app.thanks.label",
"message": "Thank you.",
},
"world": {
"message": "world",
},
Expand All @@ -106,6 +123,12 @@ describe('pull translations', () => {
"hello": {
"message": "merci",
},
"profile": {
"message": "profil",
},
"thanks": {
"message": "Merci.",
},
"world": {
"message": "monde",
},
Expand Down Expand Up @@ -182,6 +205,13 @@ describe('pull translations', () => {
"greeting",
],
},
"profile": {
"message": "profil",
},
"thanks": {
"globalKey": "app.thanks.label",
"message": "Thanks",
},
"world": {
"message": "world",
},
Expand All @@ -190,6 +220,9 @@ describe('pull translations', () => {
"hello": {
"message": "merci",
},
"profile": {
"message": "profil",
},
"world": {
"message": "monde",
},
Expand Down Expand Up @@ -237,4 +270,47 @@ describe('pull translations', () => {
expect(jest.mocked(writeFile)).toHaveBeenCalledTimes(0);
});
});

describe('when pulling translations and some global keys do not have any translations', () => {
beforeEach(() => {
jest.mocked(pullAllTranslations).mockClear();
jest.mocked(writeFile).mockClear();
jest.mocked(pullAllTranslations).mockImplementation(() =>
Promise.resolve({
en: {
'hello.mytranslations': {
message: 'Hi there',
},
},
fr: {
'hello.mytranslations': {
message: 'merci',
},
},
}),
);
});

const options = {
languages: [{ name: 'en' }, { name: 'fr' }],
generatedLanguages: [
{
name: 'generatedLanguage',
extends: 'en',
generator: {
transformMessage: (message: string) => `[${message}]`,
},
},
],
errorOnNoGlobalKeyTranslation: true,
};

it('should throw an error', async () => {
await expect(runPhrase(options)).rejects.toThrow(
new Error(`Missing translation for global key thanks in language fr`),
);

expect(jest.mocked(writeFile)).toHaveBeenCalledTimes(1);
});
});
});
15 changes: 12 additions & 3 deletions packages/phrase/src/pull-translations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@ import { trace } from './logger';
interface PullOptions {
branch?: string;
deleteUnusedKeys?: boolean;
errorOnNoGlobalKeyTranslation?: boolean;
}

export async function pull(
{ branch = 'local-development' }: PullOptions,
{ branch = 'local-development', errorOnNoGlobalKeyTranslation }: PullOptions,
config: UserConfig,
) {
trace(`Pulling translations from branch ${branch}`);
Expand Down Expand Up @@ -61,7 +62,8 @@ export async function pull(
defaultValues[key] = {
...defaultValues[key],
...allPhraseTranslations[config.devLanguage][
getUniqueKey(key, loadedTranslation.namespace)
defaultValues[key].globalKey ??
getUniqueKey(key, loadedTranslation.namespace)
],
};
}
Expand All @@ -85,14 +87,21 @@ export async function pull(
allPhraseTranslations[alternativeLanguage];

for (const key of localKeys) {
const phraseKey = getUniqueKey(key, loadedTranslation.namespace);
const phraseKey =
defaultValues[key].globalKey ??
getUniqueKey(key, loadedTranslation.namespace);
const phraseTranslationMessage =
phraseAltTranslations[phraseKey]?.message;

if (!phraseTranslationMessage) {
trace(
`Missing translation. No translation for key ${key} in phrase as ${phraseKey} in language ${alternativeLanguage}.`,
);
if (errorOnNoGlobalKeyTranslation && defaultValues[key].globalKey) {
throw new Error(
`Missing translation for global key ${key} in language ${alternativeLanguage}`,
);
}
continue;
}

Expand Down
Loading