Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Added russian language.

  • Loading branch information...
commit 8749f19ed469fe5485781cd233455afae849091c 1 parent bff2038
@uu authored
Showing with 2,378 additions and 2 deletions.
  1. +1 −1  config/locales/en.yml
  2. +186 −0 config/locales/from_en_to_ru.yml
  3. +568 −0 config/locales/ru.yml
  4. +1 −1  config/routes.rb
  5. +20 −0 vendor/plugins/translate/MIT-LICENSE
  6. +61 −0 vendor/plugins/translate/README
  7. +11 −0 vendor/plugins/translate/Rakefile
  8. +8 −0 vendor/plugins/translate/init.rb
  9. +17 −0 vendor/plugins/translate/lib/translate.rb
  10. +35 −0 vendor/plugins/translate/lib/translate/file.rb
  11. +123 −0 vendor/plugins/translate/lib/translate/keys.rb
  12. +35 −0 vendor/plugins/translate/lib/translate/log.rb
  13. +11 −0 vendor/plugins/translate/lib/translate/routes.rb
  14. +20 −0 vendor/plugins/translate/lib/translate/storage.rb
  15. +165 −0 vendor/plugins/translate/lib/translate_controller.rb
  16. +32 −0 vendor/plugins/translate/lib/translate_helper.rb
  17. +130 −0 vendor/plugins/translate/spec/controllers/translate_controller_spec.rb
  18. +54 −0 vendor/plugins/translate/spec/file_spec.rb
  19. +12 −0 vendor/plugins/translate/spec/files/translate/app/models/article.rb
  20. +1 −0  vendor/plugins/translate/spec/files/translate/app/views/category.erb
  21. +1 −0  vendor/plugins/translate/spec/files/translate/app/views/category.html
  22. +1 −0  vendor/plugins/translate/spec/files/translate/app/views/category.html.erb
  23. +5 −0 vendor/plugins/translate/spec/files/translate/app/views/category.rhtml
  24. +1 −0  vendor/plugins/translate/spec/files/translate/public/javascripts/application.js
  25. +97 −0 vendor/plugins/translate/spec/keys_spec.rb
  26. +47 −0 vendor/plugins/translate/spec/log_spec.rb
  27. +9 −0 vendor/plugins/translate/spec/spec_helper.rb
  28. +33 −0 vendor/plugins/translate/spec/storage_spec.rb
  29. +23 −0 vendor/plugins/translate/spec/translate_spec.rb
  30. +162 −0 vendor/plugins/translate/tasks/translate.rake
  31. +360 −0 vendor/plugins/translate/views/layouts/translate.rhtml
  32. +24 −0 vendor/plugins/translate/views/translate/_pagination.rhtml
  33. +124 −0 vendor/plugins/translate/views/translate/index.rhtml
