Skip to content

Commit

Permalink
Implement language switcher
Browse files Browse the repository at this point in the history
  • Loading branch information
andrii-bodnar committed Jul 21, 2023
1 parent 418b537 commit 509d121
Show file tree
Hide file tree
Showing 10 changed files with 162 additions and 32 deletions.
8 changes: 7 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,15 @@ jobs:
steps:
- uses: actions/checkout@v3
- uses: ./
with:
file: __tests__/files/README.md
destination: __tests__/files/docs
env:
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}

- name: List files
run: ls -la
run: |
cd ./__tests__/files/docs
ls -la
cat README.fr-FR.md
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,12 @@ To enable it, add the following placeholders to your `README.md` file:

## Options

| Option | Default value | Description |
|------------------------|---------------------------------------|--------------------------------------------------------|
| `file` | `README.md` | The Readme file name |
| `destination` | `./` | A directory where the localized readmes will be placed |
| `languages` | All Crowdin project languages | A list of languages to translate the Readme |
| Option | Default value | Description |
|---------------------|-------------------------------|----------------------------------------------------------|
| `file` | `README.md` | The Readme file name |
| `destination` | `./` | A directory where the localized readmes will be placed |
| `languages` | All Crowdin project languages | A list of languages to translate the Readme |
| `language_switcher` | `false` | Defines whether to add a language switcher to the Readme |

## Contributing

Expand Down
16 changes: 16 additions & 0 deletions __tests__/files/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Demo Readme

Some super description

## Languages

<!-- README-TRANSLATE-LANGUAGES-START -->
<!-- README-TRANSLATE-LANGUAGES-END -->

## Usage

Usage instructions

## License

MIT
4 changes: 4 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ inputs:
languages:
required: false
description: 'A list of languages to translate the Readme'
language_switcher:
required: false
description: 'Defines whether to add a language switcher to the Readme'
default: 'false'
runs:
using: 'node16'
main: 'dist/index.js'
64 changes: 53 additions & 11 deletions dist/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/index.js.map

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ interface Config {
destination: string;
branch?: string;
languages?: string[];
languageSwitcher?: boolean;
}

export {CredentialsConfig, Config};
3 changes: 2 additions & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ async function run(): Promise<void> {
file: core.getInput('file'),
branch: core.getInput('branch'),
destination: core.getInput('destination'),
languages: core.getMultilineInput('languages')
languages: core.getMultilineInput('languages'),
languageSwitcher: core.getBooleanInput('language_switcher')
};

