diff --git a/Changelog.md b/Changelog.md
index ea5b08276f3db5be012e8baf438846ce67635260..deb8081e8c900ab5049cd80ad381841a46095ef4 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -9,10 +9,14 @@
 # 0.6.2.0
 
 ## Refactor
+* Use string-direction gem for rtl detection [#7181](https://github.com/diaspora/diaspora/pull/7181)
+* Reduce i18n.load side effects [#7184](https://github.com/diaspora/diaspora/pull/7184)
+* Force jasmine fails on syntax errors [#7185](https://github.com/diaspora/diaspora/pull/7185)
 
 ## Bug fixes
 * Fix fetching comments after fetching likes [#7167](https://github.com/diaspora/diaspora/pull/7167)
 * Hide 'reshare' button on already reshared posts [#7169](https://github.com/diaspora/diaspora/pull/7169)
+* Only reload profile header when changing aspect memberships [#7183](https://github.com/diaspora/diaspora/pull/7183)
 
 ## Features
 * Show spinner when loading comments in the stream [#7170](https://github.com/diaspora/diaspora/pull/7170)
diff --git a/Gemfile b/Gemfile
index e4614cfd7db769d707666cf9129b0f8ead261ebf..9bf0486c694a757faa5c529daf9ae37d10361d40 100644
--- a/Gemfile
+++ b/Gemfile
@@ -135,6 +135,10 @@ gem "twitter-text",      "1.14.0"
 gem "ruby-oembed",       "0.10.1"
 gem "open_graph_reader", "0.6.1"
 
+# RTL support
+
+gem "string-direction", "1.2.0"
+
 # Security Headers
 
 gem "secure_headers", "3.5.0"
diff --git a/Gemfile.lock b/Gemfile.lock
index 73f20f63eeb5df72a748a9091659e09dd872591f..73559ce038019860f42fce5b50c49711904ccb8c 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -819,6 +819,8 @@ GEM
       activesupport (>= 3.0)
       sprockets (>= 2.8, < 4.0)
     state_machine (1.2.0)
+    string-direction (1.2.0)
+      yard (~> 0.8)
     swd (1.0.1)
       activesupport (>= 3)
       attr_required (>= 0.0.5)
@@ -1031,6 +1033,7 @@ DEPENDENCIES
   spring (= 2.0.0)
   spring-commands-cucumber (= 1.0.1)
   spring-commands-rspec (= 1.0.4)
+  string-direction (= 1.2.0)
   test_after_commit (= 1.1.0)
   timecop (= 0.8.1)
   turbo_dev_assets (= 0.0.2)
@@ -1046,4 +1049,4 @@ DEPENDENCIES
   will_paginate (= 3.1.5)
 
 BUNDLED WITH
-   1.13.5
+   1.13.6
diff --git a/app/assets/javascripts/app/pages/profile.js b/app/assets/javascripts/app/pages/profile.js
index b987ea3876e6fb2bf8d4a5a6d24f3d7efa272073..a2b8143993001a37a995e935d4410e42eab59e63 100644
--- a/app/assets/javascripts/app/pages/profile.js
+++ b/app/assets/javascripts/app/pages/profile.js
@@ -31,7 +31,6 @@ app.pages.Profile = app.views.Base.extend({
     this.streamCollection = _.has(opts, "streamCollection") ? opts.streamCollection : null;
     this.streamViewClass = _.has(opts, "streamView") ? opts.streamView : null;
 
-    this.model.on("change", this.render, this);
     this.model.on("sync", this._done, this);
 
     // bind to global events
diff --git a/app/assets/javascripts/app/views/profile_header_view.js b/app/assets/javascripts/app/views/profile_header_view.js
index 0fc671b8eecec2c6bf5213999c39c43086066b4c..1522b71e4b2cda6f250fc34e262d1981cf50bf12 100644
--- a/app/assets/javascripts/app/views/profile_header_view.js
+++ b/app/assets/javascripts/app/views/profile_header_view.js
@@ -15,6 +15,7 @@ app.views.ProfileHeader = app.views.Base.extend({
   initialize: function(opts) {
     this.photos = _.has(opts, 'photos') ? opts.photos : null;
     this.contacts = _.has(opts, 'contacts') ? opts.contacts : null;
+    this.model.on("change", this.render, this);
     $("#mentionModal").on("modal:loaded", this.mentionModalLoaded.bind(this));
     $("#mentionModal").on("hidden.bs.modal", this.mentionModalHidden);
   },
diff --git a/app/assets/javascripts/helpers/i18n.js b/app/assets/javascripts/helpers/i18n.js
index 42bd7a8864a747700a1409da679786f0b815b746..e6db0f2e1826e53629bf29248fd68107b79b8c39 100644
--- a/app/assets/javascripts/helpers/i18n.js
+++ b/app/assets/javascripts/helpers/i18n.js
@@ -18,7 +18,7 @@ Diaspora.I18n = {
   },
 
   updateLocale: function(locale, data) {
-    locale.data = $.extend(locale.data, data);
+    locale.data = $.extend({}, locale.data, data);
 
     var rule = locale.data.pluralization_rule;
     if (typeof rule !== "undefined") {
diff --git a/lib/direction_detector.rb b/lib/direction_detector.rb
index 9724c80c8da927e473f747d505d02f23dd8f2cb3..c7ffd055adc5b9510cf69349bdf76cb5177a6416 100644
--- a/lib/direction_detector.rb
+++ b/lib/direction_detector.rb
@@ -2,36 +2,16 @@
 #   Copyright (c) 2010-2011, Diaspora Inc.  This file is
 #   licensed under the Affero General Public License version 3 or later.  See
 #   the COPYRIGHT file.
-# Deeply inspired by https://gitorious.org/statusnet/mainline/blobs/master/plugins/DirectionDetector/DirectionDetectorPlugin.php
 
 class String
-  RTL_RANGES = [
-      [1536, 1791], # arabic, persian, urdu, kurdish, ...
-      [65136, 65279], # arabic peresent 2
-      [64336, 65023], # arabic peresent 1
-      [1424, 1535], # hebrew
-      [64256, 64335], # hebrew peresent
-      [1792, 1871], # syriac
-      [1920, 1983], # thaana
-      [1984, 2047], # nko
-      [11568, 11647] # tifinagh
-    ]
   RTL_CLEANER_REGEXES = [ /@[^ ]+|#[^ ]+/u, # mention, tag
       /^RT[: ]{1}| RT | RT: |[♺♻:]/u # retweet
     ]
 
   def is_rtl?
     return false if self.strip.empty?
-    count = 0
-    self.split(" ").each do |word|
-      if starts_with_rtl_char?(word)
-        count += 1
-      else
-        count -= 1
-      end
-    end
-    return true if count > 0 # more than half of the words are rtl words
-    return starts_with_rtl_char?(self) # otherwise let the first word decide
+    detector = StringDirection::Detector.new(:dominant)
+    detector.rtl? self
   end
 
   # Diaspora specific
@@ -42,14 +22,4 @@ class String
     end
     string.is_rtl?
   end
-
-  def starts_with_rtl_char?(string = self)
-    stripped = string.strip
-    return false if stripped.empty?
-    char = stripped.unpack('U*').first
-    RTL_RANGES.each do |limit|
-      return true if char >= limit[0] && char <= limit[1]
-    end
-    return false
-  end
 end
diff --git a/spec/javascripts/app/views/help_view_spec.js b/spec/javascripts/app/views/help_view_spec.js
index 098e7dfcc8ac8f2d9bb35e55dc909728ee59275c..38717a62035130bdb60834f774b09a739f099366 100644
--- a/spec/javascripts/app/views/help_view_spec.js
+++ b/spec/javascripts/app/views/help_view_spec.js
@@ -2,11 +2,17 @@ describe("app.views.Help", function(){
   beforeEach(function(){
     gon.appConfig = {chat: {enabled: false}};
     this.locale = JSON.parse(spec.readFixture("locale_en_help_json"));
+    Diaspora.I18n.reset();
     Diaspora.I18n.load(this.locale, "en");
     this.view = new app.views.Help();
     Diaspora.Page = "HelpFaq";
   });
 
+  afterEach(function() {
+    Diaspora.I18n.reset();
+    Diaspora.I18n.load(spec.defaultLocale);
+  });
+
   describe("render", function(){
     beforeEach(function(){
       this.view.render();
diff --git a/spec/javascripts/app/views/profile_header_view_spec.js b/spec/javascripts/app/views/profile_header_view_spec.js
index 17bfecc81546374168662ac6d0517f9b7806d953..ebd4ecde11f2fc89ba3fc17e058f0bb371fa3223 100644
--- a/spec/javascripts/app/views/profile_header_view_spec.js
+++ b/spec/javascripts/app/views/profile_header_view_spec.js
@@ -11,6 +11,34 @@ describe("app.views.ProfileHeader", function() {
     loginAs(factory.userAttrs());
   });
 
+  describe("initialize", function() {
+    it("calls #render when the model changes", function() {
+      spyOn(app.views.ProfileHeader.prototype, "render");
+      this.view.initialize();
+      expect(app.views.ProfileHeader.prototype.render).not.toHaveBeenCalled();
+      this.view.model.trigger("change");
+      expect(app.views.ProfileHeader.prototype.render).toHaveBeenCalled();
+    });
+
+    it("calls #mentionModalLoaded on modal:loaded", function() {
+      spec.content().append("<div id='mentionModal'></div>");
+      spyOn(app.views.ProfileHeader.prototype, "mentionModalLoaded");
+      this.view.initialize();
+      expect(app.views.ProfileHeader.prototype.mentionModalLoaded).not.toHaveBeenCalled();
+      $("#mentionModal").trigger("modal:loaded");
+      expect(app.views.ProfileHeader.prototype.mentionModalLoaded).toHaveBeenCalled();
+    });
+
+    it("calls #mentionModalHidden on hidden.bs.modal", function() {
+      spec.content().append("<div id='mentionModal'></div>");
+      spyOn(app.views.ProfileHeader.prototype, "mentionModalHidden");
+      this.view.initialize();
+      expect(app.views.ProfileHeader.prototype.mentionModalHidden).not.toHaveBeenCalled();
+      $("#mentionModal").trigger("hidden.bs.modal");
+      expect(app.views.ProfileHeader.prototype.mentionModalHidden).toHaveBeenCalled();
+    });
+  });
+
   context("#presenter", function() {
     it("contains necessary elements", function() {
       expect(this.view.presenter()).toEqual(jasmine.objectContaining({
diff --git a/spec/javascripts/helpers/i18n_spec.js b/spec/javascripts/helpers/i18n_spec.js
index 79dfabe27f1546b6c537fbc494403cb71ed52ab6..0190a454f3ac5cf720dbc0941540f3bc1f6062e6 100644
--- a/spec/javascripts/helpers/i18n_spec.js
+++ b/spec/javascripts/helpers/i18n_spec.js
@@ -45,6 +45,28 @@ describe("Diaspora.I18n", function() {
 
       expect(Diaspora.I18n.locale.data).toEqual(extended);
     });
+
+    it("overrides existing translations", function() {
+      var oldLocale = {name: "Bob"};
+      var newLocale = {name: "Alice"};
+      Diaspora.I18n.load(oldLocale, "en");
+      expect(Diaspora.I18n.locale.data.name).toBe("Bob");
+      Diaspora.I18n.load(newLocale, "en");
+      expect(Diaspora.I18n.locale.data.name).toBe("Alice");
+
+      Diaspora.I18n.reset(oldLocale);
+      expect(Diaspora.I18n.locale.data.name).toBe("Bob");
+      Diaspora.I18n.load(newLocale, "en");
+      expect(Diaspora.I18n.locale.data.name).toBe("Alice");
+    });
+
+    it("doesn't change locale objects given in ealier calls", function() {
+      var oldLocale = {name: "Bob"};
+      var newLocale = {name: "Alice"};
+      Diaspora.I18n.reset(oldLocale);
+      Diaspora.I18n.load(newLocale, "en");
+      expect(oldLocale.name).toBe("Bob");
+    });
   });
 
   describe("::t", function() {
diff --git a/spec/javascripts/onerror-fail.js b/spec/javascripts/onerror-fail.js
new file mode 100644
index 0000000000000000000000000000000000000000..21f69b745e325c6e8ab8486b187924e827f7c62e
--- /dev/null
+++ b/spec/javascripts/onerror-fail.js
@@ -0,0 +1,7 @@
+window.onerror = function(errorMsg, url, lineNumber) {
+  describe("Test suite", function() {
+    it("shouldn't skip tests because of syntax errors", function() {
+      fail(errorMsg + " in file " + url + " in line " + lineNumber);
+    });
+  });
+};
diff --git a/spec/javascripts/support/jasmine.yml b/spec/javascripts/support/jasmine.yml
index 87e516cca0572c94b3247c8c69281d9ee5353421..cec4333070de79c6d45abf8bdbf2f9bff7564768 100644
--- a/spec/javascripts/support/jasmine.yml
+++ b/spec/javascripts/support/jasmine.yml
@@ -53,6 +53,7 @@ helpers:
 #   - **/*[sS]pec.js
 #
 spec_files:
+  - onerror-fail.js
   - "**/**/*[sS]pec.js"
 
 # src_dir
diff --git a/spec/lib/direction_detector_spec.rb b/spec/lib/direction_detector_spec.rb
index b19e854db128fff1ffb3b5d79f8ba3ef3a6962ff..9b28700e3387083b82b5110e8032755a224fba6f 100644
--- a/spec/lib/direction_detector_spec.rb
+++ b/spec/lib/direction_detector_spec.rb
@@ -24,29 +24,6 @@ describe String do
   let(:hebrew_arabic) { "#{hebrew} #{arabic}" }
 
 
-  describe "#stats_with_rtl_char?" do
-    it 'returns true or false correctly' do
-      expect(english.starts_with_rtl_char?).to be false
-      expect(chinese.starts_with_rtl_char?).to be false
-      expect(arabic.starts_with_rtl_char?).to be true
-      expect(hebrew.starts_with_rtl_char?).to be true
-      expect(hebrew_arabic.starts_with_rtl_char?).to be true
-    end
-
-    it 'only looks at the first char' do
-      expect(english_chinese.starts_with_rtl_char?).to be false
-      expect(chinese_english.starts_with_rtl_char?).to be false
-      expect(english_arabic.starts_with_rtl_char?).to be false
-      expect(hebrew_english.starts_with_rtl_char?).to be true
-      expect(arabic_chinese.starts_with_rtl_char?).to be true
-    end
-    
-    it 'ignores whitespaces' do
-      expect(" \n \r \t".starts_with_rtl_char?).to be false
-      expect(" #{arabic} ".starts_with_rtl_char?).to be true
-    end
-  end
-
   describe "#is_rtl?" do
     it 'returns true or false correctly' do
       expect(english.is_rtl?).to be false
@@ -65,17 +42,16 @@ describe String do
       expect("#{english} #{arabic} #{arabic}".is_rtl?).to be true
     end
 
-    it "fallbacks to the first word if there's no majority" do
-      expect(hebrew_english.is_rtl?).to be true
-      expect(english_hebrew.is_rtl?).to be false
-      expect(arabic_english.is_rtl?).to be true
-      expect(english_arabic.is_rtl?).to be false
-    end
-
     it 'ignores whitespaces' do
       expect(" \n \r \t".is_rtl?).to be false
       expect(" #{arabic} ".is_rtl?).to be true
     end
+
+    it "ignores byte order marks" do
+      expect("\u{feff}".is_rtl?).to be false
+      expect("\u{feff}#{arabic}".is_rtl?).to be true
+      expect("\u{feff}#{english}".is_rtl?).to be false
+    end
   end
 
   describe '#cleaned_is_rtl?' do