How can I define a business specific spoken language with i18n? - internationalization

I develop a Ruby on Rails 5 application in which I use I18n gem for views translations. Terms used in views are quite generic, and are supposed to meet all businesses.
But for one specific customer, the business requires very specific terms. I can't make these terms the general translation. I can imagine 2 ways to support this:
create a specific en-customer language to use with i18n
create a specific .yml as a subsection of en.yml and switch to it by
configuration
How can I do any of these?
Thanks for your help!

This should be quite doable with your first example of creating a customer-specific locale code.
If you configure I18n to register this custom locale, and configure a fallback for that locale to the base language:
Rails.application.configure do
config.i18n.available_locales = %i[en es fr en-customer]
config.i18n.default_locale = :en
config.i18n.backend.class.send(:include, I18n::Backend::Fallbacks)
config.i18n.fallbacks.map('en-customer' => 'en')
end
This should let you set a preference for users of that company to use the custom locale, and you can create a yaml file to override just the keys you need to.
You could do this without a fallback, too, but you'd have to copy an entire language, then apply your changes, which can be harder to manage as text in your app changes, and more prone to breakage if you miss a key.

Related

Changing template inheritance order in Silverstripe

As per the documentation of Silverstripe, template inheritance is defined as follows:
mysite (or other name given to site folder)
module-specific themes (e.g. themes/simple_blog)
themes (e.g. themes/simple)
modules (e.g. blog)
framework
Now I've got a site that has quite a few different themes. Well, "different" in that they have different names, but they still got an awful lot in common. Now I am putting all the common files in the /mysite/templates folder, but that means that if one of my themes needs a change in one of the templates, I need to remove that file from the common folder, and move it to ALL the different theme folders. In this way I end up with a lot of duplicate templates.
In my case it would be beneficial to change the inheritance order, causing the specific theme folder to take precedence over the /mysite folder. In such a way I could just copy the template that has to be changed to the theme folder and that theme one will use the changed one, while the rest keeps using the generic one in the /mysite folder:
themes (e.g. themes/simple)
module-specific themes (e.g. themes/simple_blog)
mysite (or other name given to site folder)
modules (e.g. blog)
framework
It also seems to me to be the more obviuous way to do it, but I probably am missing some important point here. Nonetheless, would doing this be possible without hacking the core?
Template inheritance rendering seems to be managed by two classes predominantly, SSViewer (a core class for handling view rendering) and Controller (which all other controllers inherit from).
For view rendering, a SSViewer object can take an array in its constructor for template inheritance. This is important because the Controller class actually instantiates the SSViewer in a function called getViewer.
It is important to mention at this stage, a normal SilverStripe site you are normally inheriting from ContentController instead which overrides the getViewer of Controller. It won't really change too much of what you need to write but it is important depending how low-level you want this to apply.
For what you want to apply to pages in general, you would be looking at overriding getViewer in your Page_Controller. As for what specifically you would need to write, that is somewhat dependent on your entire site structure. I would imagine it would need to start off a little like this though:
public function getViewer($action) {
$viewer = Parent::getViewer($action);
$templates = $viewer->templates();
//Do your processing that you need to here
//Set it back via:
//$viewer->setTemplateFile($type, $file);
//Alternatively, you can create a new SSViewer object
return $viewer;
}
It will be a bit of experimentation to work out what exactly you need to do for shuffling around the data though this was never going to be easy. Once you start heading down this path, you likely will find a number of edge cases where this may not work properly (eg. template includes).
The question and the accepted answer are for SilverStripe 3, and should be referred to for queries relating to SilverStripe 3.x. The following refers to SilverStripe 4.
As SilverStripe 4 is currently the latest stable version and now sets theme inheritance instead of template inheritance, the question and answer may not be suitable for newer installations or up to date installations.
The best way to control inheritance in SilverStripe 4 is to ensure that the themes are configured in the correct order, and that the module inheritance is configured appropriately.
Your mysite/_config/theme.yml file typically declares the theme inheritance, and you should adjust this file to control theme inheritance appropriately. For modules, you need to specify the appropriate Before and After in your mycustommodule/_config/modules.yml file.
The following example is for a sile with both the mytheme and simple themes, a custom module without an _config/modules.yml file, and a vendor module without an _config/modules.yml file.
SilverStripe\View\SSViewer:
themes:
- 'mytheme'
- 'simple'
- '$default'
In this example, SSViewer::get_themes() will return those three items as an array in the same order: ['mytheme', 'simple', '$default]. When checking to see if a template exists, $default will then be replaced by the paths of all modules which define templates, in the same order as they appear in the manifest.
<?php
use SilverStripe\View\ThemeResourceLoader;
$templatePaths = ThemeResourceLoader::inst()->getThemePaths(SSViewer::get_themes());
$templatePaths === [
'themes/mytheme',
'themes/simple',
'mycustommodule',
'vendor/silverstripe/asset-admin',
'vendor/silverstripe/campaign-admin',
'vendor/silverstripe/reports',
'vendor/silverstripe/siteconfig',
// Some vendor modules may appear here...
'vendor/othervendor/custommodule',
'vendor/silverstripe/cms',
'vendor/silverstripe/admin',
'vendor/silverstripe/assets',
'vendor/silverstripe/framework'
];

Velocity IncludeEventHandler dilemma

Here is an interesting problem regarding the IncludeEventHandler.
I am developing a Spring-Based application which uses velocity which has different VENDORS having a separate portfolio site. I am letting vendors customize the pages by providing them the Velocity templates which are being stored the database and are picked up by the velocity engine using a DataSourceResourceLoader.
My table is organized like this.
The vendors may parse other templates by calling the macro #parse and passing their vendorid/template-name so that it looks like this.
#parse("20160109144/common-css.vm")
Now the actual problem is picking up the template according to vendorid.
I have a class (extending IncludeEventHandler) which overrides the includeEvent method. Now what can I do to return the desired template? I dont want to change the names and make them look like 20160109144/home.vm
With OP's question, the intent was to provide an alternate behavior to the DataSourceResourceLoader.
Unfortunately, the Velocity Engine version 1.7 doesn't have ability to change the SQL statement that is used to retrieve the template.
The DataSourceResourceLoader extends the ResourceLoader abstract class. That said, if you reference the source, you should be able to implement a custom ResourceLoader that behaves the way you want it to.
One option, glom most of the code from DataSourceResourceLoader and change the way it determines the template content to load from the database.
I would dump all of the query related material as you will be determining the specific columns you want to load for content. The DataSourceResourceLoader essentially maps the name of a template to a database entry and your implementation essentially revolves around the rules you've defined above.
Hopefully that can provide enough assistance to move forward. I would recommend pulling this in a debugger as well and determine what is and is-not passed in to the related load methods.

How to store/serve user-customizable HTML templates?

I want to offer a tumblr-like functionality where you can select a template, and optionally customize the HTML of your template in the browser and save it.
Current stack: Mongo, Sinatra (for REST API) for prototype. Will likely be moving to a compiled, statically-typed language later.
Wondering how best to accomplish this. Options I've considered:
Store the HTML in mongo, and duplicate it for all user accounts. So the HTML for the template you choose gets written into your account. Obvious cons of space inefficiency and need to update all users that use that template (if un-customized - you customize it it becomes your own and I won't ever touch it) if the template changes.
Store the templates in templates collection, and put custom templates either into this same collection or into the user collection with the owner of the template. User references a template id. This is quite clearly better than 1 I believe. Especially because I won't need to pull the template every time the user object is pulled.
Some third party library? Open to suggestions here.
File system.
I will need to package up these templates (insert js and stuff the user shouldn't be exposed to) and then serve them. Any advice on how best to approach this is greatly appreciated.
Your approach will depend on how often you foresee people customizing the template versus just going with a standard. How about a hybrid approach?
That is, have a field in the user document that is created lazily (on use) that either stores the custom template, or maybe a diff from one of the standards (not sure about the level of customization you are planning to allow).
Then you can have the template field you describe in 2 above, with a "special" setting for custom templates. While you still have the concern about pulling a template each time, you do have the advantage of knowing that these are some of your more dedicated users - saving a trip to the DB might be advantageous, or you might not care.
If you don't care about 2 trips to the DB for every user, then you take approach 2, add the custom templates to the templates collection and simply reference the new ID for each user that customizes.
It's a balancing act - is the extra data overhead in terms of pulling the template each time worth saving a round trip to the DB or do you want efficiency in terms of the data you get each time at the cost of multiple queries to the DB - only you can answer that one based on how you design your app and how people use it.
For the linked approach you might want to take a look at Database References and Schema Design in the MongoDB docs.

Example of branch by abstraction for an ASP.NET MVC 3 application where the abstraction is different countries

I work on an ASP.NET MVC 3 application where branches in version control have been created for each country. This makes it difficult sync changes between the branches and the problem will increase as the number of countries increases. I have read about branching by abstraction but have never actually used it so I'm struggling to comprehend how this would be actually be implemented in a reasonably complex project.
Can anyone explain how branching by abstraction can be done for an ASP.NET MVC application where the abstraction is a country, and the application is a n-tier type affair with service and data layers?
A few of the things I can't get my head round are:
Abstracting view stuff. E.g. The aspx/cshtml files, javascript, css
etc.
Abstracting controllers
How do you manage the extra complexity of having all your for code
every country in one branch? (Note, by one branch I mean the default branch, mainline, whatever you want to call it)
How you you manage feature toggles for each country? E.g. Potentially you could have many many features for each country that were not ready for release and controller by a toggle)
How do you select the appropriate country when deploying?
How do you run unit tests for country specific functionality?
UPDATE
I'm not just talking about language changes. Much of the service layer logic for each country is the same, but in certain key areas it's different, likewise for the data layer. Also view content and layout can vary, javascript can vary and controller logic can vary.
Why do you use branches to handle localization? imho it's madness.
There are several ways to do localizations without having one code base per language.
For instance. By making your own view engine on top of an existing one you can load views from external assemblies. Or you can create one view per language.
You can also use string tables in your views to make them localized.
Abstracting view stuff. E.g. The aspx/cshtml files, javascript, css etc.
Javascripts: I have one javascript with all my logic which uses variables instead of strings to show messages. In this way I can load another javascript before the logic script (for instance myplugin.sv.js before myplugin.js to get the swedish language.
The most common way for aspx/cshtml files is to use string tables in them.
Localize css? Why?
Abstracting controllers
huh? Why? Use string tables here too.
How do you manage the extra complexity of having all your for code every country in one branch?
Don't use branches.
How you you manage feature toggles for each abstraction?
huh?
How do you select the appropriate country when deploying?
Use the same site for all languages. Either use the IP address, Accept-Language http header or the user's language preference to select language. Language is specified by setting Thread.CurrentThread.CurrentCulture and Thread.CurrentThread.CurrentUICulture
How do you run unit tests for country specific functionality?
Don't. The logic should be the same.
Update in response to your comment
I've written my fair amount of systems where content is dynamically loaded into the application/service. I've never used different code bases even if different customers use different features in multi-tenant systems (SaaS).
First of all, you need to accept two things:
a) All content is loaded at all time (not talking about translations, but features).
b) Use proper/standard localization methods, but no need to translate all features to all languages.
What you do is simply control which content to load and use standard techniques to get translated features. The control is made by either checking the current language (Thread.CurrentThread.CurrentCulture.Name) or any homebrewn control.
Ask new questions to get more details.
Update 2
I wouldn't rely on the IoC to provide language specific features, but use reflection and a FeatureProvider class to do that for me.
For instance, let's say that you have a feature called ISalaryCalculator which takes into account all local tax rules etc. then I would create something like this:
// 1053 is the LCID (read about LCID / LocaleId in MSDN)
[ForLanguage(1053)]
public SwedishSalaryCalculator : ISalaryCalculator
{
}
And have a FeatureProvider to load it:
public class FeatureProvider
{
List<Assembly> _featureAssemblies;
public T GetLocalFeature<T>()
{
var featureType = typeof(T);
foreach (var assembly in _featureAssemblies)
{
foreach (var type in assembly.GetTypes().Where(t => featureType.IsAssignableFrom(t))
{
var attribute = type.GetCustomAttributes(typeof(ForLanguageAttribute)).First();
if (attribute.LocaleId == Thread.CurrentThread.CurrentCulture.LCID)
return (T)Activator.CreateInstance(type);
}
}
return null;
}
}
And to get the feature (for the current language):
var calculator = _featureManager.GetLocalFeature<ISalaryCalculator>();
var salaryAfterTaxes = calculator.GetSalaryAfterTax(400000);
In a simple scenario (CMS/not much or shared business logic) you are dealing with just content in different languages and:
Layout (left to right vs right to left) - you will have two CSS files
Localized (translated) content
Layout CSS can be in separate files and be pulled in depending on the User Account's culture or site region setting.
Localization can reside in different assemblies - one for each culture or in separate string table files (as already mentioned).
In a more complex scenario, where you are developing an application for sensitive cultures and regions where:
You have to be careful about the content you display - for example an image can be very offensive in one country but perfectly fine in another country.
Different business logic (e.g. think financial application, etc)
Most of the content issues you can solve with CSS, but even if you need heavily customized views as has already been mentioned you can create a view engine that pulls a view file based on the current culture (e.g. Index.en-Us.cshtml, Index.ru.cshtml, etc)
For the different business rules you must have a good design and architecture utilizing Inversion of control (e.g. dependency injection) and patterns such as the Strategy, state, Template method, etc. Given that you have that in place you will be able to create an IoC container project/assembly per culture at run time and your UI will be unaware of the difference since it will be simply consuming some services defined by those interfaces.
Slightly more tricky if your ViewModels are significantly different between cultures, but if they are you probably want to maybe split View + Controller per culture in different assemblies and again use IoC to configure the routing at run time initialization.
Clearly deploying multi-cultural sites is more complex than non-such and no matter which way you go (site instance per culture or one site instance for all cultures) you will probably want to invest in some tooling to be able to script multi-culture deployment.
And last - regarding the unit tests - if you already have a good IoC based design testing per culture will be a matter of using and configuring the right dependency container prior to running a set of unit tests.
With such a setup the teams of developers should be working in relatively self-contained environment, since your project will be logically and physically be partitioned to support multiple teams working on separate parts of it at the same time. It also shouldn't matter whether on branches or trunk/head. Of course if everything is a big mess in just a few projects and people have to constantly merge in - you are in a very bad state and your future isn't bright.
Final words: It's not about branching - it's about design, architecture, partitioning (solutions, projects and assemblies) and last but not least about automation (build and test).
This is a little off topic, but I believe you're heading in a more difficult direction than you need to. Localization is built into ASP.NET and the MVC framework can take advantage of it also.
The short story is that you basically make a resx for each language you'd like to support. You then use Resources.Global.YourLocalizedStringPropertyName in your views.
This blog post is the long story. Scott Hanselman goes over javascript localization and other finer points. It's definitely worth a read and will probably save you incredible amounts of work to use the built in functionality instead of building it yourself:
http://www.hanselman.com/blog/GlobalizationInternationalizationAndLocalizationInASPNETMVC3JavaScriptAndJQueryPart1.aspx

What separates a Ruby DSL from an ordinary API

What are some defining characteristics of a Ruby DSL that separate it from just a regular API?
When you use an API you instantiate objects and call methods in an imperative manner. On the other hand a good DSL should be declarative, representing rules and relationships in your problem domain, not instructions to be executed. Moreover ideally DSL should be readable and modifiable by somebody who is not a programmer (which is not the case with APIs).
Also please keep in mind the distinction between internal and external DSLs.
Internal domain specific language is embedded in a programming language (eg. Ruby). It's easy to implement, but the structure of the DSL is dependent on the parent language it is embedded in.
External domain specific language is a separate language designed with the particular domain in mind. It gives you a greater flexibility when it comes to syntax, but you have to implement the code to interpret it. It's also more secure, as the person editing domain rules doesn't have access to all the power of the parent language.
DSL (domain specific language) is an over-hyped term. If you are simply using a sub-set of a language (say Ruby), how is it a different language than the original? The answer is, it isn't.
However, if you do some preprocessing of the source text to introduce new syntax or new semantics not found in the core language then you indeed have a new language, which may be domain-specific.
The combination of Ruby's poetry mode and operator overloading does present the possibility of having something that is at the same time legal Ruby syntax and a reasonable DSL.
And the continued aggravation that is XML does show that perhaps the simple DSL built into all those config files wasn't completely misguided..
Creating a DSL:
Adding new methods to the Object class so that you can just call them as if they were built-in language constructs. (see rake)
Creating methods on a custom object or set of objects, and then having script files run the statements in the context of a top-level object. (see capistrano)
API design:
Creating methods on a custom object or set of objects, so the user creates an object to use the methods.
Creating methods as class methods, so that the user prefixes the classname in front of all the methods.
Creating methods as a mixin that users include or extend to use the methods in their custom objects.
So yes, the line is thin between them. It's trivial to turn a custom set of objects into a DSL by adding one method that runs a script file in the right context.
The difference between a DSL and an API to me is that a DSL could be at least understood (and verified) if not written as a sub-language of Ruby by someone in that domain.
For example, you could have financial analysts writing rules for a stock trading application in a Ruby DSL and they would never have to know they were using Ruby.
They are, in fact, the same thing. DSLs are generally implemented via the normal language mechanisms in Ruby, so technically they're all APIs.
However, for people to recognize something as a DSL, it usually ends up adding what look like declarative statements to existing classes. Something like the validators and relationship declarations in ActiveRecord.
class Foo << ActiveRecord::Base
validates_uniqueness_of :name
validates_numericality_of :number, :integer_only => true
end
looks like a DSL, while the following doesn't:
class Foo <<ActiveRecord::BAse
def validate
unless unique? name
errors.add(:name, "must be unique")
end
unless number.to_s.match?(/^[-]?\d$/)
errors.add(:number, "must be an integer")
end
end
end
They're both going to be implemented by normal Ruby code. It's just that one looks like you've got cool new language constructs, while the other seems rather pedestrian (and overly verbose, etc. etc.)

Resources