Skip to content

Commit 40eb28b

Browse files
committed
Init
0 parents  commit 40eb28b

File tree

18 files changed

+918
-0
lines changed

18 files changed

+918
-0
lines changed

.editorconfig

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# This file is for unifying the coding style for different editors and IDEs
2+
# editorconfig.org
3+
4+
[*]
5+
charset = utf-8
6+
indent_style = tab
7+
indent_size = 2
8+
end_of_line = lf
9+
insert_final_newline = true
10+
trim_trailing_whitespace = true
11+
12+
[*.php]
13+
indent_size = 2
14+
15+
[*.md,*.txt]
16+
trim_trailing_whitespace = false
17+
insert_final_newline = false
18+
19+
[composer.json]
20+
indent_size = 4

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/node_modules
2+
/vendor
3+
/composer.lock

Readme.md

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
# Machine Translation
2+
3+
This Kirby plugin allows you to automatically translate pages using the DeepL API. All field types are supported.
4+
5+
**If you are using machine translation, you should inform your users of this fact.**\
6+
For example, you could display a message like this (Text available via `t('tobiaswolf.machine-translation.info')`)\
7+
*This page has been machine translated. Despite the high quality of machine translation, the translation may contain errors.*
8+
9+
## Installation
10+
11+
### Download
12+
13+
Download and copy this repository to `/site/plugins/machine-translation`.
14+
15+
### Git submodule
16+
17+
```
18+
git submodule add https://github.com/tobiasfabian/machine-translation.git site/plugins/machine-translation
19+
```
20+
21+
### Composer
22+
23+
```
24+
composer require tobiasfabian/machine-translation
25+
```
26+
27+
## Requirements
28+
29+
- Kirby 4.0+
30+
- Authentication Key for DeepL API ([DeepL API for developers](https://www.deepl.com/de/pro#developer))
31+
32+
## Setup
33+
34+
To use this plugin you have to set your personal *Authentication Key for DeepL API*.
35+
[API Access / Authentication – DeepL Documentation](https://www.deepl.com/docs-api/api-access/authentication/)
36+
37+
```php
38+
// config/config.php
39+
return [
40+
'tobiaswolf.machine-translation.deepl.authKey' => '279a2e9d-83b3-c416-7e2d-f721593e42a0:fx',
41+
];
42+
```
43+
44+
### DeepL Options
45+
46+
You can set several options, provided by DeepL. Read more about the options in the [API Documentation](https://www.deepl.com/docs-api/translate-text/translate-text/) of DeepL. Each option has to be prefixed. `tobiaswolf.machine-translation.deepl.{Name}`
47+
48+
| Name | Type | Default | Description
49+
|---------------------------|-----------|---------------|--------
50+
| `authKey` **(required)** | `string` | `null` | You can find your Auth key on your account page. [DeepL Documentation](https://www.deepl.com/docs-api/api-access/authentication/)
51+
| `split_sentences` | `string` | `nonewlines` | Possible values are: `0` no splitting at all, whole input is treated as one sentence. `1` splits on punctuation and on newlines. `nonewlines` splits on punctuation only, ignoring newlines
52+
| `preserve_formatting` | `bool` | `false` | Sets whether the translation engine should respect the original formatting, even if it would usually correct some aspects.
53+
| `formality` | `string` | `default` | You can use one of these options: `default` (default), `more` for a more formal language, `less` for a more informal language, `prefer_more` for a more formal language if available, otherwise fallback to default formality, `prefer_less` for a more informal language if available, otherwise fallback to default formality.
54+
| `glossary_id` | `string` | `null` | Specify the glossary to use for the translation. The language pair of the glossary has to match the language pair of the request.
55+
| `tag_handling` | `string` | `html` | Sets which kind of tags should be handled. Options currently available: `xml`, `html`
56+
| `outline_detection` | `bool` | `true`   | [API Documentation](https://www.deepl.com/docs-api/translate-text/translate-text/)
57+
| `non_splitting_tags` | `array` | `null` | List of XML or HTML tags.
58+
| `splitting_tags` | `array` | `null` | List of XML or HTML tags.
59+
| `ignore_tags` | `array` | `null` | List of XML or HTML tags.
60+
61+
62+
## Usage
63+
64+
### Blueprint section
65+
66+
Add the section `machine-translate` to your blueprint to get the interface to translate the page.
67+
68+
```yaml
69+
sections:
70+
machineTranslate:
71+
type: machine-translate
72+
```
73+
74+
After the page is translated an object field `machineTranslated` with `date` and `showInfo` is saved to the translated page content. This can be used to detect machine translated pages and display a notice/warning on the frontend that the text is machine translated. You can add this object field to any fields section (optional).
75+
76+
```yaml
77+
sections:
78+
fields:
79+
type: fields
80+
fields:
81+
machineTranslated:
82+
extends: fields/machineTranslated
83+
```
84+
85+
### API endpoint
86+
87+
This plugin provides an API endpoint `/api/machine-translate/pages/(:any)` that can be used to translate an entire page. Read [Kirby’s API Guide](https://getkirby.com/docs/guide/api/introduction) to learn more about using the API.
88+
89+
The endpoint allows `get` and `post` requests. The endpoint requires a `language` (target language) query. When making a `post` request, `sourceLang` and `forceOverwrite` can be added. By default `sourceLang` is the default language. If `forceOverwrite` is false or not specified, only fields where the target field does not exist or is empty will be translated.
90+
91+
```JavaScript
92+
const pageId = 'test';
93+
const targetLang = 'es';
94+
const csrf = '';
95+
fetch(`/api/machine-translate/pages/{ pageId }?language={ targetLang }`, {
96+
method: 'post',
97+
body: JSON.stringify({
98+
sourceLang: 'en',
99+
forceOverwrite: true,
100+
}),
101+
headers: {
102+
'x-csrf': csrf,
103+
},
104+
});
105+
```
106+
107+
```php
108+
$pageId = 'test';
109+
110+
kirby()->api()->call('machine-translate/pages/' . $pageId, 'POST', [
111+
'query' => [
112+
'language' => 'en',
113+
],
114+
'body' => [
115+
'sourceLang' => 'de',
116+
'forceOverwrite' => true,
117+
],
118+
]);
119+
```
120+
121+
### Field method
122+
123+
The field method `$field->translate($targetLang, $blueprintField)` translates the field value and returns the field with the translated value. All field types are supported. The type of field is specified via `$blueprintField['type']`.
124+
125+
If you want to save the translated field, you can do this like this.
126+
127+
```php
128+
$targetLang = 'de'
129+
$text = $page->text(); // e.g. Hello World
130+
$translatedText = $text->translate($targetLang); // returns the field with the translated text (Hallo Welt)
131+
$page->update([
132+
'text' => $translatedText,
133+
], $targetLang);
134+
```
135+
136+
### Page method
137+
138+
The page method `$page->translate($targetLang, $sourceLang, $force)` allows you to translate the content of a page into a target language. By default already translated fields will not be overwritten. By setting `$force` to `true` all fields will be translated, existing fields will be overwritten.
139+
140+
An object field `machineTranslated` with `date` and `showInfo` is added to the translated page content. This can be used to detect machine translated pages and display a notice/warning on the frontend that the text is machine translated.
141+
142+
### Translate Class
143+
144+
If you want to, you can use the static method of the `Translate` class. Use the `translate($text, $targetLang, $sourceLang)` method to translate text. Make sure you pass an array as the first parameter (you can translate multiple texts at once). You can omit the third parameter to have the source language automatically detected.
145+
146+
```php
147+
use Tobiaswolf\MachineTranslation\Translate;
148+
149+
$sourceTexts = ['Hello World', 'Greetings Earthlings'];
150+
$translatedTexts = Translate::translate($sourceTexts, 'es');
151+
152+
var_dump($translatedTexts);
153+
// array(2) { [0]=> array(2) { ["detected_source_language"]=> string(2) "EN" ["text"]=> string(10) "Hola Mundo" } [1]=> array(2) { ["detected_source_language"]=> string(2) "EN" ["text"]=> string(19) "Saludos terrícolas" } }
154+
```
155+
156+
### Cache
157+
158+
Each request to DeepL is cached. This has the advantage that if the same text appears more than once on the website, a new request is not always made. This saves you *Character usage* of your DeepL plan.
159+
160+
You can disable the cache via config.
161+
162+
```php
163+
// config/config.php
164+
return [
165+
'cache.tobiaswolf.machine-translation.translate' => false, // default true
166+
]
167+
```
168+
169+
## License
170+
171+
MIT
172+
173+
## Credits
174+
175+
- [Tobias Wolf](https://github.com/tobiasfabian)

api/routes/machine-translate.php

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
use Kirby\Api\Api;
4+
use Kirby\Cms\Find;
5+
use Kirby\Cms\Page;
6+
use Kirby\Exception\Exception;
7+
8+
return [
9+
[
10+
/**
11+
* Translates the entire page.
12+
* Returns the translated page.
13+
*/
14+
'pattern' => 'machine-translate/pages/(:any)',
15+
'auth' => false,
16+
'method' => 'POST|GET',
17+
'action' => function (string $pageId): Page
18+
{
19+
/** @var Api $this */
20+
21+
$page = Find::page($pageId);
22+
$targetLang = $this->requestQuery('language');
23+
$sourceLang = $this->requestBody('sourceLang', $this->kirby()->defaultLanguage()->code());
24+
$forceOverwrite = (bool)$this->requestBody('forceOverwrite', false);
25+
26+
if (!is_string($targetLang)) {
27+
throw new Exception('Missing “targetLang” in post request body.');
28+
}
29+
30+
if (is_string($sourceLang)) {
31+
$this->kirby()->setCurrentLanguage($sourceLang);
32+
}
33+
34+
$page = $page->machineTranslate($targetLang, $sourceLang, $forceOverwrite);
35+
36+
$this->kirby()->setCurrentLanguage($targetLang);
37+
38+
return $page;
39+
}
40+
]
41+
];
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
label: tobiaswolf.machine-translation.panel.label
2+
type: object
3+
fields:
4+
date:
5+
type: date
6+
time:
7+
step: 1
8+
help: tobiaswolf.machine-translation.panel.date.help
9+
showInfo:
10+
label: tobiaswolf.machine-translation.panel.showInfo.label
11+
type: toggle
12+
default: true
13+
help: tobiaswolf.machine-translation.panel.showInfo.help

composer.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"name": "tobiaswolf/machine-translation",
3+
"description": "Translate pages/fields with Deepl API",
4+
"version": "1.0.0-beta.1",
5+
"type": "kirby-plugin",
6+
"license": "MIT",
7+
"authors": [
8+
{
9+
"name": "Tobias Wolf",
10+
"email": "hello@tobiaswolf.me"
11+
}
12+
],
13+
"require": {
14+
"getkirby/composer-installer": "^1.1"
15+
},
16+
"config": {
17+
"allow-plugins": {
18+
"getkirby/composer-installer": true
19+
}
20+
}
21+
}

field-methods/translate.php

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php
2+
3+
use Kirby\Content\Field;
4+
use Kirby\Toolkit\Str;
5+
use Tobiaswolf\MachineTranslation\Translate;
6+
7+
/**
8+
* Translates the given field to the target language based on the field type.
9+
*
10+
* @param Field $field The field to be translated.
11+
* @param string $targetLang The target language code (e.g., 'en', 'fr', 'es').
12+
* @param array|null $blueprintField (Optional) The blueprint settings for the field, if not provided, it will be fetched from the parent model. If provided, it must contain 'type' and can have 'translate'.
13+
*
14+
* @return Field|null Returns the translated field as a Field object, or null if translation is not allowed or not possible.
15+
*/
16+
return function (Field $field, string $targetLang, ?array $blueprintField = null): ?Field
17+
{
18+
$key = $field->key();
19+
20+
// try to get the blueprint field from parent model (page/site)
21+
if ($blueprintField === null) {
22+
// All fields of the page’s blueprint (with lowercase keys)
23+
$blueprintFields = array_change_key_case($field->model()->blueprint()->fields(), CASE_LOWER);
24+
$defaulBlueprintField = [
25+
'type' => 'text',
26+
'translate' => true,
27+
];
28+
$blueprintField = $blueprintFields[$field->key()] ?? $defaulBlueprintField;
29+
}
30+
31+
if (($blueprintField['translate'] ?? true) === false || $key === 'uuid' || $field->isEmpty()) {
32+
return null;
33+
}
34+
35+
switch ($blueprintField['type']) {
36+
case 'tags':
37+
case 'text':
38+
case 'textarea':
39+
case 'writer':
40+
$field = Translate::translateTextField($field, $targetLang);
41+
break;
42+
case 'blocks':
43+
$field = Translate::translateBlocksField($field, $targetLang, $blueprintField);
44+
break;
45+
case 'layout':
46+
$field = Translate::translateLayoutField($field, $targetLang, $blueprintField);
47+
break;
48+
case 'structure':
49+
$field = Translate::translateStructureField($field, $targetLang, $blueprintField);
50+
break;
51+
case 'object':
52+
$field = Translate::translateObjectField($field, $targetLang, $blueprintField);
53+
break;
54+
}
55+
56+
if ($field->value !== null && is_string($field->value)) {
57+
$field->value = Str::replace($field->value, '&amp;', '&');
58+
}
59+
60+
return $field;
61+
};

index.css

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

index.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

index.php

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
use Kirby\Cms\App;
4+
use Kirby\Filesystem\F;
5+
6+
F::loadClasses([
7+
'Tobiaswolf\\MachineTranslation\\Translate' => 'lib/Translate.php'
8+
], __DIR__);
9+
10+
App::plugin('tobiaswolf/machine-translation', [
11+
'api' => [
12+
'routes' => array_merge(
13+
include __DIR__ . '/api/routes/machine-translate.php',
14+
),
15+
],
16+
'blueprints' => [
17+
'fields/machineTranslated' => __DIR__ . '/blueprints/fields/machineTranslated.yml'
18+
],
19+
'fieldMethods' => [
20+
'translate' => require_once __DIR__ . '/field-methods/translate.php',
21+
],
22+
'options' => [
23+
'deepl' => [
24+
'authKey' => null,
25+
'split_sentences' => 'nonewlines',
26+
'preserve_formatting' => false,
27+
'formality' => 'default',
28+
'glossary_id' => null,
29+
'tag_handling' => 'html',
30+
'outline_detection' => true,
31+
'non_splitting_tags' => null,
32+
'splitting_tags' => null,
33+
'ignore_tags' => null,
34+
],
35+
'cache.translate' => true,
36+
],
37+
'pageMethods' => [
38+
'machineTranslate' => require_once __DIR__ . '/page-methods/machineTranslate.php',
39+
],
40+
'sections' => [
41+
'machine-translate' => require_once __DIR__ . '/sections/machineTranslate.php',
42+
],
43+
'translations' => [
44+
'de' => require_once __DIR__ . '/translations/de.php',
45+
'en' => require_once __DIR__ . '/translations/en.php',
46+
]
47+
]);

0 commit comments

Comments
 (0)