View
2  config/locales/en.yml
@@ -321,7 +321,7 @@ en:
title: "Title"
description: "Description"
rsvp: "RSVP"
- activate: "Activare"
+ activate: "Activate"
deactivate: "Deactivate"
my:
home: "Home"
View
186 config/locales/from_en_to_ru.yml
@@ -0,0 +1,186 @@
+---
+account:
+ about_me: "About Me"
+ anniversary: Anniversary
+ default_permission: "Default Permission"
+ error:
+ email_not_found: "Could not find that email address. Try again."
+ login_not_valid: "Uh-oh, login didn't work. Do you have caps locks on? Try it again."
+ facebook_connect: "Facebook Connect"
+ house_name: "House Name"
+ notice:
+ confirmation_invalid: "We're sorry but it seems that the confirmation did not go thru. You may have provided an expired key."
+ email_already_confirmed: "Your email is already confirmed by us. Please login to continue."
+ email_confirmed: "Thanks your email is confirmed by us. Please login to continue"
+ profile_activation_problem: "We're sorry but it seems that there was a problem activating your profile. Please contact the administrators to resolve the issue."
+ please_login: "Please {{login}} or {{signup}} to participate!"
+ referral:
+ message: "Additional Message"
+ name: "Referral Person Name"
+ year: "Referral Person Year"
+ requested_email: "Requested New Email"
+ sign_up: "Sign-up Now"
+ sign_up_confirmation_message: "Please click on the confirmation link sent to <strong>{{email}}</strong>to verify and continue. Do keep in mind that sometimes email might end up in your spam/junk folder, in that case, please configure your email client to receive all email from <strong>{{site}}</strong> as not junk."
+ spouse_name: "Spouse Name"
+ validation:
+ accept_tos: "Please Accept The Terms of Service"
+ email_already_taken: "This email has already been taken"
+ email_available: "This email is available"
+ login_already_taken: "This login has already been taken"
+ login_available: "This login is available"
+activate: Activate
+activerecord:
+ errors:
+ full_messages:
+ format: "{{attribute}} {{message}}"
+ messages:
+ accepted: "must be accepted"
+admin:
+ welcome: "Welcome to {{site}}"
+ago: ago
+announcement:
+ notice:
+ not_successfully_updated: "Announcement was not successfully updated."
+ successfully_deleted: "Announcement was successfully deleted."
+ successfully_updated: "Announcement was successfully updated."
+ update_create: "Announcement Update/Create"
+application:
+ error:
+ no_permission_to_view: "It looks like you don't have permission to view that page."
+blog:
+ posted_at: "posted at {{postdate}}"
+blogged: blogged
+comma_separated: "Comma Seperated"
+comment:
+ wall_to_wall_with: "Wall-to-Wall with {{user}}"
+confirm: "Are you sure?"
+deactivate: Deactivate
+delete: Delete
+description: Description
+edit: Edit
+email: Email
+end: End
+ends_at: "Ends at"
+event:
+ anniversaries: Anniversaries
+ anniversary: Anniversary
+ birthday: Birthday
+ birthdays: Birthdays
+ delete: "Delete Event"
+ edit: "Edit Event"
+ send: "Send Event"
+facebook:
+ error:
+ session_expired: "Your facebook session has been expired."
+feed_item:
+ user_comment_icon: "User Comment Icon"
+ user_invited: "{{inviter}} is now a {{description}} of {{invited}}"
+ user_photo_icon: "User Photo Icon"
+ user_updated_profile: "{{user}} has updated their profile"
+feedback:
+ delete: Destroy
+ feedback: Feedback
+ give_us_feedback: "Give us Feedback"
+follow_on_twitter: "Follow on Twitter"
+follow_on_twitter_link: "Follow {{site}} on "
+forum:
+ delete: "Delete Forum"
+ edit: "Edit Forum"
+ forum: Forum
+ forums: Forums
+ last_post: "Last Post"
+ new: "Create New Forum"
+ no_forums: "{{site}} has no forums."
+ posts: Posts
+ topic_starter: "Topic Starter"
+ topics: Topics
+forum_posts:
+ delete: "Delete Post"
+ most_recent: "Most Recent Forums Posts<span> (latest {{count}} of {{total}})</span>"
+ post_by_followup: "Post By Followup"
+forum_topic:
+ back_to_forum: "Back to Forum"
+ back_to_forums: "Back to Forums"
+ delete: "Delete Topic"
+ edit: "Edit Forum Topic"
+ new: "New Forum Topic"
+friend:
+ following: Following
+ see_all: "(See All)"
+group: Group
+groups: Groups
+help: Help
+hi: "Hi {{user}}"
+home:
+ credits: Credits
+hosted_by: "Hosted by"
+invite:
+ email_list: "Enter a list of email addresses separated by commas (,)"
+ email_list_title: "Comma Separated List of Emails"
+ invite_batchmates: "Invite Batchmates"
+location: Location
+login: Login
+login_entrance: Login
+logout: Logout
+message: Message
+my:
+ admin: Admin
+ blog: "My Blog"
+ direct_message: "Direct Message"
+ friends: "My Friends"
+ home: Home
+ messages: "My Messages"
+ polls: "My Polls"
+ profile: "My Profile"
+ read_blogs: "Read My Blogs"
+ see_polls: "See My Polls"
+name: Name
+new: New
+notifier:
+ user_is_following_you: "{{user}} is now following you on {{sitename}} (http://{{site}})."
+ user_is_following_you_message: "You can go view their profile here:"
+ user_is_now_in_your_list: "{{user}} is now in your {{list}} list"
+ user_send_news: "Blog post titled {{title}} by {{user}}"
+ user_send_news_message: "Follow the URL to comment on the blog."
+ user_sends_message: "{{user}} sent the following message :"
+ user_sends_message_link: "To read this message, follow the link below:"
+ user_status: "Your profile has now been {{status}}"
+ user_unfriends_you: "{{user}} has deleted you as friend on {{site}}"
+ user_wrote_on_other_blog: "{{user}} wrote on {{other}} Blog"
+ user_wrote_on_your_blog: "{{user}} wrote on your Blog"
+ user_wrote_on_your_wall: "{{user}} wrote on your Wall"
+number:
+ currency:
+ format:
+ delimiter: ","
+ format:
+ delimiter: ","
+password: Password
+password_confirm: "Confirm Password"
+photo:
+ caption: Caption
+ crop: "Crop the image"
+ delete: "Delete Image"
+ new: "Create new {{image}}"
+ no_images: "{{site}} has no {{image}}"
+poll:
+ close: "Close Poll"
+ delete: "Delete Poll"
+ edit: "Edit Poll"
+ most_recent: "Most Recent Polls<span> (latest {{count}} of {{total}})</span>"
+ user_has_no_polls: "{{user}} has no polls"
+posted: Posted
+profile:
+ twitter_username: "Twitter Login"
+remember_me: "Remember me"
+rsvp: RSVP
+save: Save
+search:
+ friends: "Search for Friends"
+ search: Search
+start: Start
+starts_at: "Starts at"
+subject: Subject
+tags: Tags
+title: Title
+username: Username
View
568 config/locales/ru.yml
@@ -0,0 +1,568 @@
+---
+ru:
+ account:
+ about_me: ""
+ activities: Мероприятия
+ anniversary: Юбилей
+ blood_group: "Группа крови"
+ current_email: "Текущий адрес электронной почты"
+ current_password: "Текущий пароль"
+ date_of_birth: "Дата рождения"
+ default_permission: ""
+ edit: "Изменить учетную запись"
+ error:
+ email_not_confirmed: "Ваш адрес электронной почты еще не был подтвержден."
+ email_not_found: ""
+ login_not_valid: ""
+ facebook_connect: ""
+ first_name: Имя
+ forgot_password: "Напомнить пароль"
+ gender: Пол
+ house_name: ""
+ last_name: Фамилия
+ maiden_last_name: "Девичья фамилия"
+ middle_name: Отчество
+ new_password_was_generated: "Новый пароль будет сгенерирован и отправлен на этот адрес электронной почты."
+ new_pasword: "Новый пароль"
+ notice:
+ confirmation_invalid: ""
+ email_already_confirmed: ""
+ email_confirmed: ""
+ greeting: Привет
+ logged_out: "Вы вышли из системы."
+ new_password_emailed: "Новый пароль был выслан Вам."
+ profile_activation_problem: ""
+ please_login: ""
+ professioanl_qualification: "Профессиональная квалификация"
+ referral:
+ message: "Дополнительное сообщение"
+ name: ""
+ year: ""
+ relationship_status: "Семейное положение"
+ requested_email: ""
+ sign_up: ""
+ sign_up_confirmation_message: ""
+ spouse_name: ""
+ thanks_for_sign_up: "Благодарим за регистрацию!"
+ tos: "Условия предоставления услуг"
+ validation:
+ accept_tos: ""
+ cant_be_blank: "не может быть пустым"
+ email_already_taken: ""
+ email_available: ""
+ login_already_taken: ""
+ login_available: ""
+ activate: Активировать
+ activerecord:
+ errors:
+ full_messages:
+ format: "{{attribute}} {{message}}"
+ messages:
+ accepted: "нужно подтвердить"
+ blank: "не может быть пустым"
+ confirmation: "не совпадает с подтверждением"
+ empty: "не может быть пустым"
+ equal_to: "может иметь лишь значение, равное {{count}}"
+ even: "может иметь лишь нечетное значение"
+ exclusion: "имеет зарезервированное значение"
+ greater_than: "может иметь лишь значение большее {{count}}"
+ greater_than_or_equal_to: "может иметь лишь значение большее или равное {{count}}"
+ inclusion: "имеет непредусмотренное значение"
+ invalid: "имеет неверное значение"
+ less_than: "может иметь лишь значение меньшее чем {{count}}"
+ less_than_or_equal_to: "может иметь значение меньшее или равное {{count}}"
+ not_a_number: "не является числом"
+ odd: "может иметь лишь четное значение"
+ record_invalid: "Возникли ошибки: {{errors}}"
+ taken: "уже существует"
+ too_long:
+ few: "слишком большой длины (не может быть больше чем {{count}} символа)"
+ many: "слишком большой длины (не может быть больше чем {{count}} символов)"
+ one: "слишком большой длины (не может быть больше чем {{count}} символ)"
+ other: "слишком большой длины (не может быть больше чем {{count}} символа)"
+ too_short:
+ few: "недостаточной длины (не может быть меньше {{count}} символов)"
+ many: "недостаточной длины (не может быть меньше {{count}} символов)"
+ one: "недостаточной длины (не может быть меньше {{count}} символа)"
+ other: "недостаточной длины (не может быть меньше {{count}} символа)"
+ wrong_length:
+ few: "неверной длины (может быть длиной ровно {{count}} символа)"
+ many: "неверной длины (может быть длиной ровно {{count}} символов)"
+ one: "неверной длины (может быть длиной ровно {{count}} символ)"
+ other: "неверной длины (может быть длиной ровно {{count}} символа)"
+ models: ~
+ template:
+ body: "Проблемы возникли со следующими полями:"
+ header:
+ few: "{{model}}: сохранение не удалось из-за {{count}} ошибок"
+ many: "{{model}}: сохранение не удалось из-за {{count}} ошибок"
+ one: "{{model}}: сохранение не удалось из-за {{count}} ошибки"
+ other: "{{model}}: сохранение не удалось из-за {{count}} ошибки"
+ admin:
+ welcome: "Добро пожаловать на {{site}}"
+ ago: назад
+ announcement:
+ announcements: Объявления
+ delete: "Удалить объявление"
+ edit: "Изменить объявление"
+ notice:
+ not_successfully_created: "Объявление не было успешно создано."
+ not_successfully_updated: ""
+ successfully_created: "Объявление было успешно создано."
+ successfully_deleted: ""
+ successfully_updated: ""
+ update_create: ""
+ application:
+ error:
+ no_permission_to_view: "Похоже, у вас нет разрешения на просмотр этой страницы."
+ blog:
+ archive: "Архив блога"
+ blogs_tag: "Блоги о"
+ body: Содержание
+ create: "Создание нового блога"
+ delete: "Удалить блог"
+ edit: ""
+ error:
+ post_create_failed: "Не удалось создать новый блог."
+ post_update_failed: "Ошибка при обновлении блога."
+ heading: Заголовок
+ include_youtube: ""
+ most_recent: ""
+ notice:
+ empty_blog: ""
+ post_created: "Новый блог создан."
+ post_deleted: ""
+ post_updated: ""
+ posted_at: "опубликовал в {{postdate}}"
+ posted_by_at: ""
+ public: ""
+ rss: ""
+ search: "Поиск блога"
+ send: "Отправить блог"
+ send_confirmation: "Вы уверены? Хотите отправить сообщение всем пользователям?"
+ tags: "Блог тэги"
+ title: Название
+ user_has_no_blogs: ""
+ blogged: ""
+ comma_separated: ""
+ comment:
+ add: "Добавить комментарий"
+ comment: комментарий
+ commented: комментирует
+ comments: Комментарии
+ latest_comments: "Последние комментарии "
+ wall_to_wall: "Стенка на стенку"
+ wall_to_wall_with: ""
+ confirm: "Вы уверены?"
+ date:
+ abbr_day_names:
+ - Вс
+ - Пн
+ - Вт
+ - Ср
+ - Чт
+ - Пт
+ - Сб
+ abbr_month_names:
+ - ~
+ - янв.
+ - февр.
+ - марта
+ - апр.
+ - мая
+ - июня
+ - июля
+ - авг.
+ - сент.
+ - окт.
+ - нояб.
+ - дек.
+ day_names:
+ - воскресенье
+ - понедельник
+ - вторник
+ - среда
+ - четверг
+ - пятница
+ - суббота
+ formats:
+ default: "%d.%m.%Y"
+ long: "%d %B %Y"
+ short: "%d %b"
+ month_names:
+ - ~
+ - января
+ - февраля
+ - марта
+ - апреля
+ - мая
+ - июня
+ - июля
+ - августа
+ - сентября
+ - октября
+ - ноября
+ - декабря
+ order:
+ - !ruby/symbol day
+ - !ruby/symbol month
+ - !ruby/symbol year
+ standalone_abbr_month_names:
+ - ~
+ - янв.
+ - февр.
+ - март
+ - апр.
+ - май
+ - июнь
+ - июль
+ - авг.
+ - сент.
+ - окт.
+ - нояб.
+ - дек.
+ standalone_day_names:
+ - Воскресенье
+ - Понедельник
+ - Вторник
+ - Среда
+ - Четверг
+ - Пятница
+ - Суббота
+ standalone_month_names:
+ - ~
+ - Январь
+ - Февраль
+ - Март
+ - Апрель
+ - Май
+ - Июнь
+ - Июль
+ - Август
+ - Сентябрь
+ - Октябрь
+ - Ноябрь
+ - Декабрь
+ datetime:
+ distance_in_words:
+ about_x_hours:
+ few: "около {{count}} часов"
+ many: "около {{count}} часов"
+ one: "около {{count}} часа"
+ other: "около {{count}} часа"
+ about_x_months:
+ few: "около {{count}} месяцев"
+ many: "около {{count}} месяцев"
+ one: "около {{count}} месяца"
+ other: "около {{count}} месяца"
+ about_x_years:
+ few: "около {{count}} лет"
+ many: "около {{count}} лет"
+ one: "около {{count}} года"
+ other: "около {{count}} лет"
+ almost_x_years:
+ few: "почти {{count}} года"
+ many: "почти {{count}} лет"
+ one: "почти {{count}} год"
+ other: "почти {{count}} лет"
+ half_a_minute: "меньше минуты"
+ less_than_x_minutes:
+ few: "меньше {{count}} минут"
+ many: "меньше {{count}} минут"
+ one: "меньше {{count}} минуты"
+ other: "меньше {{count}} минуты"
+ less_than_x_seconds:
+ few: "меньше {{count}} секунд"
+ many: "меньше {{count}} секунд"
+ one: "меньше {{count}} секунды"
+ other: "меньше {{count}} секунды"
+ over_x_years:
+ few: "больше {{count}} лет"
+ many: "больше {{count}} лет"
+ one: "больше {{count}} года"
+ other: "больше {{count}} лет"
+ x_days:
+ few: "{{count}} дня"
+ many: "{{count}} дней"
+ one: "{{count}} день"
+ other: "{{count}} дня"
+ x_minutes:
+ few: "{{count}} минуты"
+ many: "{{count}} минут"
+ one: "{{count}} минуту"
+ other: "{{count}} минуты"
+ x_months:
+ few: "{{count}} месяца"
+ many: "{{count}} месяцев"
+ one: "{{count}} месяц"
+ other: "{{count}} месяца"
+ x_seconds:
+ few: "{{count}} секунды"
+ many: "{{count}} секунд"
+ one: "{{count}} секунда"
+ other: "{{count}} секунды"
+ prompts:
+ day: День
+ hour: Часов
+ minute: Минут
+ month: Месяц
+ second: Секунд
+ year: Год
+ deactivate: Деактивировать
+ delete: Удалить
+ description: Описание
+ edit: Изменить
+ email: "Электронная почта"
+ end: Конец
+ ends_at: Окончание
+ event:
+ anniversaries: Юбилеи
+ anniversary: Юбилей
+ birthday: "День рождения"
+ birthdays: "Дни рождения"
+ delete: "Удалить мероприятие"
+ edit: "Изменить мероприятие"
+ send: "Отправить мероприятие"
+ facebook:
+ error:
+ session_expired: "Ваша Facebook сессия истекла"
+ feed_item:
+ friends_icon: "Иконка друзей"
+ user_comment_icon: ""
+ user_created_poll: "Создать опрос"
+ user_invited: ""
+ user_photo_icon: ""
+ user_profile_icon: "Иконка профиля пользователя"
+ user_updated_profile: ""
+ feedback:
+ delete: Удалить
+ feedback: "Обратная связь"
+ give_us_feedback: "Связаться с нами"
+ follow_on_twitter: ""
+ follow_on_twitter_link: ""
+ forum:
+ delete: "Удалить форум"
+ edit: "Изменить форум"
+ forum: Форум
+ forums: Форумы
+ last_post: "Последнее сообщение"
+ new: "Создать форум"
+ no_forums: "На {{site}} пока нет форумов."
+ posts: Сообщения
+ topic_starter: "Автор темы"
+ topics: Темы
+ forum_posts:
+ delete: "Удалить сообщение"
+ most_recent: ""
+ post_by_followup: ""
+ forum_topic:
+ back_to_forum: "Вернуться на форум"
+ back_to_forums: "Вернуться на форумы"
+ delete: "Удалить тему"
+ edit: "Изменить тему форума"
+ new: "Новая тема форума"
+ friend:
+ following: ""
+ see_all: "(Просмотреть все)"
+ group: Группа
+ groups: Группы
+ help: Помощь
+ hi: "Привет {{user}}"
+ home:
+ about_school: "О школе"
+ contact_us: "Связаться с Нами"
+ credits: ""
+ hosted_by: ""
+ invite:
+ email_list: ""
+ email_list_title: ""
+ invite_batchmates: ""
+ invite_friends: "Пригласить ещё друзей"
+ location: Местонахождение
+ login: Логин
+ login_entrance: Войти
+ logout: Выйти
+ message: Сообщение
+ my:
+ admin: Администратор
+ blog: "Мой блог"
+ direct_message: "Прямое сообщение"
+ friends: "Мои друзья"
+ home: Главная
+ messages: "Мои сообщения"
+ polls: "Мои опросы"
+ profile: "Мой профиль"
+ read_blogs: "Мои блоги"
+ see_polls: "Мои опросов"
+ name: Имя
+ new: Новый
+ notifier:
+ user_is_following_you: ""
+ user_is_following_you_message: "Вы можете просмотреть его профиль здесь:"
+ user_is_now_in_your_list: ""
+ user_send_news: ""
+ user_send_news_message: ""
+ user_sends_message: ""
+ user_sends_message_link: "Чтобы прочитать сообщение перейдите по ссылке ниже:"
+ user_status: ""
+ user_unfriends_you: ""
+ user_wrote_on_other_blog: ""
+ user_wrote_on_your_blog: ""
+ user_wrote_on_your_wall: ""
+ number:
+ currency:
+ format:
+ delimiter: " "
+ format: "%n %u"
+ precision: 2
+ separator: "."
+ unit: руб.
+ format:
+ delimiter: " "
+ precision: 3
+ separator: "."
+ human:
+ format:
+ delimiter: ""
+ precision: 1
+ storage_units:
+ format: "%n %u"
+ units:
+ byte:
+ few: байта
+ many: байт
+ one: байт
+ other: байта
+ gb: ГБ
+ kb: КБ
+ mb: МБ
+ tb: ТБ
+ percentage:
+ format:
+ delimiter: ""
+ precision:
+ format:
+ delimiter: ""
+ password: Пароль
+ password_confirm: "Подтвердить пароль"
+ photo:
+ caption: ""
+ crop: "Кадрировать изображение"
+ delete: "Удалить изображение"
+ new: ""
+ no_images: ""
+ upload: ""
+ upload_image: ""
+ pluralize: ""
+ poll:
+ close: "Закрыть опрос"
+ delete: "Удалить опрос"
+ edit: "Изменить опрос"
+ most_recent: ""
+ new: "Создать новый опрос"
+ new_post: "Добавить новое сообщение"
+ option: Вариант
+ poll_question: "Тема опроса"
+ total_votes: "Общее число голосов"
+ user_has_no_polls: ""
+ users_polls: "опросы от {{user}}"
+ posted: ""
+ profile:
+ actions: ""
+ address1: "Адрес 1"
+ address2: "Адрес 2"
+ blog: Блог
+ blog_comment_notification: "Уведомления о комментариях в блоге"
+ city: Город
+ company_name: "Название компании"
+ company_website: "Веб-сайт компании"
+ conact_info: Контакты
+ country: Страна
+ delete_friend_notification: ""
+ delete_icon: ""
+ delicious_links: "Del.icio.us ссылки"
+ edit: "Изменить профиль"
+ education: Образование
+ education_info: "Информация об образовании"
+ email_notification: "Уведомление по электронной почте"
+ event_notification: "Уведомление о событиях"
+ everyone: Каждый
+ featured_member: "Избранные члены"
+ flickr_username: ""
+ follow_notification: ""
+ friends: Друзья
+ from_year: ""
+ general_info: ""
+ google_map: ""
+ greetings_calendar: ""
+ group_members: ""
+ industry: ""
+ job_description: ""
+ known_site_users: ""
+ landline: ""
+ latest_flickr_pictures: ""
+ latest_youtube_videos: ""
+ linkedin_username: ""
+ location: ""
+ map_location: "Расположение на карте"
+ message_notification: Уведомления
+ mobile: ""
+ mysqlef: "Я сам"
+ newest_members: "Новые участники"
+ news_notification: "Уведомления о новостях"
+ notification: Уведомления
+ occupation: "Род занятий"
+ permissions: Разрешения
+ personal_info: "Личная информация"
+ picture_gallery: Фотогалерея
+ postal_code: "Почтовый индекс"
+ profile: Профиль
+ profile_comment_notification: "Уведомление о комментарии в профиле"
+ recent_activity: "Последние действия"
+ recent_events: "Последние события"
+ recent_events_in_network: "Недавние события в Сети"
+ see_all: "Просмотреть все"
+ send_invites: "Отправить приглашения"
+ state: Государство
+ status: Статус
+ supporters: Сторонники
+ to_year: "До года"
+ twitter_username: "Twitter логин"
+ university: Университет
+ unregistered_users_from: "Незарегистрированные студенты"
+ upload_icon: "Загрузить аватар"
+ upload_icon_message: ""
+ web_info: Веб-Инфо
+ website: Вебсайт
+ work_details: "Подробности о работе"
+ work_info: "Информация о работе"
+ yahoo_username: ""
+ youtube_username: ""
+ remember_me: "Запомнить меня"
+ rsvp: ""
+ save: Сохранить
+ search:
+ friends: "Поиск друзей"
+ search: Поиск
+ start: Начало
+ starts_at: Начало
+ subject: Тема
+ support:
+ array:
+ last_word_connector: " и "
+ sentence_connector: и
+ skip_last_comma: true
+ two_words_connector: " и "
+ words_connector: ", "
+ select:
+ prompt: "Выберите: "
+ tags: Метки
+ time:
+ am: утра
+ formats:
+ default: "%a, %d %b %Y, %H:%M:%S %z"
+ long: "%d %B %Y, %H:%M"
+ short: "%d %b, %H:%M"
+ pm: вечера
+ title: Название
+ username: Логин
View
2  config/routes.rb
@@ -109,5 +109,5 @@
map.with_options(:controller => 'javascripts') do |javascript|
javascript.hide_announcement 'javascripts/hide_announcement', :action => 'hide_announcement', :format => 'js'
end
-#Translate::Routes.translation_ui(map) if RAILS_ENV != "production"
+ Translate::Routes.translation_ui(map) if RAILS_ENV != "production"
end
View
20 vendor/plugins/translate/MIT-LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2009 [name of plugin creator]
+
+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.
View
61 vendor/plugins/translate/README
@@ -0,0 +1,61 @@
+Translate
+=========
+
+This plugin provides a web interface for translating Rails I18n texts (requires Rails 2.2 or higher) from one locale to another. The plugin has been tested only with the simple I18n backend that ships with Rails. I18n texts are read from and written to YAML files under config/locales.
+
+To translate to a new locale you need to add a YAML file for that locale that contains the locale as the top key and at least one translation.
+
+Please note that there are certain I18n keys that map to Array objects rather than strings and those are currently not dealt with by the translation UI. This means that Rails built in keys such as date.day_names need to be translated manually directly in the YAML file.
+
+To get the translation UI to write the YAML files in UTF8 you need to install the ya2yaml gem.
+
+The translation UI finds all I18n keys by extracting them from I18n lookups in your application source code. In addition it adds all :en and default locale keys from the I18n backend.
+
+- Updated: Each string in the UI now has an "Auto Translate" link which will send the original text to Google Translate and will input the returned translation into the form field for further clean up and review prior to saving.
+
+
+Rake Tasks
+=========
+
+In addition to the web UI this plugin adds the following rake tasks:
+
+translate:lost_in_translation
+translate:merge_keys
+translate:google
+translate:changed
+
+The lost_in_translation task shows you any I18n keys in your code that are do not have translations in the YAML file for your default locale, i.e. config/locales/sv.yml.
+
+The merge_keys task is supposed to be used in conjunction with Sven Fuch's Rails I18n TextMate bundle (http://github.com/svenfuchs/rails-i18n/tree/master). Texts and keys extracted with the TextMate bundle end up in the temporary file log/translations.yml. When you run the merge_keys rake task the keys are moved over to the corresponding I18n locale file, i.e. config/locales/sv.yml. The merge_keys task also checks for overwrites of existing keys by warning you that one of your extracted keys already exists with a different translation.
+
+The google task is used for auto translating from one locale to another using Google Translate.
+
+The changed rake task can show you between one YAML file to another which keys have had their texts changed.
+
+Installation
+=========
+Obtain the source with:
+
+./script/plugin install git://github.com/newsdesk/translate.git
+
+To mount the plugin, add the following to your config/routes.rb file:
+
+Translate::Routes.translation_ui(map) if RAILS_ENV != "production"
+
+Now visit /translate in your web browser to start translating.
+
+Dependencies
+=========
+
+- Rails 2.2 or higher
+- The ya2yaml gem if you want your YAML files written in UTF8 encoding.
+
+Authors
+=========
+
+- Peter Marklund (programming)
+- Joakim Westerlund (web design)
+
+Many thanks to http://newsdesk.se for sponsoring the development of this plugin.
+
+Copyright (c) 2009 Peter Marklund, released under the MIT license
View
11 vendor/plugins/translate/Rakefile
@@ -0,0 +1,11 @@
+require 'rake'
+require 'spec/rake/spectask'
+
+desc 'Default: run specs.'
+task :default => :spec
+
+desc 'Run the specs'
+Spec::Rake::SpecTask.new(:spec) do |t|
+ t.spec_opts = ['--colour --format progress --loadby mtime --reverse']
+ t.spec_files = FileList['spec/**/*_spec.rb']
+end
View
8 vendor/plugins/translate/init.rb
@@ -0,0 +1,8 @@
+require 'translate'
+
+# TODO: Use new method available_locales once Rails is upgraded, see:
+# http://github.com/svenfuchs/i18n/commit/411f8fe7c8f3f89e9b6b921fa62ed66cb92f3af4
+def I18n.valid_locales
+ I18n.backend.send(:init_translations) unless I18n.backend.initialized?
+ backend.send(:translations).keys.reject { |locale| locale == :root }
+end
View
17 vendor/plugins/translate/lib/translate.rb
@@ -0,0 +1,17 @@
+module Translate
+ def self.locales_dir=(dir)
+ @locales_dir = dir.to_s
+ end
+
+ def self.locales_dir
+ @locales_dir || Rails.root.join("config", "locales").to_s
+ end
+end
+
+require File.join(File.dirname(__FILE__), "translate_controller")
+require File.join(File.dirname(__FILE__), "translate_helper")
+Dir[File.join(File.dirname(__FILE__), "translate", "*.rb")].each do |file|
+ require file
+end
+
+
View
35 vendor/plugins/translate/lib/translate/file.rb
@@ -0,0 +1,35 @@
+require 'fileutils'
+require 'ya2yaml'
+class Translate::File
+ attr_accessor :path
+
+ def initialize(path)
+ self.path = path
+ end
+
+ def write(keys)
+ FileUtils.mkdir_p File.dirname(path)
+ File.open(path, "w") do |file|
+ file.puts keys_to_yaml(Translate::File.deep_stringify_keys(keys))
+ end
+ end
+
+ def read
+ File.exists?(path) ? YAML::load(IO.read(path)) : {}
+ end
+
+ # Stringifying keys for prettier YAML
+ def self.deep_stringify_keys(hash)
+ hash.inject({}) { |result, (key, value)|
+ value = deep_stringify_keys(value) if value.is_a? Hash
+ result[(key.to_s rescue key) || key] = value
+ result
+ }
+ end
+
+ private
+ def keys_to_yaml(keys)
+ # Using ya2yaml, if available, for UTF8 support
+ keys.respond_to?(:ya2yaml) ? keys.ya2yaml(:escape_as_utf8 => true) : keys.to_yaml
+ end
+end
View
123 vendor/plugins/translate/lib/translate/keys.rb
@@ -0,0 +1,123 @@
+require 'pathname'
+
+class Translate::Keys
+ # Allows keys extracted from lookups in files to be cached
+ def self.files
+ @@files ||= Translate::Keys.new.files
+ end
+
+ # Allows flushing of the files cache
+ def self.files=(files)
+ @@files = files
+ end
+
+ def files
+ @files ||= extract_files
+ end
+ alias_method :to_hash, :files
+
+ def keys
+ files.keys
+ end
+ alias_method :to_a, :keys
+
+ def i18n_keys(locale)
+ I18n.backend.send(:init_translations) unless I18n.backend.initialized?
+ extract_i18n_keys(I18n.backend.send(:translations)[locale.to_sym]).sort
+ end
+
+ # Convert something like:
+ #
+ # {
+ # :pressrelease => {
+ # :label => {
+ # :one => "Pressmeddelande"
+ # }
+ # }
+ # }
+ #
+ # to:
+ #
+ # {'pressrelease.label.one' => "Pressmeddelande"}
+ #
+ def self.to_shallow_hash(hash)
+ hash.inject({}) do |shallow_hash, (key, value)|
+ if value.is_a?(Hash)
+ to_shallow_hash(value).each do |sub_key, sub_value|
+ shallow_hash[[key, sub_key].join(".")] = sub_value
+ end
+ else
+ shallow_hash[key.to_s] = value
+ end
+ shallow_hash
+ end
+ end
+
+ # Convert something like:
+ #
+ # {'pressrelease.label.one' => "Pressmeddelande"}
+ #
+ # to:
+ #
+ # {
+ # :pressrelease => {
+ # :label => {
+ # :one => "Pressmeddelande"
+ # }
+ # }
+ # }
+ def self.to_deep_hash(hash)
+ hash.inject({}) do |deep_hash, (key, value)|
+ keys = key.to_s.split('.').reverse
+ leaf_key = keys.shift
+ key_hash = keys.inject({leaf_key.to_sym => value}) { |hash, key| {key.to_sym => hash} }
+ deep_merge!(deep_hash, key_hash)
+ deep_hash
+ end
+ end
+
+ # deep_merge by Stefan Rusterholz, see http://www.ruby-forum.com/topic/142809
+ def self.deep_merge!(hash1, hash2)
+ merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 }
+ hash1.merge!(hash2, &merger)
+ end
+
+ private
+ def extract_i18n_keys(hash, parent_keys = [])
+ hash.inject([]) do |keys, (key, value)|
+ full_key = parent_keys + [key]
+ if value.is_a?(Hash)
+ # Nested hash
+ keys += extract_i18n_keys(value, full_key)
+ elsif value.present?
+ # String leaf node
+ keys << full_key.join(".")
+ end
+ keys
+ end
+ end
+
+ def extract_files
+ files_to_scan.inject(HashWithIndifferentAccess.new) do |files, file|
+ IO.read(file).scan(i18n_lookup_pattern).flatten.map(&:to_sym).each do |key|
+ files[key] ||= []
+ path = Pathname.new(File.expand_path(file)).relative_path_from(Pathname.new(Rails.root)).to_s
+ files[key] << path unless files[key].include?(path)
+ end
+ files
+ end
+ end
+
+ def i18n_lookup_pattern
+ /\b(?:I18n\.t|I18n\.translate|t)(?:\s|\():?'([a-z0-9_]+.[a-z0-9_.]+)'\)?/
+ end
+
+ def files_to_scan
+ Dir.glob(File.join(files_root_dir, "{app,config,lib}", "**","*.{rb,erb,rhtml}")) +
+ Dir.glob(File.join(files_root_dir, "public", "javascripts", "**","*.js"))
+ end
+
+ def files_root_dir
+ Rails.root
+ end
+end
View
35 vendor/plugins/translate/lib/translate/log.rb
@@ -0,0 +1,35 @@
+class Translate::Log
+ attr_accessor :from_locale, :to_locale, :keys
+
+ def initialize(from_locale, to_locale, keys)
+ self.from_locale = from_locale
+ self.to_locale = to_locale
+ self.keys = keys
+ end
+
+ def write_to_file
+ current_texts = File.exists?(file_path) ? file.read : {}
+ current_texts.merge!(from_texts)
+ file.write(current_texts)
+ end
+
+ def read
+ file.read
+ end
+
+ private
+ def file
+ @file ||= Translate::File.new(file_path)
+ end
+
+ def from_texts
+ Translate::File.deep_stringify_keys(Translate::Keys.to_deep_hash(keys.inject({}) do |hash, key|
+ hash[key] = I18n.backend.send(:lookup, from_locale, key)
+ hash
+ end))
+ end
+
+ def file_path
+ File.join(Translate.locales_dir, "from_#{from_locale}_to_#{to_locale}.yml")
+ end
+end
View
11 vendor/plugins/translate/lib/translate/routes.rb
@@ -0,0 +1,11 @@
+module Translate
+ class Routes
+ def self.translation_ui(map)
+ map.with_options(:controller => 'translate') do |t|
+ t.translate_list 'translate'
+ t.translate 'translate/translate', :action => 'translate'
+ t.translate_reload 'translate/reload', :action => 'reload'
+ end
+ end
+ end
+end
View
20 vendor/plugins/translate/lib/translate/storage.rb
@@ -0,0 +1,20 @@
+class Translate::Storage
+ attr_accessor :locale
+
+ def initialize(locale)
+ self.locale = locale.to_sym
+ end
+
+ def write_to_file
+ Translate::File.new(file_path).write(keys)
+ end
+
+ private
+ def keys
+ {locale => I18n.backend.send(:translations)[locale]}
+ end
+
+ def file_path
+ File.join(Translate.locales_dir, "#{locale}.yml")
+ end
+end
View
165 vendor/plugins/translate/lib/translate_controller.rb
@@ -0,0 +1,165 @@
+class TranslateController < ActionController::Base
+ # It seems users with active_record_store may get a "no :secret given" error if we don't disable csrf protection,
+ skip_before_filter :verify_authenticity_token
+
+ prepend_view_path(File.join(File.dirname(__FILE__), "..", "views"))
+ layout 'translate'
+
+ before_filter :init_translations
+ before_filter :set_from_and_to_locales
+
+ def index
+ initialize_keys
+ filter_by_key_pattern
+ filter_by_text_pattern
+ filter_by_translated_or_changed
+ sort_keys
+ paginate_keys
+ @total_entries = @keys.size
+ end
+
+ def translate
+ I18n.backend.store_translations(@to_locale, Translate::Keys.to_deep_hash(params[:key]))
+ Translate::Storage.new(@to_locale).write_to_file
+ Translate::Log.new(@from_locale, @to_locale, params[:key].keys).write_to_file
+ force_init_translations # Force reload from YAML file
+ flash[:notice] = "Translations stored"
+ redirect_to params.slice(:filter, :sort_by, :key_type, :key_pattern, :text_type, :text_pattern).merge({:action => :index})
+ end
+
+ def reload
+ Translate::Keys.files = nil
+ redirect_to :action => 'index'
+ end
+
+ private
+ def initialize_keys
+ @files = Translate::Keys.files
+ @keys = (@files.keys.map(&:to_s) + Translate::Keys.new.i18n_keys(@from_locale)).uniq
+ @keys.reject! do |key|
+ from_text = lookup(@from_locale, key)
+ # When translating from one language to another, make sure there is a text to translate from.
+ # Always exclude non string translation objects as we don't support editing them in the UI.
+ (@from_locale != @to_locale && !from_text.present?) || (from_text.present? && !from_text.is_a?(String))
+ end
+ end
+
+ def lookup(locale, key)
+ I18n.backend.send(:lookup, locale, key)
+ end
+ helper_method :lookup
+
+ def filter_by_translated_or_changed
+ params[:filter] ||= 'all'
+ return if params[:filter] == 'all'
+ @keys.reject! do |key|
+ case params[:filter]
+ when 'untranslated'
+ lookup(@to_locale, key).present?
+ when 'translated'
+ lookup(@to_locale, key).blank?
+ when 'changed'
+ old_from_text(key).blank? || lookup(@from_locale, key) == old_from_text(key)
+ else
+ raise "Unknown filter '#{params[:filter]}'"
+ end
+ end
+ end
+
+ def filter_by_key_pattern
+ return if params[:key_pattern].blank?
+ @keys.reject! do |key|
+ case params[:key_type]
+ when "starts_with":
+ !key.starts_with?(params[:key_pattern])
+ when "contains":
+ key.index(params[:key_pattern]).nil?
+ else
+ raise "Unknown key_type '#{params[:key_type]}'"
+ end
+ end
+ end
+
+ def filter_by_text_pattern
+ return if params[:text_pattern].blank?
+ @keys.reject! do |key|
+ case params[:text_type]
+ when 'contains':
+ !lookup(@from_locale, key).present? || !lookup(@from_locale, key).to_s.downcase.index(params[:text_pattern].downcase)
+ when 'equals':
+ !lookup(@from_locale, key).present? || lookup(@from_locale, key).to_s.downcase != params[:text_pattern].downcase
+ else
+ raise "Unknown text_type '#{params[:text_type]}'"
+ end
+ end
+ end
+
+ def sort_keys
+ params[:sort_by] ||= "key"
+ case params[:sort_by]
+ when "key":
+ @keys.sort!
+ when "text":
+ @keys.sort! do |key1, key2|
+ if lookup(@from_locale, key1).present? && lookup(@from_locale, key2).present?
+ lookup(@from_locale, key1).to_s.downcase <=> lookup(@from_locale, key2).to_s.downcase
+ elsif lookup(@from_locale, key1).present?
+ -1
+ else
+ 1
+ end
+ end
+ else
+ raise "Unknown sort_by '#{params[:sort_by]}'"
+ end
+ end
+
+ def paginate_keys
+ params[:page] ||= 1
+ @paginated_keys = @keys[offset, per_page]
+ end
+
+ def offset
+ (params[:page].to_i - 1) * per_page
+ end
+
+ def per_page
+ 50
+ end
+ helper_method :per_page
+
+ def init_translations
+ I18n.backend.send(:init_translations) unless I18n.backend.initialized?
+ end
+
+ def force_init_translations
+ I18n.backend.send(:init_translations)
+ end
+
+ def default_locale
+ I18n.default_locale
+ end
+
+ def set_from_and_to_locales
+ session[:from_locale] ||= default_locale
+ session[:to_locale] ||= :en
+ session[:from_locale] = params[:from_locale] if params[:from_locale].present?
+ session[:to_locale] = params[:to_locale] if params[:to_locale].present?
+ @from_locale = session[:from_locale].to_sym
+ @to_locale = session[:to_locale].to_sym
+ end
+
+ def old_from_text(key)
+ return @old_from_text[key] if @old_from_text && @old_from_text[key]
+ @old_from_text = {}
+ text = key.split(".").inject(log_hash) do |hash, k|
+ hash ? hash[k] : nil
+ end
+ @old_from_text[key] = text
+ end
+ helper_method :old_from_text
+
+ def log_hash
+ @log_hash ||= Translate::Log.new(@from_locale, @to_locale, {}).read
+ end
+end
View
32 vendor/plugins/translate/lib/translate_helper.rb
@@ -0,0 +1,32 @@
+module TranslateHelper
+ def simple_filter(labels, param_name = 'filter', selected_value = nil)
+ selected_value ||= params[param_name]
+ filter = []
+ labels.each do |item|
+ if item.is_a?(Array)
+ type, label = item
+ else
+ type = label = item
+ end
+ if type.to_s == selected_value.to_s
+ filter << "<i>#{label}</i>"
+ else
+ link_params = params.merge({param_name.to_s => type})
+ link_params.merge!({"page" => nil}) if param_name.to_s != "page"
+ filter << link_to(label, link_params)
+ end
+ end
+ filter.join(" | ")
+ end
+
+ def n_lines(text, line_size)
+ n_lines = 1
+ if text.present?
+ n_lines = text.split("\n").size
+ if n_lines == 1 && text.length > line_size
+ n_lines = text.length / line_size + 1
+ end
+ end
+ n_lines
+ end
+end
View
130 vendor/plugins/translate/spec/controllers/translate_controller_spec.rb
@@ -0,0 +1,130 @@
+# -*- coding: utf-8 -*-
+require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
+
+describe TranslateController do
+ describe "index" do
+ before(:each) do
+ controller.stub!(:per_page).and_return(1)
+ I18n.backend.stub!(:translations).and_return(i18n_translations)
+ I18n.backend.instance_eval { @initialized = true }
+ keys = mock(:keys)
+ keys.stub!(:i18n_keys).and_return(['vendor.foobar'])
+ Translate::Keys.should_receive(:new).and_return(keys)
+ Translate::Keys.should_receive(:files).and_return(files)
+ I18n.stub!(:valid_locales).and_return([:en, :sv])
+ I18n.stub!(:default_locale).and_return(:sv)
+ end
+
+ it "shows sorted paginated keys from the translate from locale and extracted keys by default" do
+ get_page :index
+ assigns(:from_locale).should == :sv
+ assigns(:to_locale).should == :en
+ assigns(:files).should == files
+ assigns(:keys).sort.should == ['articles.new.page_title', 'home.page_title', 'vendor.foobar']
+ assigns(:paginated_keys).should == ['articles.new.page_title']
+ end
+
+ it "can be paginated with the page param" do
+ get_page :index, :page => 2
+ assigns(:files).should == files
+ assigns(:paginated_keys).should == ['home.page_title']
+ end
+
+ it "accepts a key_pattern param with key_type=starts_with" do
+ get_page :index, :key_pattern => 'articles', :key_type => 'starts_with'
+ assigns(:files).should == files
+ assigns(:paginated_keys).should == ['articles.new.page_title']
+ assigns(:total_entries).should == 1
+ end
+
+ it "accepts a key_pattern param with key_type=contains" do
+ get_page :index, :key_pattern => 'page_', :key_type => 'contains'
+ assigns(:files).should == files
+ assigns(:total_entries).should == 2
+ assigns(:paginated_keys).should == ['articles.new.page_title']
+ end
+
+ it "accepts a filter=untranslated param" do
+ get_page :index, :filter => 'untranslated'
+ assigns(:total_entries).should == 2
+ assigns(:paginated_keys).should == ['articles.new.page_title']
+ end
+
+ it "accepts a filter=translated param" do
+ get_page :index, :filter => 'translated'
+ assigns(:total_entries).should == 1
+ assigns(:paginated_keys).should == ['vendor.foobar']
+ end
+
+ it "accepts a filter=changed param" do
+ log = mock(:log)
+ old_translations = {:home => {:page_title => "Skapar ny artikel"}}
+ log.should_receive(:read).and_return(Translate::File.deep_stringify_keys(old_translations))
+ Translate::Log.should_receive(:new).with(:sv, :en, {}).and_return(log)
+ get_page :index, :filter => 'changed'
+ assigns(:total_entries).should == 1
+ assigns(:keys).should == ["home.page_title"]
+ end
+
+ def i18n_translations
+ HashWithIndifferentAccess.new({
+ :en => {
+ :vendor => {
+ :foobar => "Foo Baar"
+ }
+ },
+ :sv => {
+ :articles => {
+ :new => {
+ :page_title => "Skapa ny artikel"
+ }
+ },
+ :home => {
+ :page_title => "Välkommen till I18n"
+ },
+ :vendor => {
+ :foobar => "Fobar"
+ }
+ }
+ })
+ end
+
+ def files
+ HashWithIndifferentAccess.new({
+ :'home.page_title' => ["app/views/home/index.rhtml"],
+ :'general.back' => ["app/views/articles/new.rhtml", "app/views/categories/new.rhtml"],
+ :'articles.new.page_title' => ["app/views/articles/new.rhtml"]
+ })
+ end
+ end
+
+ describe "translate" do
+ it "should store translations to I18n backend and then write them to a YAML file" do
+ session[:from_locale] = :sv
+ session[:to_locale] = :en
+ translations = {
+ :articles => {
+ :new => {
+ :title => "New Article"
+ }
+ },
+ :category => "Category"
+ }
+ key_param = {'articles.new.title' => "New Article", "category" => "Category"}
+ I18n.backend.should_receive(:store_translations).with(:en, translations)
+ storage = mock(:storage)
+ storage.should_receive(:write_to_file)
+ Translate::Storage.should_receive(:new).with(:en).and_return(storage)
+ log = mock(:log)
+ log.should_receive(:write_to_file)
+ Translate::Log.should_receive(:new).with(I18n.default_locale, :en, key_param.keys).and_return(log)
+ post :translate, "key" => key_param
+ response.should be_redirect
+ end
+ end
+
+ def get_page(*args)
+ get(*args)
+ response.should be_success
+ end
+end
View
54 vendor/plugins/translate/spec/file_spec.rb
@@ -0,0 +1,54 @@
+require 'fileutils'
+require File.dirname(__FILE__) + '/spec_helper'
+
+describe Translate::File do
+ describe "write" do
+ before(:each) do
+ @file = Translate::File.new(file_path)
+ end
+
+ after(:each) do
+ FileUtils.rm(file_path)
+ end
+
+ it "writes all I18n messages for a locale to YAML file" do
+ @file.write(translations)
+ @file.read.should == Translate::File.deep_stringify_keys(translations)
+ end
+
+ def translations
+ {
+ :en => {
+ :article => {
+ :title => "One Article"
+ },
+ :category => "Category"
+ }
+ }
+ end
+ end
+
+ describe "deep_stringify_keys" do
+ it "should convert all keys in a hash to strings" do
+ Translate::File.deep_stringify_keys({
+ :en => {
+ :article => {
+ :title => "One Article"
+ },
+ :category => "Category"
+ }
+ }).should == {
+ "en" => {
+ "article" => {
+ "title" => "One Article"
+ },
+ "category" => "Category"
+ }
+ }
+ end
+ end
+
+ def file_path
+ File.join(File.dirname(__FILE__), "files", "en.yml")
+ end
+end
View
12 vendor/plugins/translate/spec/files/translate/app/models/article.rb
@@ -0,0 +1,12 @@
+class Article < ActiveRecord::Base
+ def validate
+ # t('li')
+ errors.add_to_base([t(:'article.key1') + "#{t('article.key2')}"])
+ I18n.t 'article.key3'
+ I18n.t 'article.key3'
+ I18n.t :'article.key4'
+ I18n.translate :'article.key5'
+ 'bla bla t' + "blubba bla" + ' foobar'
+ 'bla bla t ' + "blubba bla" + ' foobar'
+ end
+end
View
1  vendor/plugins/translate/spec/files/translate/app/views/category.erb
@@ -0,0 +1 @@
+<%= t(:'category_erb.key1') %>
View
1  vendor/plugins/translate/spec/files/translate/app/views/category.html
@@ -0,0 +1 @@
+t(:'category_html.key1')
View
1  vendor/plugins/translate/spec/files/translate/app/views/category.html.erb
@@ -0,0 +1 @@
+<%= t(:'category_html_erb.key1') %>
View
5 vendor/plugins/translate/spec/files/translate/app/views/category.rhtml
@@ -0,0 +1,5 @@
+<script>
+ document.createElement('li');
+</script>
+
+<%= t(:'category_rhtml.key1') %>
View
1  vendor/plugins/translate/spec/files/translate/public/javascripts/application.js
@@ -0,0 +1 @@
+I18n.t('js.alert')
View
97 vendor/plugins/translate/spec/keys_spec.rb
@@ -0,0 +1,97 @@
+require File.dirname(__FILE__) + '/spec_helper'
+
+describe Translate::Keys do
+ before(:each) do
+ @keys = Translate::Keys.new
+ @keys.stub!(:files_root_dir).and_return(i18n_files_dir)
+ end
+
+ describe "to_a" do
+ it "extracts keys from I18n lookups in .rb, .html.erb, and .rhtml files" do
+ @keys.to_a.map(&:to_s).sort.should == ['article.key1', 'article.key2', 'article.key3', 'article.key4', 'article.key5',
+ 'category_erb.key1', 'category_html_erb.key1', 'category_rhtml.key1', 'js.alert']
+ end
+ end
+
+ describe "to_hash" do
+ it "return a hash with I18n keys and file lists" do
+ @keys.to_hash[:'article.key3'].should == ["vendor/plugins/translate/spec/files/translate/app/models/article.rb"]
+ end
+ end
+
+ describe "i18n_keys" do
+ before(:each) do
+ I18n.backend.send(:init_translations) unless I18n.backend.initialized?
+ end
+
+ it "should return all keys in the I18n backend translations hash" do
+ I18n.backend.should_receive(:translations).and_return(translations)
+ @keys.i18n_keys(:en).should == ['articles.new.page_title', 'categories.flash.created', 'home.about']
+ end
+
+ def translations
+ {
+ :en => {
+ :home => {
+ :about => "This site is about making money"
+ },
+ :articles => {
+ :new => {
+ :page_title => "New Article"
+ }
+ },
+ :categories => {
+ :flash => {
+ :created => "Category created"
+ }
+ },
+ :empty => nil
+ }
+ }
+ end
+ end
+
+ describe "to_deep_hash" do
+ it "convert shallow hash with dot separated keys to deep hash" do
+ Translate::Keys.to_deep_hash(shallow_hash).should == deep_hash
+ end
+ end
+
+ describe "to_shallow_hash" do
+ it "converts a deep hash to a shallow one" do
+ Translate::Keys.to_shallow_hash(deep_hash).should == shallow_hash
+ end
+ end
+
+ ##########################################################################
+ #
+ # Helper Methods
+ #
+ ##########################################################################
+
+ def shallow_hash
+ {
+ 'pressrelease.label.one' => "Pressmeddelande",
+ 'pressrelease.label.other' => "Pressmeddelanden",
+ 'article' => "Artikel",
+ 'category' => ''
+ }
+ end
+
+ def deep_hash
+ {
+ :pressrelease => {
+ :label => {
+ :one => "Pressmeddelande",
+ :other => "Pressmeddelanden"
+ }
+ },
+ :article => "Artikel",
+ :category => ''
+ }
+ end
+
+ def i18n_files_dir
+ File.join(File.dirname(__FILE__), "files", "translate")
+ end
+end
View
47 vendor/plugins/translate/spec/log_spec.rb
@@ -0,0 +1,47 @@
+require 'fileutils'
+require File.dirname(__FILE__) + '/spec_helper'
+
+describe Translate::Log do
+ describe "write_to_file" do
+ before(:each) do
+ I18n.locale = :sv
+ I18n.backend.store_translations(:sv, from_texts)
+ keys = Translate::Keys.new
+ @log = Translate::Log.new(:sv, :en, keys.send(:extract_i18n_keys, from_texts))
+ @log.stub!(:file_path).and_return(file_path)
+ FileUtils.rm_f file_path
+ end
+
+ after(:each) do
+ FileUtils.rm_f file_path
+ end
+
+ it "writes new log file with from texts" do
+ File.exists?(file_path).should be_false
+ @log.write_to_file
+ File.exists?(file_path).should be_true
+ Translate::File.new(file_path).read.should == Translate::File.deep_stringify_keys(from_texts)
+ end
+
+ it "merges from texts with current texts in log file and re-writes the log file" do
+ @log.write_to_file
+ I18n.backend.store_translations(:sv, {:category => "Kategori ny"})
+ @log.keys = ['category']
+ @log.write_to_file
+ Translate::File.new(file_path).read['category'].should == "Kategori ny"
+ end
+
+ def file_path
+ File.join(File.dirname(__FILE__), "files", "from_sv_to_en.yml")
+ end
+
+ def from_texts
+ {
+ :article => {
+ :title => "En artikel"
+ },
+ :category => "Kategori"
+ }
+ end
+ end
+end
View
9 vendor/plugins/translate/spec/spec_helper.rb
@@ -0,0 +1,9 @@
+begin
+ require File.dirname(__FILE__) + '/../../../../spec/spec_helper'
+rescue LoadError
+ puts "You need to install rspec in your base app"
+ exit
+end
+
+plugin_spec_dir = File.dirname(__FILE__)
+ActiveRecord::Base.logger = Logger.new(plugin_spec_dir + "/debug.log")
View
33 vendor/plugins/translate/spec/storage_spec.rb
@@ -0,0 +1,33 @@
+require File.dirname(__FILE__) + '/spec_helper'
+
+describe Translate::Storage do
+ describe "write_to_file" do
+ before(:each) do
+ @storage = Translate::Storage.new(:en)
+ end
+
+ it "writes all I18n messages for a locale to YAML file" do
+ I18n.backend.should_receive(:translations).and_return(translations)
+ @storage.stub!(:file_path).and_return(file_path)
+ file = mock(:file)
+ file.should_receive(:write).with(translations)
+ Translate::File.should_receive(:new).with(file_path).and_return(file)
+ @storage.write_to_file
+ end
+
+ def file_path
+ File.join(File.dirname(__FILE__), "files", "en.yml")
+ end
+
+ def translations
+ {
+ :en => {
+ :article => {
+ :title => "One Article"
+ },
+ :category => "Category"
+ }
+ }
+ end
+ end
+end
View
23 vendor/plugins/translate/spec/translate_spec.rb
@@ -0,0 +1,23 @@
+require File.dirname(__FILE__) + '/spec_helper'
+
+describe Translate do
+ before(:each) do
+ Translate.send :remove_instance_variable, "@locales_dir" if Translate.instance_variable_get("@locales_dir")
+ end
+
+ describe "locales_dir" do
+ it "should have the correct default" do
+ Translate.locales_dir.should == Rails.root.join("config", "locales").to_s
+ end
+
+ it "should be possible to set it" do
+ Translate.locales_dir = Rails.root.join("config", "locales").to_s
+ Translate.locales_dir.should == Rails.root.join("config", "locales").to_s
+ end
+
+ it "should autmatically coerce inputs to strings on assignment" do
+ Translate.locales_dir = Rails.root.join("config", "locales")
+ Translate.locales_dir.should == Rails.root.join("config", "locales").to_s
+ end
+ end
+end
View
162 vendor/plugins/translate/tasks/translate.rake
@@ -0,0 +1,162 @@
+require 'yaml'
+
+class Hash
+ def deep_merge(other)
+ # deep_merge by Stefan Rusterholz, see http://www.ruby-forum.com/topic/142809
+ merger = proc { |key, v1, v2| (Hash === v1 && Hash === v2) ? v1.merge(v2, &merger) : v2 }
+ merge(other, &merger)
+ end
+
+ def set(keys, value)
+ key = keys.shift
+ if keys.empty?
+ self[key] = value
+ else
+ self[key] ||= {}
+ self[key].set keys, value
+ end
+ end
+
+ if ENV['SORT']
+ # copy of ruby's to_yaml method, prepending sort.
+ # before each so we get an ordered yaml file
+ def to_yaml( opts = {} )
+ YAML::quick_emit( self, opts ) do |out|
+ out.map( taguri, to_yaml_style ) do |map|
+ sort.each do |k, v| #<- Adding sort.
+ map.add( k, v )
+ end
+ end
+ end
+ end
+ end
+end
+
+namespace :translate do
+ desc "Show I18n keys that are missing in the config/locales/default_locale.yml YAML file"
+ task :lost_in_translation => :environment do
+ LOCALE = I18n.default_locale
+ keys = []; result = []; locale_hash = {}
+ locale_files = Dir.glob(File.join(Translate.locales_dir, "**","#{LOCALE}.yml"))
+ locale_files.each do |locale_file_name|
+ locale_hash = locale_hash.deep_merge(YAML::load(File.open(locale_file_name))[LOCALE.to_s])
+ end
+ lookup_pattern = Translate::Keys.new.send(:i18n_lookup_pattern)
+ Dir.glob(File.join("app", "**","*.{erb,rb,rhtml}")).each do |file_name|
+ File.open(file_name, "r+").each do |line|
+ line.scan(lookup_pattern) do |key_string|
+ result << "#{key_string} in \t #{file_name} is not in any locale file" unless key_exist?(key_string.first.split("."), locale_hash)
+ end
+ end
+ end
+ puts !result.empty? ? result.join("\n") : "No missing translations for locale: #{LOCALE}"
+ end
+
+ def key_exist?(key_arr,locale_hash)
+ key = key_arr.slice!(0)
+ if key
+ key_exist?(key_arr, locale_hash[key]) if (locale_hash && locale_hash.include?(key))
+ elsif locale_hash
+ true
+ end
+ end
+
+ desc "Merge I18n keys from log/translations.yml into config/locales/*.yml (for use with the Rails I18n TextMate bundle)"
+ task :merge_keys => :environment do
+ I18n.backend.send(:init_translations)
+ new_translations = YAML::load(IO.read(File.join(Rails.root, "log", "translations.yml")))
+ raise("Can only merge in translations in single locale") if new_translations.keys.size > 1
+ locale = new_translations.keys.first
+
+ overwrites = false
+ Translate::Keys.new.send(:extract_i18n_keys, new_translations[locale]).each do |key|
+ new_text = key.split(".").inject(new_translations[locale]) { |hash, sub_key| hash[sub_key] }
+ existing_text = I18n.backend.send(:lookup, locale.to_sym, key)
+ if existing_text && new_text != existing_text
+ puts "ERROR: key #{key} already exists with text '#{existing_text.inspect}' and would be overwritten by new text '#{new_text}'. " +
+ "Set environment variable OVERWRITE=1 if you really want to do this."
+ overwrites = true
+ end
+ end
+
+ if !overwrites || ENV['OVERWRITE']
+ I18n.backend.store_translations(locale, new_translations[locale])
+ Translate::Storage.new(locale).write_to_file
+ end
+ end
+
+ desc "Apply Google translate to auto translate all texts in locale ENV['FROM'] to locale ENV['TO']"
+ task :google => :environment do
+ raise "Please specify FROM and TO locales as environment variables" if ENV['FROM'].blank? || ENV['TO'].blank?
+
+ # Depends on httparty gem
+ # http://www.robbyonrails.com/articles/2009/03/16/httparty-goes-foreign
+ class GoogleApi
+ include HTTParty
+ base_uri 'ajax.googleapis.com'
+ def self.translate(string, to, from)
+ tries = 0
+ begin
+ get("/ajax/services/language/translate",
+ :query => {:langpair => "#{from}|#{to}", :q => string, :v => 1.0},
+ :format => :json)
+ rescue
+ tries += 1
+ puts("SLEEPING - retrying in 5...")
+ sleep(5)
+ retry if tries < 3
+ end
+ end
+ end
+
+ I18n.backend.send(:init_translations)
+
+ start_at = Time.now
+ translations = {}
+ Translate::Keys.new.i18n_keys(ENV['FROM']).each do |key|
+ from_text = I18n.backend.send(:lookup, ENV['FROM'], key).to_s
+ to_text = I18n.backend.send(:lookup, ENV['TO'], key)
+ if !from_text.blank? && to_text.blank?
+ print "#{key}: '#{from_text[0, 40]}' => "
+ if !translations[from_text]
+ if response = GoogleApi.translate(from_text, ENV['TO'], ENV['FROM'])
+ translations[from_text] = response["responseData"] && response["responseData"]["translatedText"]
+ end
+ end
+ if !(translation = translations[from_text]).blank?
+ translation.gsub!(/\(\(([a-z_.]+)\)\)/i, '{{\1}}')
+ # Google translate sometimes replaces {{foobar}} with (()) foobar. We skip these
+ if translation !~ /\(\(\)\)/
+ puts "'#{translation[0, 40]}'"
+ I18n.backend.store_translations(ENV['TO'].to_sym, Translate::Keys.to_deep_hash({key => translation}))
+ else
+ puts "SKIPPING since interpolations were messed up: '#{translation[0,40]}'"
+ end
+ else
+ puts "NO TRANSLATION - #{response.inspect}"
+ end
+ end
+ end
+
+ puts "\nTime elapsed: #{(((Time.now - start_at) / 60) * 10).to_i / 10.to_f} minutes"
+ Translate::Storage.new(ENV['TO'].to_sym).write_to_file
+ end
+
+ desc "List keys that have changed I18n texts between YAML file ENV['FROM_FILE'] and YAML file ENV['TO_FILE']. Set ENV['VERBOSE'] to see changes"
+ task :changed => :environment do
+ from_hash = Translate::Keys.to_shallow_hash(Translate::File.new(ENV['FROM_FILE']).read)
+ to_hash = Translate::Keys.to_shallow_hash(Translate::File.new(ENV['TO_FILE']).read)
+ from_hash.each do |key, from_value|
+ if (to_value = to_hash[key]) && to_value != from_value
+ key_without_locale = key[/^[^.]+\.(.+)$/, 1]
+ if ENV['VERBOSE']
+ puts "KEY: #{key_without_locale}"
+ puts "FROM VALUE: '#{from_value}'"
+ puts "TO VALUE: '#{to_value}'"
+ else
+ puts key_without_locale
+ end
+ end
+ end
+ end
+end
View
360 vendor/plugins/translate/views/layouts/translate.rhtml
@@ -0,0 +1,360 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
+ <%= javascript_include_tag :defaults, 'prototype' %>
+ <title><%= h(@page_title) %></title>
+
+<script type="text/javascript" src="http://www.google.com/jsapi"></script>
+<script type="text/javascript">
+ google.load("language", "1");
+
+ function getGoogleTranslation(id, text, from_language, to_language) {
+ text = text.replace(/\{\{/, '__').replace(/\}\}/, '__')
+ google.language.translate(text, from_language, to_language, function(result) {
+ if (!result.error) {
+ result_text = result.translation.unescapeHTML().gsub(/__(.+)__/, function(match){
+ return '{{' + match[1] + '}}';
+ });
+ Form.Element.setValue(id, result_text);
+ }
+ });
+
+ }
+
+ /*
+ prototypeUtils.js from http://jehiah.com/
+ Licensed under Creative Commons.
+ version 1.0 December 20 2005
+
+ Contains:
+ + Form.Element.setValue()
+ + unpackToForm()
+
+ */
+
+ /* Form.Element.setValue("fieldname/id","valueToSet") */
+ Form.Element.setValue = function(element,newValue) {
+ element_id = element;
+ element = $(element);
+ if (!element){element = document.getElementsByName(element_id)[0];}
+ if (!element){return false;}
+ var method = element.tagName.toLowerCase();
+ var parameter = Form.Element.SetSerializers[method](element,newValue);
+ }
+
+ Form.Element.SetSerializers = {
+ input: function(element,newValue) {
+ switch (element.type.toLowerCase()) {
+ case 'submit':
+ case 'hidden':
+ case 'password':
+ case 'text':
+ return Form.Element.SetSerializers.textarea(element,newValue);
+ case 'checkbox':
+ case 'radio':
+ return Form.Element.SetSerializers.inputSelector(element,newValue);
+ }
+ return false;
+ },
+
+ inputSelector: function(element,newValue) {
+ fields = document.getElementsByName(element.name);
+ for (var i=0;i<fields.length;i++){
+ if (fields[i].value == newValue){
+ fields[i].checked = true;
+ }
+ }
+ },
+
+ textarea: function(element,newValue) {
+ element.value = newValue;
+ },
+
+ select: function(element,newValue) {
+ var value = '', opt, index = element.selectedIndex;
+ for (var i=0;i< element.options.length;i++){
+ if (element.options[i].value == newValue){
+ element.selectedIndex = i;
+ return true;
+ }
+ }
+ }
+ }
+
+ function unpackToForm(data){
+ for (i in data){
+ Form.Element.setValue(i,data[i].toString());
+ }
+ }
+
+</script>
+
+
+<style type="text/css">
+ /*reset.css*/
+ /* v1.0 | 20080212 */
+ html, body, div, span, applet, object, iframe,
+ h1, h2, h3, h4, h5, h6, p, blockquote, pre,
+ a, abbr, acronym, address, big, cite, code,
+ del, dfn, em, font, img, ins, kbd, q, s, samp,
+ small, strike, strong, sub, sup, tt, var,
+ b, u, i, center,
+ dl, dt, dd, ol, ul, li,
+ fieldset, form, label, legend,
+ table, caption, tbody, tfoot, thead, tr, th, td {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ outline: 0;
+ font-size: 100%;
+ vertical-align: baseline;
+ background: transparent;
+ }
+ body {
+ line-height: 1;
+ }
+ ol, ul {
+ list-style: none;
+ }
+ blockquote, q {
+ quotes: none;
+ }
+ blockquote:before, blockquote:after,
+ q:before, q:after {
+ content: '';
+ content: none;
+ }
+
+ /* remember to define focus styles! */
+ :focus {
+ outline: 0;
+ }
+
+ /* remember to highlight inserts somehow! */
+ ins {
+ text-decoration: none;
+ }
+ del {
+ text-decoration: line-through;
+ }
+
+ /* tables still need 'cellspacing="0"' in the markup */
+ table {
+ border-collapse: collapse;
+ border-spacing: 0;
+ }
+ /*clear fix*/
+ .clearfix:after{content:".";display:block;height:0;clear:both;visibility:hidden;}
+ .clearfix{display:inline-block;}
+ html[xmlns] .clearfix {
+ display: block;
+ }
+ * html .clearfix{height:1%;}
+ /*start layout*/
+ body{
+ background:#fff;
+ color:#333;
+ font-size:75%;
+ font-family:Arial;
+ margin:2em auto;
+ line-height:1.5em;
+ }
+ textarea,input,select{
+ font-family:Arial;
+ font-size:1em;
+ }
+ h1{
+ color:#d46021;
+ font-size:2em;
+ margin-bottom:0.5em;
+ }
+ h2{
+ text-align:left;
+ color:#d46021;
+ font-size:1.3em;
+ padding-left:0;
+ }
+ a{
+ color:#2158C7;
+ }
+ div#container{
+ width:960px;
+ margin:0 auto;
+ font-size:1em;
+ }
+ /*paging*/
+ div.paging{
+ margin-bottom:1em;
+ text-align:left;
+ }
+ div.paging div{
+ border:solid 1px red;
+ margin:1em 1em 0;
+ padding:0.5em;
+ border:solid 1px #d5d6d5;
+ background:#f1f1f1;
+ }
+ ul.paging{
+ display:inline-block;
+ }
+ ul.paging li{
+ display:block;
+ margin:0.2em 0;
+ float:left;
+ }
+ ul.paging li.selected a{
+ color:#fff;
+ background:#2158C7;
+ font-weight:bold;
+ padding:0.5em 0.7em;
+ }
+ ul.paging li a{
+ display:inline-block;
+ line-height:1em;
+ padding:0.5em 0.5em;
+ }
+ /*forms filter*/
+ fieldset{
+ padding:1em;
+ margin:1em;
+ border:solid 2px #d46021;
+ }
+ legend{
+ font-size: