Commits

Bogdan Savluk committed 3bcd601

GalleryPhoto i18n support.

  • Participants
  • Parent commits 7a46d42

Comments (0)

Files changed (8)

File GalleryController.php

 
             $model = new GalleryPhoto();
             $model->gallery_id = $gallery_id;
+            $i18nModels = array();
             if (isset($_POST['GalleryPhoto']))
                 $model->attributes = $_POST['GalleryPhoto'];
-
+            foreach (Yii::app()->params['languages'] as $lang) {
+                $i18nModel = new GalleryPhotoI18n();
+                $i18nModel->lang = $lang;
+                if (isset($_POST['GalleryPhotoI18n']) && isset($_POST['GalleryPhotoI18n'][$lang]))
+                    $model->attributes = $_POST['GalleryPhotoI18n'][$lang];
+                $i18nModels[$lang] = $i18nModel;
+            }
             $imageFile = CUploadedFile::getInstance($model, 'image');
             $model->file_name = $imageFile->getName();
             $model->save();
 
+            foreach (Yii::app()->params['languages'] as $lang) {
+                $i18nModels[$lang]->id = $model->id;
+                $i18nModels[$lang]->save();
+            }
+
             $model->setImage($imageFile->getTempName());
             header("Content-Type: application/json");
+
+            $i18ns = array();
+            foreach (Yii::app()->params['languages'] as $lang) {
+                $i18ns[$lang] = array(
+                    'name' => (string)$i18nModels[$lang]->name,
+                    'description' => (string)$i18nModels[$lang]->description,
+                );
+            }
             echo CJSON::encode(
                 array(
                     'id' => $model->id,
                     'rank' => $model->rank,
-                    'name' => (string)$model->name, //todo: something wrong with model - it returns null, but it must return an empty string
-                    'description' => (string)$model->description,
+                    'i18ns' => $i18ns,
                     'preview' => $model->getPreview(),
                 ));
         } else throw new CHttpException(403);
             /** @var $models GalleryPhoto[] */
             $models = GalleryPhoto::model()->findAll($criteria);
             foreach ($data as $id => $attributes) {
-                if (isset($attributes['name']))
-                    $models[$id]->name = $attributes['name'];
-                if (isset($attributes['description']))
-                    $models[$id]->description = $attributes['description'];
-                $models[$id]->save();
+                $i18nModels = $models[$id]->i18ns;
+                foreach (Yii::app()->params['languages'] as $lang) {
+                    if (isset($attributes[$lang])) {
+                        if (isset($attributes[$lang]['name']))
+                            $i18nModels[$lang]->name = $attributes[$lang]['name'];
+                        if (isset($attributes[$lang]['description']))
+                            $i18nModels[$lang]->description = $attributes[$lang]['description'];
+                        $i18nModels[$lang]->save();
+                    }
+                }
             }
             $resp = array();
             foreach ($models as $model) {
+                $i18ns = array();
+                foreach (Yii::app()->params['languages'] as $lang) {
+                    $i18ns[$lang] = array(
+                        'name' => (string)$model->i18ns[$lang]->name,
+                        'description' => (string)$model->i18ns[$lang]->description,
+                    );
+                }
                 $resp[] = array(
                     'id' => $model->id,
                     'rank' => $model->rank,
-                    'name' => (string)$model->name, //todo: something wrong with model - it returns null, but it must return an empty string
-                    'description' => (string)$model->description,
+                    'i18ns' => $i18ns,
                     'preview' => $model->getPreview(),
                 );
             }

File GalleryManager.php

         if ($this->controllerRoute === null)
             throw new CException('$controllerRoute must be set.', 500);
 
+        $photos = array();
+        foreach ($this->gallery->galleryPhotos as $photo) {
+            $i18ns = array();
+            foreach (Yii::app()->params['languages'] as $lang) {
+                $i18ns[$lang] = array(
+                    'name' => (string)$photo->i18ns[$lang]->name,
+                    'description' => (string)$photo->i18ns[$lang]->description,
+                );
+            }
+            $photos[] = array(
+                'id' => $photo->id,
+                'rank' => $photo->rank,
+                'i18ns' => $i18ns,
+                'preview' => $photo->getPreview(),
+            );
+        }
         $opts = CJavaScript::encode(array(
-            'hasName:' => $this->gallery->name ? true : false,
-            'hasDesc:' => $this->gallery->description ? true : false,
+            'lang' => Yii::app()->language,
+            'langs' => Yii::app()->params['languages'],
+            'hasName' => $this->gallery->name ? true : false,
+            'hasDesc' => $this->gallery->description ? true : false,
             'uploadUrl' => Yii::app()->createUrl($this->controllerRoute . '/ajaxUpload', array('gallery_id' => $this->gallery->id)),
             'deleteUrl' => Yii::app()->createUrl($this->controllerRoute . '/delete'),
             'updateUrl' => Yii::app()->createUrl($this->controllerRoute . '/changeData'),
             'arrangeUrl' => Yii::app()->createUrl($this->controllerRoute . '/order'),
             'nameLabel' => Yii::t('galleryManager.main', 'Name'),
             'descriptionLabel' => Yii::t('galleryManager.main', 'Description'),
+            'photos'=>$photos,
         ));
         $src = "$('#{$this->id}').galleryManager({$opts});";
         $cs->registerScript('galleryManager#' . $this->id, $src);

