I'm trying to write a plugin that translate entries programmatically based on subsites that have different language setup.

Normally what happens is this: you setup multi-site, set each sub-site as having a different language and a different url (like @web/de, @web/sv etc), you set a field as translatable by language, then you edit an entry on each of those sub-sites. And the system knows to separate entries by siteId.

Now, I tried the same approach programmatically. I get all the entries for all the subsites like this:

$sites = Craft::$app->getSites()
/** @var Entry[] $entries */
$entries = [];
foreach ($sites as $site) {
    /** @var Entry $entry */
    $entries[$site->language] = Entry::find()

The I go and get all the fields for the English entry (in my setup is safe to assume that all the sub-sites respect the same field layout/structure). To test this, I dumped the values to compare the English version of the field, with the Swedish version. Values were different, which means I'm looking at the correct fields for the correct sub-entry.

//this is how we get the english fields
$fields = $entries['en']->getType()

//this is how we translate each field for each language
foreach ($fields as $field) {
    if ($field->translationMethod === \craft\base\Field::TRANSLATION_METHOD_LANGUAGE) {
        //get english value
        $enValue = $entries['en']->getFieldValue($field->handle);
        if (!empty($enValue)) {
            //translate in all languages
            $translations = $this->translate($enValue, array_keys($entries));
            foreach ($entries as $lang => $entry) {
                $fieldValue = $entry->getFieldValue($field->handle);
                //only translate if the destination language/field is in English (not translated yet)
                if (('en' !== $lang) && !empty($enValue) && ($enValue === $fieldValue)) {
                    if (!empty($translations[$lang])) {
                        //setFieldValue for each language
                        $entry->setFieldValue($field->handle, $translations[$lang]);

This is how I try to save the entries at the end of it all

//save all non-eng entries
$elements = new Elements();
foreach ($entries as $lang => $entry) {
    if (('en' !== $lang) && !$elements->saveElement($entry)) {
        echo json_encode($entry->getErrorSummary(true));

The issue is that it saves the last translated language in all the sub-sites, for some reason, and I don't understand what I'm doing wrong.

  • $elements->saveElement($entry, true, false) does the same thing, by the way. Feb 11 at 13:44
  • This might be relevant (ie using enabledForSite)... Feb 11 at 18:33
  • None of those 2 solutions work with my example, tested them both. Probably because I'm not saving a new entry, but an existing one, who has already been propagated on the first save (when it was created). Feb 11 at 23:40
  • Have you checked that the site ID is correctly set on the entry before saving?
    – Ben Croker
    Feb 12 at 15:02
  • @BenCroker shouldn't it already be set since I'm pulling an already saved entry from the DB? Feb 12 at 17:05

There's no clear question here, instead you've thrown up a bunch of code and said that it's not working. My suggestion would be to double check that the entry fields are indeed being populated with the appropriate translations. So double check whether the following line is actually executed.

$entry->setFieldValue($field->handle, $translations[$lang]);

If it is, then I would expect the save to work. One thing to be aware of, is that the saveElement method accepts a boolean value as the third parameter which specifies whether to propagate the entry. This is true by default, meaning that the entry is propogated for each site each time you save it. Maybe consider setting it to false or better yet, modifying your saving strategy to be more performant.

if (('en' !== $lang) && !$elements->saveElement($entry, true, false)) {

See the method signature at: https://github.com/craftcms/cms/blob/e55041d4c24ea827b13ee4367d3fae52df6ab80f/src/services/Elements.php#L746

  • I guess my question was: Am I correct in assuming that the only difference between the entries is indeed just the siteId, even when the entry id stays the same? Feb 15 at 10:46
  • 1
    In that case, yes.
    – Ben Croker
    Feb 15 at 19:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.