let credentialsConfig: CredentialsConfig = {
Expand Down
71 changes: 61 additions & 10 deletions src/translator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@ import crowdin, {SourceFilesModel, TranslationsModel} from '@crowdin/crowdin-api
import {Config, CredentialsConfig} from './config';
import {Logger} from './logger';
import BuildRequest = TranslationsModel.BuildRequest;
import {downloadZipAndUnzip} from './utils';
import {baseName, downloadZipAndUnzip} from './utils';

export class Translator {
private placeholderStart = '<!-- README-TRANSLATE-LANGUAGES-START -->';
private placeholderEnd = '<!-- README-TRANSLATE-LANGUAGES-END -->';

private credentials: CredentialsConfig;
private config: Config;
private logger: Logger;
Expand All @@ -25,11 +28,21 @@ export class Translator {
public async translate(): Promise<void> {
this.logger.log('info', 'Start...');

const switcher = await this.renderSwitcher();

if (this.config.languageSwitcher) {
await this.addLanguageSwitcher(this.config.file, switcher);
}

await this.uploadSources();

const translationsUrl = await this.downloadTranslations();

await downloadZipAndUnzip(translationsUrl, this.config.destination);
const translationFiles = await downloadZipAndUnzip(translationsUrl, this.config.destination);

translationFiles.map(async (file: string): Promise<void> => {
await this.addLanguageSwitcher(file, switcher);
});

this.logger.log('info', 'Done!');
}
Expand All @@ -38,8 +51,10 @@ export class Translator {
this.logger.log('info', 'Uploading sources...');

const readmeFile = await this.getFile();
const exportPattern = '/README.%locale%.md';

const storageFile = await this.crowdin.uploadStorageApi.addStorage(
this.config.file,
baseName(this.config.file),
fs.readFileSync(this.config.file)
);

Expand All @@ -65,25 +80,61 @@ export class Translator {
await this.crowdin.sourceFilesApi.updateOrRestoreFile(this.credentials.projectId, readmeFile.id, {
storageId: storageFile.data.id,
exportOptions: {
exportPattern: '/README.%locale%.md'
exportPattern: exportPattern
}
});

this.logger.log('info', 'Source file successfully updated!');
} else {
await this.crowdin.sourceFilesApi.createFile(this.credentials.projectId, {
storageId: storageFile.data.id,
name: this.config.file,
name: baseName(this.config.file),
branchId: branchId,
exportOptions: {
exportPattern: '/README.%locale%.md'
exportPattern: exportPattern
}
});

this.logger.log('info', 'Source file successfully created!');
}
}

private async addLanguageSwitcher(file: string, switcher: string): Promise<void> {
this.logger.log('info', `Adding language switcher to ${file}...`);

let fileContents = fs.readFileSync(file).toString();

if (!fileContents.includes(this.placeholderStart) || !fileContents.includes(this.placeholderEnd)) {
this.logger.log(
'warning',
`Skipped! Please add ${this.placeholderStart} and ${this.placeholderEnd} to your README.md`
);

return;
}

const sliceFrom = fileContents.indexOf(this.placeholderStart) + this.placeholderStart.length;
const sliceTo = fileContents.indexOf(this.placeholderEnd);

fileContents = `${fileContents.slice(0, sliceFrom)}\n${switcher}\n${fileContents.slice(sliceTo)}`;

this.logger.log('debug', fileContents);

fs.writeFileSync(file, fileContents);
}

private async renderSwitcher(): Promise<string> {
const project = await this.crowdin.projectsGroupsApi.getProject(this.credentials.projectId);

let languages: string[] = [];

project.data.targetLanguages.map(language => {
languages.push(`[${language.name}](${this.config.destination}/README.${language.locale}.md)`);
});

return languages.join(' | ');
}

private async downloadTranslations(): Promise<string> {
this.logger.log('info', 'Downloading translations...');

Expand Down Expand Up @@ -112,8 +163,6 @@ export class Translator {
result.data.id
);

this.logger.log('info', `Url: ${translations.data.url}`);

this.logger.log('info', 'Translations downloaded!');

return translations.data.url;
Expand All @@ -136,10 +185,12 @@ export class Translator {
}

private getFilePath(): string {
const fileName = baseName(this.config.file);

if (this.config.branch) {
return `/${this.config.branch}/${this.config.file}`;
return `/${this.config.branch}/${fileName}`;
} else {
return `/${this.config.file}`;
return `/${fileName}`;
}
}
}
14 changes: 11 additions & 3 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,24 @@ import AdmZip from 'adm-zip';
import axios from 'axios';
import {toPlatformPath} from '@actions/core';

const downloadZipAndUnzip = async (zipUrl: string, destination: string): Promise<void> => {
const downloadZipAndUnzip = async (zipUrl: string, destination: string): Promise<string[]> => {
const response = await axios.get(zipUrl, {responseType: 'arraybuffer'});
const zipFilePath = toPlatformPath(`${destination}/translations-${Date.now()}.zip`);
const zipFilePath = toPlatformPath(`Translations-${Date.now()}.zip`);

fs.writeFileSync(zipFilePath, response.data);

const zip = new AdmZip(zipFilePath);
zip.extractAllTo(toPlatformPath(destination), true);

const entries: string[] = zip.getEntries().map(entry => entry.entryName);

fs.unlinkSync(zipFilePath);

return entries;
};

const baseName = (filePath: string): string => {
return filePath.split('/').pop() || filePath;
};

export {downloadZipAndUnzip};
export {downloadZipAndUnzip, baseName};

0 comments on commit 509d121

Please sign in to comment.