File assets/jquery.galleryManager.js

     function galleryManager(el, options) {
         //Defaults:
         this.defaults = {
+            lang:'ru',
+            langs:['ru', 'en'],
             nameLabel:'Name',
             descriptionLabel:'Description',
 
             uploadUrl:'',
             deleteUrl:'',
             updateUrl:'',
-            arrangeUrl:''
+            arrangeUrl:'',
+            photos:[]
         };
 
         //Extending options:
         var $sorter = $('.sorter', $gallery);
         var $images = $('.images', $sorter);
         var $editorModal = $('.editor-modal', $gallery);
-        var $editorForm = $('.form', $editorModal);
 
 
-        function photoEditorTemplate(id, src, name, description) {
+        function photoEditorTemplate(id, src, name, description, lang) {
             return '<div class="photo-editor">' +
                 '<div class="preview"><img src="' + src + '" alt=""/></div>' +
                 '<div>' +
                 (opts.hasName
-                    ? '<label for="photo_name_' + id + '">' + opts.nameLabel + ':</label>' +
-                    '<input type="text" name="photo[' + id + '][name]" class="input-xlarge" value="' + name + '" id="photo_name_' + id + '"/>'
+                    ? '<label for="photo_name_' + id + '_' + lang + '">' + opts.nameLabel + ':</label>' +
+                    '<input type="text" name="photo[' + id + '][' + lang + '][name]" class="input-xlarge" value="' + name + '" id="photo_name_' + id + '_' + lang + '"/>'
                     : '') +
                 (opts.hasDesc
-                    ? '<label for="photo_description_' + id + '">' + opts.descriptionLabel + ':</label>' +
-                    '<textarea name="photo[' + id + '][description]" rows="3" cols="40" class="input-xlarge" id="photo_description_' + id + '">' + description + '</textarea>'
+                    ? '<label for="photo_description_' + id + '_' + lang + '">' + opts.descriptionLabel + ':</label>' +
+                    '<textarea name="photo[' + id + '][' + lang + '][description]" rows="3" cols="40" class="input-xlarge" id="photo_description_' + id + '_' + lang + '">' + description + '</textarea>'
                     : '') +
                 '</div>' +
                 '</div>';
             e.preventDefault();
             var id = $(this).data('photo-id');
             var photo = $(this).parents('.photo');
-            var src = $('img', photo[0]).attr('src');
-            var name = $('.caption h5', photo[0]).text();
-            var description = $('.caption p', photo[0]).text();
-            $editorForm.html(photoEditorTemplate(id, src, name, description));
+            var photoData = photo.data('data');
+            for (var i in opts.langs) {
+                var lang = opts.langs[i];
+                $('#' + opts.wId + '_editor_tab_' + lang + ' .form', $editorModal).html(photoEditorTemplate(id, photoData['preview'], photoData['i18ns'][lang]['name'], photoData['i18ns'][lang]['description'], lang))
+            }
             $editorModal.modal('show');
             return false;
         }
             $('.photo-select', newOne).change(selectChanged);
         }
 
-        bindPhotoEvents($('.photo', $gallery));
 
         $('.images', $sorter).sortable().disableSelection().bind("sortstop", function () {
             $.post(opts.arrangeUrl, $('input', $sorter).serialize() + '&ajax=true', function () {
                 e.preventDefault();
                 var filesCount = this.files.length;
                 var uploadedCount = 0;
-                $editorForm.html('');
+                $('.form', $editorModal).html('');
 
                 for (var i = 0; i < filesCount; i++) {
                     var fd = new FormData();
                         uploadedCount++;
                         if (this.status == 200) {
                             var resp = JSON.parse(this.response);
-                            var newOne = $(photoTemplate(resp['id'], resp['preview'], resp['name'], resp['description'], resp['rank']));
+                            var newOne = $(photoTemplate(resp['id'], resp['preview'], resp['i18ns'][opts.lang]['name'], resp['i18ns'][opts.lang]['description'], resp['rank'])).data('data', resp);
 
                             bindPhotoEvents(newOne);
 
                             $images.append(newOne);
-                            if (opts.hasName || opts.hasDesc)
-                                $editorForm.append($(photoEditorTemplate(resp['id'], resp['preview'], resp['name'], resp['description'])));
+                            if (opts.hasName || opts.hasDesc){
+                                for (var i in opts.langs) {
+                                    var lang = opts.langs[i];
+                                    $('#' + opts.wId + '_editor_tab_' + lang + ' .form', $editorModal).append(photoEditorTemplate(resp['id'], resp['preview'], resp['i18ns'][lang]['name'], resp['i18ns'][lang]['description'], lang))
+                                }
+                            }
                         }
                         if (uploadedCount === filesCount && (opts.hasName || opts.hasDesc)) $editorModal.modal('show');
                     };
             $('.afile', $gallery).on('change', function (e) {
 
                 e.preventDefault();
-                $editorForm.html('');
+                $('.form', $editorModal).html('');
 
                 $.ajax(
                     opts.uploadUrl, {
                         iframe:true,
                         dataType:"json"
                     }).done(function (resp) {
-                        var newOne = $(photoTemplate(resp['id'], resp['preview'], resp['name'], resp['description'], resp['rank']));
+                        var newOne = $(photoTemplate(resp['id'], resp['preview'], resp['i18ns'][opts.lang]['name'], resp['i18ns'][opts.lang]['description'], resp['rank'])).data('data', resp);
                         bindPhotoEvents(newOne);
                         $images.append(newOne);
-                        if (opts.hasName || opts.hasDesc)
-                            $editorForm.append($(photoEditorTemplate(resp['id'], resp['preview'], resp['name'], resp['description'])));
-
+                        if (opts.hasName || opts.hasDesc){
+                            for (var i in opts.langs) {
+                                var lang = opts.langs[i];
+                                $('#' + opts.wId + '_editor_tab_' + lang + ' .form', $editorModal).append(photoEditorTemplate(resp['id'], resp['preview'], resp['i18ns'][lang]['name'], resp['i18ns'][lang]['description'], lang))
+                            }
+                        }
                         if (opts.hasName || opts.hasDesc) $editorModal.modal('show');
                     });
 
 
         $('.save-changes', $editorModal).click(function (e) {
             e.preventDefault();
-            $.post(opts.updateUrl, $('input, textarea', $editorForm).serialize() + '&ajax=true', function (data) {
+            $.post(opts.updateUrl, $('.form input, .form textarea', $editorModal).serialize() + '&ajax=true', function (data) {
                 var count = data.length;
                 for (var key = 0; key < count; key++) {
                     var p = data[key];
                     var photo = $('#' + opts.wId + '-' + p.id);
+                    photo.data('data', p);
                     $('img', photo).attr('src', p['src']);
                     if (opts.hasName)
-                        $('.caption h5', photo).text(p['name']);
+                        $('.caption h5', photo).text(p['i18ns'][opts.lang]['name']);
                     if (opts.hasDesc)
-                        $('.caption p', photo).text(p['description']);
+                        $('.caption p', photo).text(p['i18ns'][opts.lang]['description']);
                 }
                 $editorModal.modal('hide');
                 //deselect all items after editing
         $('.edit_selected', $gallery).click(function (e) {
             e.preventDefault();
             var cc = 0;
-            var form = $editorForm.html('');
+            //todo
+            var form = $('.form', $editorModal).html('');
             $('.photo.selected', $sorter).each(function () {
                 cc++;
                 var photo = $(this),
-                    id = photo.attr('id').substr((opts.wId + '-').length),
-                    src = $('img', photo[0]).attr('src'),
-                    name = $('.caption h5', photo[0]).text(),
-                    description = $('.caption p', photo[0]).text();
-                form.append(photoEditorTemplate(id, src, name, description));
+                    id = photo.attr('id').substr((opts.wId + '-').length);
+                var photoData = photo.data('data');
+                for (var i in opts.langs) {
+                    var lang = opts.langs[i];
+                    $('#' + opts.wId + '_editor_tab_' + lang + ' .form', $editorModal).append(photoEditorTemplate(id, photoData['preview'], photoData['i18ns'][lang]['name'], photoData['i18ns'][lang]['description'], lang))
+                }
             });
             if (cc > 0)$editorModal.modal('show');
             return false;
                     success:function (t) {
                         if (t == 'OK') $('#' + opts.wId + '-' + id).remove();
                         else alert(t);
+                        updateButtons();
                     }});
             });
         });
             }
             updateButtons();
         });
+
+        for (var i in opts.photos) {
+            var resp = opts.photos[i];
+            var newOne = $(photoTemplate(resp['id'], resp['preview'], resp['i18ns'][opts.lang]['name'], resp['i18ns'][opts.lang]['description'], resp['rank'])).data('data', resp);
+
+            bindPhotoEvents(newOne);
+            $images.append(newOne);
+        }
     }
 
     // The actual plugin

File assets/jquery.galleryManager.min.js

-(function(a){function b(b,c){function j(a,b,c,e){return'<div class="photo-editor"><div class="preview"><img src="'+b+'" alt=""/></div>'+"<div>"+(d.hasName?'<label for="photo_name_'+a+'">'+d.nameLabel+":</label>"+'<input type="text" name="photo['+a+'][name]" class="input-xlarge" value="'+c+'" id="photo_name_'+a+'"/>':"")+(d.hasDesc?'<label for="photo_description_'+a+'">'+d.descriptionLabel+":</label>"+'<textarea name="photo['+a+'][description]" rows="3" cols="40" class="input-xlarge" id="photo_description_'+a+'">'+e+"</textarea>":"")+"</div>"+"</div>"}function k(a,b,c,e,f){var g='<div id="'+d.wId+"-"+a+'" class="photo">'+'<div class="image-preview"><img src="'+b+'"/></div><div class="caption">';return d.hasName&&(g+="<h5>"+c+"</h5>"),d.hasDesc&&(g+="<p>"+e+"</p>"),g+='</div><input type="hidden" name="order['+a+']" value="'+f+'"/><div class="actions">'+(d.hasName||d.hasDesc?'<span data-photo-id="'+a+'" class="editPhoto btn btn-primary"><i class="icon-edit icon-white"></i></span> ':"")+'<span data-photo-id="'+a+'" class="deletePhoto btn btn-danger"><i class="icon-remove icon-white"></i></span>'+'</div><input type="checkbox" class="photo-select"/></div>',g}function l(b){b.preventDefault();var c=a(this).data("photo-id");return a.ajax({type:"POST",url:d.deleteUrl,data:"id="+c,success:function(b){b=="OK"?a("#"+d.wId+"-"+c).remove():alert(b)}}),!1}function m(b){b.preventDefault();var c=a(this).data("photo-id"),d=a(this).parents(".photo"),e=a("img",d[0]).attr("src"),f=a(".caption h5",d[0]).text(),g=a(".caption p",d[0]).text();return i.html(j(c,e,f,g)),h.modal("show"),!1}function n(){var b=a(".photo.selected",f).length;a(".select_all",e).prop("checked",a(".photo",f).length==b),b==0?a(".edit_selected, .remove_selected",e).addClass("disabled"):a(".edit_selected, .remove_selected",e).removeClass("disabled")}function o(){var b=a(this);b.is(":checked")?b.parent().addClass("selected"):b.parent().removeClass("selected"),n()}function p(b){a(".deletePhoto",b).click(l),a(".editPhoto",b).click(m),a(".photo-select",b).change(o)}this.defaults={nameLabel:"Name",descriptionLabel:"Description",hasName:!0,hasDesc:!0,uploadUrl:"",deleteUrl:"",updateUrl:"",arrangeUrl:""};var d=a.extend({},this.defaults,c),e=a(b);d.wId=e.attr("id");var f=a(".sorter",e),g=a(".images",f),h=a(".editor-modal",e),i=a(".form",h);p(a(".photo",e)),a(".images",f).sortable().disableSelection().bind("sortstop",function(){a.post(d.arrangeUrl,a("input",f).serialize()+"&ajax=true",function(){},"json")}),typeof window.FormData=="function"?a(".afile",e).attr("multiple","true").on("change",function(b){b.preventDefault();var c=this.files.length,e=0;i.html("");for(var f=0;f<c;f++){var l=new FormData;l.append(this.name,this.files[f]);var m=new XMLHttpRequest;m.open("POST",d.uploadUrl,!0),m.onload=function(){e++;if(this.status==200){var b=JSON.parse(this.response),f=a(k(b.id,b.preview,b.name,b.description,b.rank));p(f),g.append(f),(d.hasName||d.hasDesc)&&i.append(a(j(b.id,b.preview,b.name,b.description)))}e===c&&(d.hasName||d.hasDesc)&&h.modal("show")},m.send(l)}}):a(".afile",e).on("change",function(b){b.preventDefault(),i.html(""),a.ajax(d.uploadUrl,{files:a(this),iframe:!0,dataType:"json"}).done(function(b){var c=a(k(b.id,b.preview,b.name,b.description,b.rank));p(c),g.append(c),(d.hasName||d.hasDesc)&&i.append(a(j(b.id,b.preview,b.name,b.description))),(d.hasName||d.hasDesc)&&h.modal("show")})}),a(".save-changes",h).click(function(b){b.preventDefault(),a.post(d.updateUrl,a("input, textarea",i).serialize()+"&ajax=true",function(b){var c=b.length;for(var g=0;g<c;g++){var i=b[g],j=a("#"+d.wId+"-"+i.id);a("img",j).attr("src",i.src),d.hasName&&a(".caption h5",j).text(i.name),d.hasDesc&&a(".caption p",j).text(i.description)}h.modal("hide"),a(".photo.selected",f).each(function(){a(".photo-select",this).prop("checked",!1)}).removeClass("selected"),a(".select_all",e).prop("checked",!1),n()},"json")}),a(".edit_selected",e).click(function(b){b.preventDefault();var c=0,e=i.html("");return a(".photo.selected",f).each(function(){c++;var b=a(this),f=b.attr("id").substr((d.wId+"-").length),g=a("img",b[0]).attr("src"),h=a(".caption h5",b[0]).text(),i=a(".caption p",b[0]).text();e.append(j(f,g,h,i))}),c>0&&h.modal("show"),!1}),a(".remove_selected",e).click(function(b){b.preventDefault(),a(".photo.selected",f).each(function(){var b=a(this).attr("id").substr((d.wId+"-").length);a.ajax({type:"POST",url:d.deleteUrl,data:"id="+b,success:function(c){c=="OK"?a("#"+d.wId+"-"+b).remove():alert(c)}})})}),a(".select_all",e).change(function(){a(this).prop("checked")?a(".photo",f).each(function(){a(".photo-select",this).prop("checked",!0)}).addClass("selected"):a(".photo.selected",f).each(function(){a(".photo-select",this).prop("checked",!1)}).removeClass("selected"),n()})}a.fn.galleryManager=function(a){this.length&&this.each(function(){b(this,a)})}})(jQuery);
+(function(a){function b(b,c){function i(a,b,c,e,f){return'<div class="photo-editor"><div class="preview"><img src="'+b+'" alt=""/></div>'+"<div>"+(d.hasName?'<label for="photo_name_'+a+"_"+f+'">'+d.nameLabel+":</label>"+'<input type="text" name="photo['+a+"]["+f+'][name]" class="input-xlarge" value="'+c+'" id="photo_name_'+a+"_"+f+'"/>':"")+(d.hasDesc?'<label for="photo_description_'+a+"_"+f+'">'+d.descriptionLabel+":</label>"+'<textarea name="photo['+a+"]["+f+'][description]" rows="3" cols="40" class="input-xlarge" id="photo_description_'+a+"_"+f+'">'+e+"</textarea>":"")+"</div>"+"</div>"}function j(a,b,c,e,f){var g='<div id="'+d.wId+"-"+a+'" class="photo">'+'<div class="image-preview"><img src="'+b+'"/></div><div class="caption">';return d.hasName&&(g+="<h5>"+c+"</h5>"),d.hasDesc&&(g+="<p>"+e+"</p>"),g+='</div><input type="hidden" name="order['+a+']" value="'+f+'"/><div class="actions">'+(d.hasName||d.hasDesc?'<span data-photo-id="'+a+'" class="editPhoto btn btn-primary"><i class="icon-edit icon-white"></i></span> ':"")+'<span data-photo-id="'+a+'" class="deletePhoto btn btn-danger"><i class="icon-remove icon-white"></i></span>'+'</div><input type="checkbox" class="photo-select"/></div>',g}function k(b){b.preventDefault();var c=a(this).data("photo-id");return a.ajax({type:"POST",url:d.deleteUrl,data:"id="+c,success:function(b){b=="OK"?a("#"+d.wId+"-"+c).remove():alert(b)}}),!1}function l(b){b.preventDefault();var c=a(this).data("photo-id"),e=a(this).parents(".photo"),f=e.data("data");for(var g in d.langs){var j=d.langs[g];a("#"+d.wId+"_editor_tab_"+j+" .form",h).html(i(c,f.preview,f.i18ns[j].name,f.i18ns[j].description,j))}return h.modal("show"),!1}function m(){var b=a(".photo.selected",f).length;a(".select_all",e).prop("checked",a(".photo",f).length==b),b==0?a(".edit_selected, .remove_selected",e).addClass("disabled"):a(".edit_selected, .remove_selected",e).removeClass("disabled")}function n(){var b=a(this);b.is(":checked")?b.parent().addClass("selected"):b.parent().removeClass("selected"),m()}function o(b){a(".deletePhoto",b).click(k),a(".editPhoto",b).click(l),a(".photo-select",b).change(n)}this.defaults={lang:"ru",langs:["ru","en"],nameLabel:"Name",descriptionLabel:"Description",hasName:!0,hasDesc:!0,uploadUrl:"",deleteUrl:"",updateUrl:"",arrangeUrl:"",photos:[]};var d=a.extend({},this.defaults,c),e=a(b);d.wId=e.attr("id");var f=a(".sorter",e),g=a(".images",f),h=a(".editor-modal",e);a(".images",f).sortable().disableSelection().bind("sortstop",function(){a.post(d.arrangeUrl,a("input",f).serialize()+"&ajax=true",function(){},"json")}),typeof window.FormData=="function"?a(".afile",e).attr("multiple","true").on("change",function(b){b.preventDefault();var c=this.files.length,e=0;a(".form",h).html("");for(var f=0;f<c;f++){var k=new FormData;k.append(this.name,this.files[f]);var l=new XMLHttpRequest;l.open("POST",d.uploadUrl,!0),l.onload=function(){e++;if(this.status==200){var b=JSON.parse(this.response),f=a(j(b.id,b.preview,b.i18ns[d.lang].name,b.i18ns[d.lang].description,b.rank)).data("data",b);o(f),g.append(f);if(d.hasName||d.hasDesc)for(var k in d.langs){var l=d.langs[k];a("#"+d.wId+"_editor_tab_"+l+" .form",h).append(i(b.id,b.preview,b.i18ns[l].name,b.i18ns[l].description,l))}}e===c&&(d.hasName||d.hasDesc)&&h.modal("show")},l.send(k)}}):a(".afile",e).on("change",function(b){b.preventDefault(),a(".form",h).html(""),a.ajax(d.uploadUrl,{files:a(this),iframe:!0,dataType:"json"}).done(function(b){var c=a(j(b.id,b.preview,b.i18ns[d.lang].name,b.i18ns[d.lang].description,b.rank)).data("data",b);o(c),g.append(c);if(d.hasName||d.hasDesc)for(var e in d.langs){var f=d.langs[e];a("#"+d.wId+"_editor_tab_"+f+" .form",h).append(i(b.id,b.preview,b.i18ns[f].name,b.i18ns[f].description,f))}(d.hasName||d.hasDesc)&&h.modal("show")})}),a(".save-changes",h).click(function(b){b.preventDefault(),a.post(d.updateUrl,a(".form input, .form textarea",h).serialize()+"&ajax=true",function(b){var c=b.length;for(var g=0;g<c;g++){var i=b[g],j=a("#"+d.wId+"-"+i.id);j.data("data",i),a("img",j).attr("src",i.src),d.hasName&&a(".caption h5",j).text(i.i18ns[d.lang].name),d.hasDesc&&a(".caption p",j).text(i.i18ns[d.lang].description)}h.modal("hide"),a(".photo.selected",f).each(function(){a(".photo-select",this).prop("checked",!1)}).removeClass("selected"),a(".select_all",e).prop("checked",!1),m()},"json")}),a(".edit_selected",e).click(function(b){b.preventDefault();var c=0,e=a(".form",h).html("");return a(".photo.selected",f).each(function(){c++;var b=a(this),e=b.attr("id").substr((d.wId+"-").length),f=b.data("data");for(var g in d.langs){var j=d.langs[g];a("#"+d.wId+"_editor_tab_"+j+" .form",h).append(i(e,f.preview,f.i18ns[j].name,f.i18ns[j].description,j))}}),c>0&&h.modal("show"),!1}),a(".remove_selected",e).click(function(b){b.preventDefault(),a(".photo.selected",f).each(function(){var b=a(this).attr("id").substr((d.wId+"-").length);a.ajax({type:"POST",url:d.deleteUrl,data:"id="+b,success:function(c){c=="OK"?a("#"+d.wId+"-"+b).remove():alert(c),m()}})})}),a(".select_all",e).change(function(){a(this).prop("checked")?a(".photo",f).each(function(){a(".photo-select",this).prop("checked",!0)}).addClass("selected"):a(".photo.selected",f).each(function(){a(".photo-select",this).prop("checked",!1)}).removeClass("selected"),m()});for(var p in d.photos){var q=d.photos[p],r=a(j(q.id,q.preview,q.i18ns[d.lang].name,q.i18ns[d.lang].description,q.rank)).data("data",q);o(r),g.append(r)}}a.fn.galleryManager=function(a){this.length&&this.each(function(){b(this,a)})}})(jQuery);

File models/Gallery.php

             array('name, description', 'safe'),
             // The following rule is used by search().
             // Please remove those attributes that should not be searched.
-            array('id, sizes, name, description', 'safe', 'on' => 'search'),
+            array('id, versions_data, name, description', 'safe', 'on' => 'search'),
         );
     }
 
     {
         return array(
             'id' => 'ID',
+            'versions_data' => 'Versions Data',
             'name' => 'Name',
             'description' => 'Description',
         );
         $criteria = new CDbCriteria;
 
         $criteria->compare('id', $this->id);
+        $criteria->compare('versions_data', $this->versions_data, true);
         $criteria->compare('name', $this->name);
         $criteria->compare('description', $this->description);
 

File models/GalleryPhoto.php

  * @property integer $id
  * @property integer $gallery_id
  * @property integer $rank
- * @property string $name
- * @property string $description
  * @property string $file_name
  *
  * The followings are the available model relations:
  * @property Gallery $gallery
+ * @property GalleryPhotoI18n[] $i18ns
+ * @property GalleryPhotoI18n $i18n
  *
  * @author Bogdan Savluk <savluk.bogdan@gmail.com>
  */
         // will receive user inputs.
         return array(
             array('gallery_id', 'required'),
-//            array('gallery_id, rank', 'numerical', 'integerOnly' => true),
-            array('name', 'length', 'max' => 512),
+            array('gallery_id, rank', 'numerical', 'integerOnly' => true),
             array('file_name', 'length', 'max' => 128),
             // The following rule is used by search().
             // Please remove those attributes that should not be searched.
-            array('id, gallery_id, rank, name, description, file_name', 'safe', 'on' => 'search'),
+            array('id, gallery_id, rank, file_name', 'safe', 'on' => 'search'),
         );
     }
 
         // class name for the relations automatically generated below.
         return array(
             'gallery' => array(self::BELONGS_TO, 'Gallery', 'gallery_id'),
+            'i18ns' => array(self::HAS_MANY, 'GalleryPhotoI18n', 'id', 'index' => 'lang'),
+            'i18n' => array(self::HAS_ONE, 'GalleryPhotoI18n', 'id', 'condition' => 'lang=\'' . Yii::app()->language . '\''),
         );
     }
 
             'id' => 'ID',
             'gallery_id' => 'Gallery',
             'rank' => 'Rank',
-            'name' => 'Name',
-            'description' => 'Description',
             'file_name' => 'File Name',
         );
     }
         $criteria->compare('id', $this->id);
         $criteria->compare('gallery_id', $this->gallery_id);
         $criteria->compare('rank', $this->rank);
