protected function loadTranslationImpl(PersistentObject $object, $lang, $useDefaults=true) {
    if ($object == null) {
      throw new IllegalArgumentException('Cannot load translation for null');
    }

    $oidStr = $object->getOID()->__toString();

    $cacheKey = $oidStr.'.'.$lang.'.'.$useDefaults;
    if (!isset($this->translatedObjects[$cacheKey])) {
      $translatedObject = $object;

      // load the translations and translate the object for any language
      // different to the default language
      // NOTE: the original object will be detached from the transaction
      if ($lang != $this->getDefaultLanguage()) {
        $transaction = $this->persistenceFacade->getTransaction();
        $transaction->detach($translatedObject->getOID());
        $translatedObject = clone $object;

        $query = new ObjectQuery($this->translationType, __CLASS__.'load_save');
        $tpl = $query->getObjectTemplate($this->translationType);
        $tpl->setValue('objectid', Criteria::asValue('=', $oidStr));
        $tpl->setValue('language', Criteria::asValue('=', $lang));
        $translationInstances = $query->execute(BuildDepth::SINGLE);

        // create map for faster access
        $translations = [];
        foreach ($translationInstances as $translationInstance) {
          $translations[$translationInstance->getValue('attribute')] = $translationInstance->getValue('translation');
        }

        // set the translated values in the object
        $iter = new NodeValueIterator($object, false);
        for($iter->rewind(); $iter->valid(); $iter->next()) {
          $valueName = $iter->key();
          $translation = isset($translations[$valueName]) ? $translations[$valueName] : '';
          $this->setTranslatedValue($translatedObject, $valueName, $translation, $useDefaults);
        }
      }
      $this->translatedObjects[$cacheKey] = $translatedObject;
    }
    return $this->translatedObjects[$cacheKey];
  }