How We Created a Multi-site Setup for a Vehicle Marketplace: Part 1
Tags: webdev, ruby, challenge, showdev
Published: 2019-05-02 08:17:10

---
title: How We Created a Multi-site Setup for a Vehicle Marketplace: Part 1
published: true
description: Codica team created a marketplace platform including 89 independent websites. How do we easily localize, configure, test, deploy, monitor them all? Check our article.
tags: webdev, ruby, challenge, showdev
cover_image: https://thepracticaldev.s3.amazonaws.com/i/fxt0tlf9hbed7881vjiv.jpg
canonical_url: https://www.codica.com/blog/multisite-vehicle-marketplace/
---
*This article was originally published on [Codica Blog](https://www.codica.com/blog/multisite-vehicle-marketplace/).*

Today, we want to present you the first part of the article, where we describe the way we developed the first [multi-vendor vehicle marketplace](https://www.codica.com/case-studies/multi-vendor-vehicle-marketplace/) in Africa. The platform provides its users with an opportunity to trade new and second-hand cars, trucks, and motorbikes. 

The client had a primary goal to develop a global marketplace solution which would serve many geographic areas of Africa and South America. The mission is to connect customers with private owners and dealers.

At the beginning of the development, there were certain platform websites and our goal was to develop more and extend the marketplace size. Taking into account all the benefits and drawbacks, our development team concluded to develop a multi-site setup. 

Up to date, there are **89 marketplace websites** functioning separately, and the number continues to grow. Let's get this show on the road and see how we achieved this result.
## What is a multi-site setup?
To be precise, a multi-site setup is a composite of independent websites. Here you can see a comparison table of multi-domain and multi-site solutions to understand why we have chosen this option.

Aspect | Multi-domain | Multi-site
| --- | --- | --- |
Domain | Subdomains | Different domains
Database | One | Separate database for each site/domain
Content management | Via one panel | Via multiple panels for every site/domain
Monitoring | Easy | Medium
Maintenance | Hard | Medium
Stability | Low | High 
Loading speed | Low | High

As we can see, one of the main benefits of multi-domain option is easy content management, but our development team chose a multi-site solution. The main reason for this is that it provides undeniable benefits stability and maintenance.

## Challenges

### Configuration
**Goal: Proper organization and maintenance of the setup**

Shortly, our development team has adopted a composite configuration system that gives an opportunity to edit the settings of each site and its logic. It is stashed on a single Git repository. Thus, it stores all the websites’ files with the templates and CSS files of the colour scheme.

Initially, each site has its own file with the setting of supported currencies and other information.

```python
site_name: NewSite
site_url: Newsite.com
continent: Continent
country_code: AO
currencies: { 'AOA': 'AOA', 'USD': 'USD' }
emails:
  contact_email: info@newsite.com
  from_email: info@newsite.com
socials:
  facebook: https://www.facebook.com/newsite/
  google_plus: https://plus.google.com/u/0/+Newsite
  instagram: https://www.instagram.com/newsite/
  linkedin: http://www.linkedin.com/company/new-site
  twitter: https://www.twitter.com/newsite
  youtube: https://www.youtube.com/channel/newsite
```
Moreover, we have an opportunity to edit and manage all the sites at a time. We have such a possibility because there exists a main HTML/CSS template which is common for all the sites. 

Here you can see an example of a common template CSS file. In case we edit the variables in this file, then all the websites’ colour scheme will be changed accordingly. This solution simplifies the configuration adjustment pretty much.

```javascript
$color-1: #0052b4;
$color-1-hover: darken($color-1, 20%);
$color-2: #cb032b;
```
### Localization
#### Languages

If you run the marketplace in many countries, then the language issue becomes challenging. It includes creating translations, as well as updates and control. 

Our primary goal was to take into account all the probable languages for a particular geographic area and integrate them for the best user experience. In order to achieve this, we have created a fully-fledged localization system that supports **any country** and **multiple languages** in these countries.

**Goal 1. Provide fast and easy localization**

Each site provides several languages, including English and local ones. To modify the languages, we have adopted [Ruby gems](https://www.codica.com/blog/top-ruby-gems-we-cant-live-without/) that allow solving many problems in a short period of time as they simplify the development process.

Our development team has used [i18n gem](https://github.com/ruby-i18n/i18n) (set by default), to provide internationalization solution for the marketplace.

Furthermore, with the help of this gem, we have an opportunity to localize such information as date and time, validation notification, etc.

**Goal 2. Monitor and manage multiple translation files**

At this stage of development, we perfectly realized that it is of fundamental importance for the customer to easily manage the translations.

We have developed our tailored solution: we have created an import and export function, with the help of mentioned gems (i18n, i18n- tasks) and [Custom CSV import and export tasks](https://github.com/glebm/i18n-tasks/wiki/Custom-CSV-import-and-export-tasks).

Let’s define the way it works:  
* The client adds translations to a Google Document.
* This document syncs with our Git repository <code>.yml</code> files and therefore updates in our locale files.

Moreover, we are able to update Google Doc by changing the data in the code base.

Below you can see a Google Doc screen with the ‘key’ value from the code base and its consequent values in different languages.

![](https://thepracticaldev.s3.amazonaws.com/i/lf66na5ljlsedmr12v28.png)

Moving right along, we have faced a new issue: in order to adjust the whole platform localization, we need to modify the locale files quite often. 

To solve this problem, our development team has modified and advanced the above mentioned **Custom CSV import and export tasks** function. Now it gives us a possibility of writing data to separate files at one time that provides a boost in localization administration. 

Let’s see how this mechanism works:
```javascript
def csv_export(_opts = {})
    translations_by_path = {}
    router = I18n::Tasks::Data::Router::PatternRouter.new(nil, write: i18n.config['csv']['export'])

    create_locales_file(i18n.locales)

    all_locales = i18n.locales << i18n.base_locale

    all_locales.each do |locale|
      router.route(locale, i18n.data_forest) do |path, nodes|
        translations_by_path[path] ||= {}
        translations_by_path[path][locale] ||= {}
        nodes.leaves do |node|
          translations_by_path[path][locale][node.full_key(root: false)] = node.value
        end
      end
      import_locale_in_csv(locale, translations_by_path)
    end
  End
```
Now, we have an opportunity to track and verify all the files within Google Drive API and export locales to Google Drive. There we can edit and manage the translations flatly and handily.

When we receive an email notification, we run an import function to get the data from Google Drive using this method:
```javascript
  def csv_import_from_google
    translations = []
    file_links.each do |_local, token|
      csv = File.read(URI.open("https://docs.google.com/spreadsheets/d/#{token}/export?format=csv"))
      import_locale(csv, translations)
    end
    i18n.data.merge! I18n::Tasks::Data::Tree::Siblings.from_flat_pairs(translations)
  end

 def import_locale(csv, translations)
    CSV.parse(csv, headers: true) do |row|
      key = row['key']
      next unless key

      get_locale_from_files(row).each do |locale|
        raise "Locale missing for key #{key}! (locales in app: #{locales} / locales in file: #{row.headers.inspect})" unless row.key?(locale)
        translations << [[locale, key].join('.'), row[locale]] if row[locale].present? && skip_locale?(key)
      end
    end
  End
```
#### Currencies
**Goal: Create conversion system for multiple currencies**

Considering a lot of locations and currencies, it was of utmost importance to support the platform users with a convenient currency conversion system. 

Marketplace customers needed the opportunity to determine prices in their local currency or USD, and quickly convert them. This task was a bit complicated because the number of sites and currencies is pretty large. 

For each website, we set several currencies, including USD and the local ones. A user is able to set the price in any listed currency, as well as convert it into any other available one. 

To implement efficient currency conversion at the platform, we have adopted [Money Gem](https://github.com/RubyMoney/money). 

Then, we have faced another problem: **currency rates change quickly**.

To cope with this issue, we have developed a special automated service that updates currency rates **every hour**. We have adopted [Cron Jobs](https://cron-job.org/en/) on our server - it is a tool for scheduling certain recurring actions in the Rails application. 

Also, our development team has configured the tool to send a request to Money Gem API hourly, receive the data on current currency conversion rates, parse the data and then complete it on the platform.

Let’s see how this service works:

```javascript
class UpdateCurrenciesRate < BaseService

  def call
    base_rate = ExchangeRate.base_currency
    Settings.currencies.to_h.values.each do |currency|
      next if currency == base_rate
      update_rate(base_rate, currency)
    end
  end

  private

  def update_rate(base, target)
    currency_rate = JSON.parse(open(generate_link(base, target)).read)
    save_rate(base, target, currency_rate.values.first)
  rescue StandardError => error
    puts error
  end

  def generate_link(base, target)
    "http://free.currencyconverterapi.com/api/v3/convert?q=#{base}_#{target}&compact=ultra"
  end

  def save_rate(base, target, rate)
    Money.default_bank.add_rate(base, target, rate)
  end

End
```
## Conclusion

Here we presented you the first part of the article covering such challenges of [marketplace development](https://www.codica.com/services/online-marketplace-development/) like **configuration** and **localization (languages, currencies)**. The second part is coming soon and there we will tell you about **testing** and **deployment** challenges.

This solution is time- and cost-effective because you have a possibility to administrate the platform websites easily and efficiently. The configuration system supports you with enhancing a single website and obtaining the results over the whole platform.

Stay tuned and read the full article version here: [How We Created a Multi-site Setup for a Vehicle Marketplace](https://www.codica.com/blog/multisite-vehicle-marketplace/).