Skip to content

Commit

Permalink
Global key support (#170)
Browse files Browse the repository at this point in the history
  • Loading branch information
jasoncheng-jora committed Oct 30, 2023
1 parent bfdb610 commit 161d698
Show file tree
Hide file tree
Showing 13 changed files with 269 additions and 7 deletions.
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
3 changes: 3 additions & 0 deletions fixtures/phrase/src/mytranslations.vocab/fr.translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,8 @@
},
"world": {
"message": "monde"
},
"profile": {
"message": "profil"
}
}
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

0 comments on commit 161d698

Please sign in to comment.