implement i18n and add french translation
authorLunar <lunar@anargeek.net>
Tue, 3 Aug 2010 17:15:04 +0000 (19:15 +0200)
committerLunar <lunar@anargeek.net>
Sat, 7 Aug 2010 08:40:17 +0000 (10:40 +0200)
.gitignore
README
Rakefile [new file with mode: 0644]
coquelicot.rb
lib/haml_gettext.rb [new file with mode: 0644]
lib/haml_parser.rb [new file with mode: 0644]
po/coquelicot.pot [new file with mode: 0644]
po/fr/coquelicot.po [new file with mode: 0644]
views/layout.haml
views/ready.haml

index 5119549..63b0272 100644 (file)
@@ -1,3 +1,4 @@
 files/
+locale/
 tmp/restart.txt
 tmp/always_restart.txt
diff --git a/README b/README
index d654e77..4b95763 100644 (file)
--- a/README
+++ b/README
@@ -82,9 +82,14 @@ Coquelicot is written in Ruby using the Sinatra web framework.
 On Debian, one can fulfill its dependencies by issueing:
 
     apt-get install libsinatra-ruby1.8 libopenssl-ruby1.8 \
-                    libhaml-ruby1.8 liblockfile-ruby
+                    libhaml-ruby1.8 liblockfile-ruby libgettext-ruby1.8 \
+                    rake
 
-Then you need to figure out the best way to host a Rack application
+Then create the translation catalog through:
+
+    rake makemo
+
+Finally you need to figure out the best way to host a Rack application
 depending on your setup. *evil grin*
 
 Test suite
@@ -105,10 +110,6 @@ Then, running the test suite is just a matter of typing:
 Future
 ------
 
- * Internationalization
-
-   Coquelicot should be i18n'ed to display users' preferred language.
-
  * Integrate other authentication systems for uploads
 
    A common password is a pretty limited authentication scheme.
diff --git a/Rakefile b/Rakefile
new file mode 100644 (file)
index 0000000..397b721
--- /dev/null
+++ b/Rakefile
@@ -0,0 +1,17 @@
+$:.unshift File.join(File.dirname(__FILE__), 'lib')
+
+desc "Update pot/po files."
+task :updatepo do
+  require 'gettext/tools'
+  require 'haml_parser'
+  GetText.update_pofiles(
+    "coquelicot",
+    Dir.glob("views/**/*.{rb,haml}") << "coquelicot.rb",
+    "coquelicot 1.0.0")
+end
+
+desc "Create mo-files"
+task :makemo do
+  require 'gettext/tools'
+  GetText.create_mofiles(:mo_root => './locale')
+end
index 9ea0714..8009c5f 100644 (file)
@@ -1,3 +1,5 @@
+$:.unshift File.join(File.dirname(__FILE__), 'lib')
+
 require 'sinatra'
 require 'haml'
 require 'digest/sha1'
@@ -6,6 +8,8 @@ require 'openssl'
 require 'yaml'
 require 'lockfile'
 require 'singleton'
+require 'gettext'
+require 'haml_gettext'
 
 set :upload_password, '0e5f7d398e6f9cd1f6bac5cc823e363aec636495'
 set :default_expire, 60 # 1 hour
@@ -300,6 +304,11 @@ def password_match?(password)
   (not password.nil?) && Digest::SHA1.hexdigest(password) == settings.upload_password
 end
 
+GetText::bindtextdomain('coquelicot')
+before do
+  GetText::set_current_locale(params[:lang] || request.env['HTTP_ACCEPT_LANGUAGE'] || 'en')
+end
+
 get '/style.css' do
   content_type 'text/css', :charset => 'utf-8'
   sass :style