-        $criteria->compare('name', $this->name, true);
-        $criteria->compare('description', $this->description, true);
         $criteria->compare('file_name', $this->file_name, true);
 
         return new CActiveDataProvider($this, array(

File models/GalleryPhotoI18n.php

+<?php
+
+/**
+ * This is the model class for table "gallery_photo_i18n".
+ *
+ * The followings are the available columns in table 'gallery_photo_i18n':
+ * @property integer $id
+ * @property string $lang
+ * @property string $name
+ * @property string $description
+ *
+ * The followings are the available model relations:
+ * @property GalleryPhoto $galleryPhoto
+ */
+class GalleryPhotoI18n extends CActiveRecord
+{
+    /**
+     * Returns the static model of the specified AR class.
+     * @param string $className active record class name.
+     * @return GalleryPhotoI18n the static model class
+     */
+    public static function model($className = __CLASS__)
+    {
+        return parent::model($className);
+    }
+
+    /**
+     * @return string the associated database table name
+     */
+    public function tableName()
+    {
+        return 'gallery_photo_i18n';
+    }
+
+    /**
+     * @return array validation rules for model attributes.
+     */
+    public function rules()
+    {
+        // NOTE: you should only define rules for those attributes that
+        // will receive user inputs.
+        return array(
+            array('name', 'length', 'max' => 512),
+            array('description', 'safe'),
+            // The following rule is used by search().
+            // Please remove those attributes that should not be searched.
+            array('id, lang, name, description', 'safe', 'on' => 'search'),
+        );
+    }
+
+    /**
+     * @return array relational rules.
+     */
+    public function relations()
+    {
+        // NOTE: you may need to adjust the relation name and the related
+        // class name for the relations automatically generated below.
+        return array(
+            'galleryPhoto' => array(self::BELONGS_TO, 'GalleryPhoto', 'id'),
+        );
+    }
+
+    /**
+     * @return array customized attribute labels (name=>label)
+     */
+    public function attributeLabels()
+    {
+        return array(
+            'id' => 'ID',
+            'lang' => 'Lang',
+            'name' => 'Name',
+            'description' => 'Description',
+        );
+    }
+
+    /**
+     * Retrieves a list of models based on the current search/filter conditions.
+     * @return CActiveDataProvider the data provider that can return the models based on the search/filter conditions.
+     */
+    public function search()
+    {
+        // Warning: Please modify the following code to remove attributes that
+        // should not be searched.
+
+        $criteria = new CDbCriteria;
+
+        $criteria->compare('id', $this->id);
+        $criteria->compare('lang', $this->lang, true);
+        $criteria->compare('name', $this->name, true);
+        $criteria->compare('description', $this->description, true);
+
+        return new CActiveDataProvider($this, array(
+            'criteria' => $criteria,
+        ));
+    }
+}

File views/galleryManager.php

 <?php
 /**
  * @var $this GalleryManager
- * @var $model GalleryPhoto
  *
  * @author Bogdan Savluk <savluk.bogdan@gmail.com>
  */
         <span class="btn btn-success fileinput-button">
             <i class="icon-plus icon-white"></i>
             <?php echo Yii::t('galleryManager.main', 'Add images…');?>
-            <?php echo CHtml::activeFileField($model, 'image', array('class' => 'afile', 'accept' => "image/*", 'multiple' => 'true'));?>
+            <?php echo CHtml::fileField('GalleryPhoto[image]',null, array('class' => 'afile', 'accept' => "image/*", 'multiple' => 'true'));?>
         </span>
 
         <span class="btn disabled edit_selected"><?php echo Yii::t('galleryManager.main', 'Edit selected');?></span>
     <hr/>
     <div class="sorter">
         <div class="images">
-            <?php foreach ($this->gallery->galleryPhotos as $photo): ?>
-            <div id="<?php echo $this->id . '-' . $photo->id ?>" class="photo">
-                <div class="image-preview">
-                    <?php echo CHtml::image($photo->getPreview()); ?>
-                </div>
-                <div class="caption">
-                    <?php if ($this->gallery->name): ?>
-                    <h5><?php echo $photo->name ?></h5>
-                    <?php endif;?>
-                    <?php if ($this->gallery->description): ?>
-                    <p><?php echo $photo->description ?></p>
-                    <?php endif;?>
-                </div>
-                <div class="actions">
-                    <?php
-                    echo CHtml::hiddenField('order[' . $photo->id . ']', $photo->rank);
-                    if ($this->gallery->name || $this->gallery->description)
-                        echo '<span data-photo-id="' . $photo->id . '" class="editPhoto btn btn-primary"><i class="icon-edit icon-white"></i></span>';
-                    echo ' <span data-photo-id="' . $photo->id . '" class="deletePhoto btn btn-danger"><i class="icon-remove icon-white"></i></span>';
-                    ?>
-                </div>
-                <label>
-                    <input type="checkbox" class="photo-select"/>
-                </label>
-            </div>
-            <?php endforeach;?>
+
         </div>
         <br style="clear: both;"/>
     </div>
             <h3><?php echo Yii::t('galleryManager.main', 'Edit information')?></h3>
         </div>
         <div class="modal-body">
-            <div class="form"></div>
+            <?php
+            /** @var $tabs BTabs */
+            $tabs = $this->beginWidget('BTabs');
+            ?>
+            <?php
+            foreach (Yii::app()->params['languages'] as $lang) {
+                $tabs->beginTab(Yii::app()->params['languageNames'][$lang],$this->id.'_editor_tab_'.$lang);
+                ?>
+                <div class="form"></div>
+                <?php
+                $tabs->endTab();
+            }
+            $this->endWidget();
+            ?>
         </div>
         <div class="modal-footer">
             <a href="#" class="btn btn-primary save-changes">