The work on localization has been started for Orchard Core by implementing content item cloning. The Beta 3 release is almost ready too!
On Orchard Core
Orchard Core Beta 3
Only one issue left for the Beta 3 release of Orchard Core. Now the community can look into testing and shipping the new release of the CMS.
Working on shell scopes
When we do something in a request (if we use a database), at the end of request we want to commit the transaction that attached to the request. We are highly modular and event based, what can happen is that we do something with the content items, then all the modules can be called to do also something with a content item. But modules don't know what's happening on the content item between different components. One component might say that the content item is invalid, so don't save anything. But maybe a previous module tries to save something. That's why we have an ambient transaction for the full request and at the end of the request if everything went well (if no module said to cancel the transaction), the transaction will be committed.
So, what we do is, at the end of the request we commit the transaction if nothing went bad. And we can do (because we need to commit the transaction at the end of the request) actions after all the things have been done. Maybe in a module we say do something after an action and then (after everything is stored in a database) do another thing. Or maybe we will have twenty events on the same thing, but we know we need to do a single thing at the end. So, we have the notion of DeferredTaskEngine.
The IDeferredTaskEngine is a service, that you can resolve and add a task and it will do a deferred task. You pass an async lambda and what will happen is once the current unit of work or work context is done (in this case a request), once the scope DI has been disposed, then we create a new scope that will execute the deferred tasks. It's like enlisting another unit of work after the current one has been done. You can just enlist from any module.
The issue we faced is that we would like to be able to do something before the unit of work is done, but just once. So, the same thing as the DeferredTaskEngine, but in the same scope and just once. For instance, let's have a look at the CreateTaskAsync method of the IndexingTaskManager.
This method is called by modules when they want to create an indexing task. It's just: "OK, store a task for later!", that says we need to index this content item. So, during a request, you might want to import 10 content items and each of them will call CreateTaskAsync. But what we don't want to do is do a database call for each of them. What we want to do is: log the task into a local queue (_tasksQueue), and once the current unit of work is done (meaning the request), flush or save all the tasks to the database with one SQL command or with one communication to the database.
To do that, we do a FlushAsync which is called with a DeferredTaskEngine. So, we load the DeferredTaskEngine, and we say add the task which is used to flush all the current queue. With that we can batch many things into one command. But this thing will be done in a different scope, so different transaction for the database. It would be great to do it in the same transaction as the request. The goal is that to be able to queue a task/job in the current request or in the current scope, or at the end of the scope and also in a separate scope. So, you could have the choice.
Create OrchardCore.Profile Module for FE Users
The goal is being to define profile pages for viewing and editing a profile of a user on the front end reusing the same composability of a UI for this specific entity, which is a user. So, we can edit the users in the admin for their security thing, but the profile could contain private info for the user perspective and that can be accessed from the front end. So, introduce custom editors for the front end, not using fields or parts or anything, but extensibility of the UI for the profiles. So, you can create modules that will add profile information to be displayed or to be edited on the frontend, like you address, your payment information, your orders and everything will be extensions to the profile module.
Extends ServiceCollection to update env.WebRootFileProvider
The asp-append-version Tag Helper is using the WebRootFileProvider file provider which is not correctly set, and doesn't find the files in modules, so it would not append the correct hash to the URL for the ASP.NET Tag Helper. The issue about it is here.
Clone content items
The goal is to implement localization and the first step is to support cloning. Thanks to JP Tissot, this feature is now ready.
You will see a Clone option in the Actions drop-down list near every content item. If you hit Clone, a new version of the content item will be created and saved as a draft. The permalink of the cloned item will be auto-generated: if the content item has a permalink: blog/post-1, the clone will have blog/post-2 or about-1, if the original one has a permalink: about.
Localize content items
Here is a plan about how to the content item localization should be implemented in Orchard Core: a LocalizationPart is added to content types that can be translated. It will provide a custom UI element in the editor to navigate between translations of the same content item using a common identifier called Localization Set which is represented by a unique identifier share across all content items of the same set. Each LocalizationPart has a unique (Culture, LocalizationSet) tuple as a single translation of the same culture can exist in a localization set. The localization set identifier can generated the first time a content item is translated.
A setting is added to all fields so they can be marked as "Culture Neutral", meaning their value cannot vary by culture. In practice when a content item is changed, the values of these fields are cloned on other versions (draft/published) of the same localization set. We synchronize them.
Content handlers should have a Localizing event such that each part could define how its content should be localized. Some parts might need their content to be made unique (autoroute part) or copied (title) or translated automatically, or picked from other translated content (content picker).
All these services, part and events should be in a Localization module, a Core and an Abstractions project. The ContentManagement module should not have to reference it. Any module which needs to handle localization in a specific way will reference the Abstractions project and implement custom handlers.
New pics of our team members
Some new pics of our team members are up! Head to lombiq.com/about-us for the photos and here you can see lot of Orchard and Orchard Core developers on one picture!