diff --git a/lib/haml_gettext.rb b/lib/haml_gettext.rb
new file mode 100644 (file)
index 0000000..a96d85d
--- /dev/null
@@ -0,0 +1,65 @@
+# Adapted from sinitra-hat: http://github.com/nanoant/sinatra-hat/
+#
+# Copyright (c) 2009 Adam Strzelecki
+# 
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+# 
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+class Haml::Engine
+  include GetText
+
+  # Inject _ gettext into plain text and tag plain text calls
+  def push_plain(text)
+    super(_(text))
+  end
+  def parse_tag(line)
+    tag_name, attributes, attributes_hashes, object_ref, nuke_outer_whitespace,
+      nuke_inner_whitespace, action, value, last_line = super(line)
+    value = _(value) unless action && action != '!' || action == '!' && value[0..0] == '=' || value.empty?
+    # translate inline ruby code too
+    value.gsub!(/_\('([^']+)'\)/) {|m| '\''+_($1)+'\''} unless action != '=' || value.empty?
+    attributes_hashes.each{|h| h.each{|v| v.gsub!(/_\('([^']+)'\)/){|m| '\''+_($1)+'\''} if v.is_a? String} unless h.nil? || h.empty?} unless attributes_hashes.nil? || attributes_hashes.empty?
+    [tag_name, attributes, attributes_hashes, object_ref, nuke_outer_whitespace,
+        nuke_inner_whitespace, action, value, last_line]
+  end
+  def push_flat_markdown(line)
+    text = line.full.dup
+    text = "" unless text.gsub!(/^#{@flat_spaces}/, '')
+    text = _(text) if text != ''
+    @filter_buffer << "#{text}\n"
+  end
+  def push_flat_javascript(line)
+    text = line.full.dup
+    text.gsub!(/_\('(([^']|\\')+)'\)/) {|m| '\''+_($1)+'\''}
+    @filter_buffer << "#{text}\n"
+  end
+  def push_flat(line)
+    return super(line) if @gettext_filters.nil? || !@gettext_filters.last
+    return send("push_flat_#{@gettext_filters.last}".to_sym, line)
+  end
+  def start_filtered(name)
+    @gettext_filters ||= []
+    @gettext_filters.push(name) if ['markdown', 'javascript'].include? name
+    super
+  end
+  def close_filtered(filter)
+    @gettext_filters.pop
+    super
+  end
+end
diff --git a/lib/haml_parser.rb b/lib/haml_parser.rb
new file mode 100644 (file)
index 0000000..2dfdd4d
--- /dev/null
@@ -0,0 +1,90 @@
+# Adapted from sinitra-hat: http://github.com/nanoant/sinatra-hat/
+#
+# Copyright (c) 2009 Adam Strzelecki
+# 
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+# 
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+require 'gettext/tools/parser/ruby'
+require 'haml'
+
+class String
+  def escape_single_quotes
+    self.gsub(/'/, "\\\\'")
+  end
+end
+
+class Haml::Engine
+  # Overriden function that parses Haml tags
+  # Injects gettext call for plain text action.
+  def parse_tag(line)
+    tag_name, attributes, attributes_hashes, object_ref, nuke_outer_whitespace,
+      nuke_inner_whitespace, action, value, last_line = super(line)
+    @precompiled << "_('#{value.escape_single_quotes}')\n" unless action && action != '!' || action == '!' && value[0..0] == '=' || value.empty?
+    [tag_name, attributes, attributes_hashes, object_ref, nuke_outer_whitespace,
+        nuke_inner_whitespace, action, value, last_line]
+  end
+  # Overriden function that producted Haml plain text
+  # Injects gettext call for plain text action.
+  def push_plain(text)
+    @precompiled << "_('#{text.escape_single_quotes}')\n"
+  end
+  def push_flat_markdown(line)
+    text = line.unstripped
+    return if text == ''
+    @precompiled << "_('#{text.escape_single_quotes}')\n"
+  end
+  def push_flat_javascript(line)
+    text = line.unstripped
+    return if text == ''
+    text.gsub(/_\('(([^']|\\')+)'\)/) do |m|
+      @precompiled << "_('#{$1}')"
+    end
+  end
+  def push_flat(line)
+    return super(line) if @gettext_filters.nil? || !@gettext_filters.last
+    return send("push_flat_#{@gettext_filters.last}".to_sym, line)
+  end
+  def start_filtered(name)
+    @gettext_filters ||= []
+    @gettext_filters.push(name) if ['markdown', 'javascript'].include? name
+    super
+  end
+  def close_filtered(filter)
+    @gettext_filters.pop
+    super
+  end
+end
+
+# Haml gettext parser
+module HamlParser
+  module_function
+  def target?(file)
+    File.extname(file) == ".haml"
+  end
+  def parse(file, ary = [])
+    haml = Haml::Engine.new(IO.readlines(file).join)
+    code = haml.precompiled.split(/$/)
+    GetText::RubyParser.parse_lines(file, code, ary)
+  end
+end
+GetText::RGetText.add_parser(HamlParser)
diff --git a/po/coquelicot.pot b/po/coquelicot.pot
new file mode 100644 (file)
index 0000000..23fa97c
--- /dev/null
@@ -0,0 +1,105 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: coquelicot 1.0.0\n"
+"POT-Creation-Date: 2010-08-03 19:12+0200\n"
+"PO-Revision-Date: 2010-08-03 17:15+0200\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
+
+#: views/index.haml:1
+msgid "Share a file!"
+msgstr ""
+
+#: views/index.haml:5
+msgid "Upload password:"
+msgstr ""
+
+#: views/index.haml:6
+msgid "File:"
+msgstr ""
+
+#: views/index.haml:7
+msgid "Available for:"
+msgstr ""
+
+#: views/index.haml:8
+msgid "5 minutes"
+msgstr ""
+
+#: views/index.haml:20
+msgid "1 hour"
+msgstr ""
+
+#: views/index.haml:22
+msgid "1 day"
+msgstr ""
+
+#: views/index.haml:24
+msgid "1 week"
+msgstr ""
+
+#: views/index.haml:26
+msgid "1 month"
+msgstr ""
+
+#: views/index.haml:28
+msgid "Download password:"
+msgstr ""
+
+#: views/expired.haml:1
+msgid "Too late…"
+msgstr ""
+
+#: views/expired.haml:2
+msgid "Sorry, file has expired."
+msgstr ""
+
+#: views/layout.haml:1
+msgid "coquelicot"
+msgstr ""
+
+#: views/layout.haml:7
+msgid "Generate random"
+msgstr ""
+
+#: views/layout.haml:7
+msgid "Generating…"
+msgstr ""
+
+#: views/layout.haml:7
+msgid "Coquelicot © 2010 potager.org"
+msgstr ""
+
+#: views/layout.haml:8 views/layout.haml:10
+msgid "—"
+msgstr ""
+
+#: views/layout.haml:9
+msgid "AGPLv3"
+msgstr ""
+
+#: views/layout.haml:11
+msgid "git clone #{base_href}coquelicot.git"
+msgstr ""
+
+#: views/ready.haml:1
+msgid "Pass this on!"
+msgstr ""
+
+#: views/enter_file_key.haml:1
+msgid "Enter download password…"
+msgstr ""
+
+#: views/enter_file_key.haml:4
+msgid "Password:"
+msgstr ""
diff --git a/po/fr/coquelicot.po b/po/fr/coquelicot.po
new file mode 100644 (file)
index 0000000..4e7dc45
--- /dev/null
@@ -0,0 +1,104 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: coquelicot 1.0.0\n"
+"POT-Creation-Date: 2010-08-03 19:12+0200\n"
+"PO-Revision-Date: 2010-08-03 17:15+0200\n"
+"Last-Translator: potager.org <jardiniers@potager.org>\n"
+"Language-Team: potager.org <jardiniers@potager.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+
+#: views/index.haml:1
+msgid "Share a file!"
+msgstr "Partager un fichier !"
+
+#: views/index.haml:5
+msgid "Upload password:"
+msgstr "Passe pour envoyer :"
+
+#: views/index.haml:6
+msgid "File:"
+msgstr "Fichier :"
+
+#: views/index.haml:7
+msgid "Available for:"
+msgstr "Disponible pendant :"
+
+#: views/index.haml:8
+msgid "5 minutes"
+msgstr "5 minutes"
+
+#: views/index.haml:20
+msgid "1 hour"
+msgstr "1 heure"
+
+#: views/index.haml:22
+msgid "1 day"
+msgstr "1 jour"
+
+#: views/index.haml:24
+msgid "1 week"
+msgstr "1 semaine"
+
+#: views/index.haml:26
+msgid "1 month"
+msgstr "1 mois"
+
+#: views/index.haml:28
+msgid "Download password:"
+msgstr "Passe pour le téléchargement :"
+
+#: views/expired.haml:1
+msgid "Too late…"
+msgstr "Trop tard…"
+
+#: views/expired.haml:2
+msgid "Sorry, file has expired."
+msgstr "Désolé, le fichier a expiré."
+
+#: views/layout.haml:1
+msgid "coquelicot"
+msgstr "coquelicot"
+
+#: views/layout.haml:7
+msgid "Generate random"
+msgstr "Générer aléatoirement"
+
+#: views/layout.haml:7
+msgid "Generating…"
+msgstr "Génération…"
+
+#: views/layout.haml:7
+msgid "Coquelicot © 2010 potager.org"
+msgstr "Coquelicot © 2010 potager.org"
+
+#: views/layout.haml:8 views/layout.haml:10
+msgid "—"
+msgstr "—"
+
+#: views/layout.haml:9
+msgid "AGPLv3"
+msgstr "AGPLv3"
+
+#: views/layout.haml:11
+msgid "git clone #{base_href}coquelicot.git"
+msgstr "git clone #{base_href}coquelicot.git"
+
+#: views/ready.haml:1
+msgid "Pass this on!"
+msgstr "À transmettre !"
+
+#: views/enter_file_key.haml:1
+msgid "Enter download password…"
+msgstr "Entrer le passe de téléchargement…"
+
+#: views/enter_file_key.haml:4
+msgid "Password:"
+msgstr "Passe :"
index 62848e0..c924d1b 100644 (file)
@@ -10,8 +10,8 @@
     %script{ :type => 'text/javascript', :src => 'javascripts/jquery.lightBoxFu.js' }
     %script{ :type => 'text/javascript', :src => 'javascripts/jquery.uploadProgress.js' }
     :javascript
-      var generateRandomPassword = 'Generate random';
-      var generatingRandomPassword = 'Generating…';
+      var generateRandomPassword = _('Generate random');
+      var generatingRandomPassword = _('Generating…');
     %script{ :type => 'text/javascript', :src => 'javascripts/coquelicot.js' }
   %body
     #container
index 312c6b8..1305c36 100644 (file)
@@ -2,7 +2,7 @@
 #content
   .url
     %a{ :href => @url }
-      %span.base> #{@base}/
+      %span.base>= "#{@base}/"
       %span.name= @name
   - unless @unprotected
     %p A password is required to download this file.