russian 0.6.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,39 +1,102 @@
1
1
  ru:
2
2
  date:
3
3
  formats:
4
- # Форматы указываются в виде, поддерживаемом strftime.
5
- # По умолчанию используется default.
6
- # Можно добавлять собственные форматы
7
- #
8
- #
9
4
  # Use the strftime parameters for formats.
10
5
  # When no format has been given, it uses default.
11
6
  # You can provide other formats here if you like!
7
+ #
8
+ #
9
+ # Форматы указываются в виде, поддерживаемом strftime.
10
+ # По умолчанию используется default.
11
+ # Можно добавлять собственные форматы.
12
12
  default: "%d.%m.%Y"
13
13
  short: "%d %b"
14
14
  long: "%d %B %Y"
15
-
16
- # Названия дней недели -- контекстные и отдельностоящие
17
- common_day_names: [воскресенье, понедельник, вторник, среда, четверг, пятница, суббота]
18
- standalone_day_names: [Воскресенье, Понедельник, Вторник, Среда, Четверг, Пятница, Суббота]
15
+
16
+ # Названия дней недели контекстные и отдельностоящие
17
+ common_day_names:
18
+ [воскресенье, понедельник, вторник, среда, четверг, пятница, суббота]
19
+ standalone_day_names:
20
+ [Воскресенье, Понедельник, Вторник, Среда, Четверг, Пятница, Суббота]
19
21
  common_abbr_day_names: [Вс, Пн, Вт, Ср, Чт, Пт, Сб]
20
22
  standalone_abbr_day_names: [вс, пн, вт, ср, чт, пт, сб]
21
23
 
22
- # Названия месяцев -- сокращенные и полные, плюс отдельностоящие.
23
- # Не забудьте nil в начале массиве (~)
24
- #
25
- #
26
- # Don't forget the nil at the beginning; there's no such thing as a 0th month
27
- common_month_names: [~, января, февраля, марта, апреля, мая, июня, июля, августа, сентября, октября, ноября, декабря]
28
- standalone_month_names: [~, Январь, Февраль, Март, Апрель, Май, Июнь, Июль, Август, Сентябрь, Октябрь, Ноябрь, Декабрь]
29
- common_abbr_month_names: [~, янв., февр., марта, апр., мая, июня, июля, авг., сент., окт., нояб., дек.]
30
- standalone_abbr_month_names: [~, янв., февр., март, апр., май, июнь, июль, авг., сент., окт., нояб., дек.]
31
-
32
- # Порядок компонентов даты для хелперов
24
+ # Don't forget the nil at the beginning; there's no such
25
+ # thing as a 0th month.
33
26
  #
34
27
  #
28
+ # Названия месяцев — сокращенные и полные, плюс отдельностоящие.
29
+ # Не забудьте nil в начале массива (~)
30
+ common_month_names:
31
+ [
32
+ ~,
33
+ января,
34
+ февраля,
35
+ марта,
36
+ апреля,
37
+ мая,
38
+ июня,
39
+ июля,
40
+ августа,
41
+ сентября,
42
+ октября,
43
+ ноября,
44
+ декабря,
45
+ ]
46
+ standalone_month_names:
47
+ [
48
+ ~,
49
+ Январь,
50
+ Февраль,
51
+ Март,
52
+ Апрель,
53
+ Май,
54
+ Июнь,
55
+ Июль,
56
+ Август,
57
+ Сентябрь,
58
+ Октябрь,
59
+ Ноябрь,
60
+ Декабрь,
61
+ ]
62
+ common_abbr_month_names:
63
+ [
64
+ ~,
65
+ янв.,
66
+ февр.,
67
+ марта,
68
+ апр.,
69
+ мая,
70
+ июня,
71
+ июля,
72
+ авг.,
73
+ сент.,
74
+ окт.,
75
+ нояб.,
76
+ дек.,
77
+ ]
78
+ standalone_abbr_month_names:
79
+ [
80
+ ~,
81
+ янв.,
82
+ февр.,
83
+ март,
84
+ апр.,
85
+ май,
86
+ июнь,
87
+ июль,
88
+ авг.,
89
+ сент.,
90
+ окт.,
91
+ нояб.,
92
+ дек.,
93
+ ]
94
+
35
95
  # Used in date_select and datime_select.
36
- order:
96
+ #
97
+ #
98
+ # Порядок компонентов даты для хелперов
99
+ order:
37
100
  - :day
38
101
  - :month
39
102
  - :year
@@ -44,7 +107,7 @@ ru:
44
107
  default: "%a, %d %b %Y, %H:%M:%S %z"
45
108
  short: "%d %b, %H:%M"
46
109
  long: "%d %B %Y, %H:%M"
47
-
110
+
48
111
  # am/pm решено перевести как "утра/вечера" :)
49
112
  am: "утра"
50
113
  pm: "вечера"
@@ -1,28 +1,49 @@
1
- # -*- encoding: utf-8 -*-
1
+ # frozen_string_literal: true
2
2
 
3
- # Правило плюрализации для русского языка, взято из CLDR, http://unicode.org/cldr/
3
+ # Russian language pluralization rules, taken from CLDR project,
4
+ # http://unicode.org/cldr/
4
5
  #
5
- # Russian language pluralization rules, taken from CLDR project, http://unicode.org/cldr/
6
+ #
7
+ # Правило плюрализации для русского языка, взято из CLDR,
8
+ # http://unicode.org/cldr/
6
9
  #
7
10
  # one -> n mod 10 is 1 and n mod 100 is not 11;
8
11
  # few -> n mod 10 in 2..4 and n mod 100 not in 12..14;
9
12
  # many -> n mod 10 is 0 or n mod 10 in 5..9 or n mod 100 in 11..14;
10
13
  # other -> everything else
11
14
  #
12
- # Пример
15
+ # Example:
13
16
  #
14
17
  # :one = 1, 21, 31, 41, 51, 61...
15
18
  # :few = 2-4, 22-24, 32-34...
16
19
  # :many = 0, 5-20, 25-30, 35-40...
17
20
  # :other = 1.31, 2.31, 5.31...
21
+ from_2_to_4 = (2..4).to_a.freeze
22
+ from_5_to_9 = (5..9).to_a.freeze
23
+ from_11_to_14 = (11..14).to_a.freeze
24
+ from_12_to_14 = (12..14).to_a.freeze
25
+
18
26
  {
19
- :ru => {
20
- :'i18n' => {
21
- :plural => {
22
- :rule => lambda { |n|
23
- n % 10 == 1 && n % 100 != 11 ? :one : [2, 3, 4].include?(n % 10) && ![12, 13, 14].include?(n % 100) ? :few : n % 10 == 0 || [5, 6, 7, 8, 9].include?(n % 10) || [11, 12, 13, 14].include?(n % 100) ? :many : :other
24
- }
27
+ ru: {
28
+ i18n: {
29
+ plural: {
30
+ rule: lambda do |n|
31
+ return :other unless n.is_a?(Numeric)
32
+
33
+ mod10 = n % 10
34
+ mod100 = n % 100
35
+
36
+ if mod10 == 1 && mod100 != 11
37
+ :one
38
+ elsif from_2_to_4.include?(mod10) && !from_12_to_14.include?(mod100)
39
+ :few
40
+ elsif mod10.zero? || from_5_to_9.include?(mod10) || from_11_to_14.include?(mod100)
41
+ :many
42
+ else
43
+ :other
44
+ end
45
+ end
25
46
  }
26
47
  }
27
48
  }
28
- }
49
+ }
@@ -1,16 +1,17 @@
1
- # -*- encoding: utf-8 -*-
1
+ # frozen_string_literal: true
2
2
 
3
- # I18n transliteration delegates to Russian::Transliteration (we're unable
4
- # to use common I18n transliteration tables with Russian)
3
+ # I18n transliteration delegates to `Russian::Transliteration` (we're unable
4
+ # to use common I18n transliteration tables with Russian).
5
5
  #
6
- # Правило транслитерации для I18n использует Russian::Transliteration
6
+ #
7
+ # Правило транслитерации для I18n использует `Russian::Transliteration`
7
8
  # (использовать обычный механизм и таблицу транслитерации I18n с
8
- # русским языком не получится)
9
+ # русским языком не получится).
9
10
  {
10
- :ru => {
11
- :i18n => {
12
- :transliterate => {
13
- :rule => lambda { |str| Russian.transliterate(str) }
11
+ ru: {
12
+ i18n: {
13
+ transliterate: {
14
+ rule: ->(str) { Russian.transliterate(str) }
14
15
  }
15
16
  }
16
17
  }
@@ -1,8 +1,99 @@
1
- # Rails hacks
2
- if defined?(ActiveModel)
3
- require 'active_model_ext/custom_error_message'
4
- end
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "active_model_ext/custom_error_message"
4
+ require_relative "action_view_ext/helpers/date_helper"
5
+
6
+ module Russian
7
+ # Installs Rails-specific patches exposed by the gem.
8
+ #
9
+ # The integration is lazy: outside Rails it does nothing, and inside Rails it
10
+ # wires itself through `ActiveSupport.on_load` hooks and also patches
11
+ # framework components that are already loaded.
12
+ #
13
+ #
14
+ # Устанавливает Rails-специфичные патчи, поставляемые gem'ом.
15
+ #
16
+ # Интеграция сделана "ленивой": вне Rails она ничего не делает,
17
+ # а внутри Rails подключается через `ActiveSupport.on_load` и
18
+ # дополнительно патчит уже загруженные части фреймворка.
19
+ #
20
+ # @example
21
+ # require "russian"
22
+ # Russian::RailsIntegration.install!
23
+ module RailsIntegration
24
+ module_function
25
+
26
+ # Installs Rails hooks and applies patches to already loaded components.
27
+ #
28
+ # The method is idempotent and safe to call more than once.
29
+ #
30
+ #
31
+ # Устанавливает Rails-хуки и применяет патчи к уже загруженным компонентам.
32
+ #
33
+ # Метод идемпотентен и безопасен для повторных вызовов.
34
+ #
35
+ # @return [void]
36
+ #
37
+ # @example
38
+ # Russian::RailsIntegration.install!
39
+ def install!
40
+ return unless rails?
41
+
42
+ define_railtie!
43
+ install_hooks!
44
+
45
+ patch_active_model! if defined?(ActiveModel::Error)
46
+ patch_action_view! if defined?(ActionView::Helpers::DateTimeSelector)
47
+ end
48
+
49
+ # @private
50
+ def install_hooks!
51
+ return if @hooks_installed
52
+
53
+ %i[active_model active_model_error].each { |hook| on_load(hook, :patch_active_model!) }
54
+ on_load(:action_view, :patch_action_view!)
5
55
 
6
- if defined?(ActionView::Helpers)
7
- require 'action_view_ext/helpers/date_helper'
56
+ @hooks_installed = true
57
+ end
58
+
59
+ # @private
60
+ def on_load(hook, patcher)
61
+ ActiveSupport.on_load(hook) { Russian::RailsIntegration.public_send(patcher) }
62
+ end
63
+
64
+ # @private
65
+ def patch_active_model!
66
+ patch!(ActiveModel::Error.singleton_class, Russian::ActiveModelExt::ErrorPatch)
67
+ end
68
+
69
+ # @private
70
+ def patch_action_view!
71
+ patch!(ActionView::Helpers::DateHelper, Russian::ActionViewExt::Helpers::DateHelperPatch)
72
+ patch!(ActionView::Helpers::DateTimeSelector, Russian::ActionViewExt::Helpers::DateTimeSelectorPatch)
73
+ end
74
+
75
+ # @private
76
+ def patch!(target, extension)
77
+ target.prepend(extension) unless target < extension
78
+ end
79
+
80
+ # @private
81
+ def rails?
82
+ defined?(Rails::Railtie) && defined?(ActiveSupport) && ActiveSupport.respond_to?(:on_load)
83
+ end
84
+
85
+ # @private
86
+ def define_railtie!
87
+ return unless defined?(Rails::Railtie)
88
+ return if defined?(Russian::Railtie)
89
+
90
+ Russian.const_set(:Railtie, Class.new(Rails::Railtie) do
91
+ initializer "russian.install" do
92
+ Russian::RailsIntegration.install!
93
+ end
94
+ end)
95
+ end
96
+ end
8
97
  end
98
+
99
+ Russian::RailsIntegration.install!
@@ -0,0 +1,166 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "date"
4
+ require "time"
5
+
6
+ module Russian
7
+ # Localized `strptime` helpers for Russian month and weekday names.
8
+ #
9
+ # The module normalizes Russian textual date/time tokens to the English
10
+ # tokens expected by Ruby's native parsers, then delegates to
11
+ # `Date.strptime`, `Time.strptime`, or `DateTime.strptime`.
12
+ #
13
+ #
14
+ # Локализованные хелперы `strptime` для русских названий месяцев и дней
15
+ # недели.
16
+ #
17
+ # Модуль нормализует русские текстовые токены даты и времени к английским
18
+ # токенам, которые ожидают стандартные parser'ы Ruby, а затем делегирует
19
+ # работу в `Date.strptime`, `Time.strptime` или `DateTime.strptime`.
20
+ module Strptime
21
+ module_function
22
+
23
+ # Parses a localized Russian date string with `Date.strptime`.
24
+ #
25
+ #
26
+ # Разбирает локализованную русскую строку даты через `Date.strptime`.
27
+ #
28
+ # @param string [String] Localized date string.
29
+ # Локализованная строка даты.
30
+ # @param format [String] Optional `strptime` format string.
31
+ # Строка формата `strptime`.
32
+ # @return [Date] Parsed date.
33
+ # Разобранная дата.
34
+ def date_strptime(string, format = "%F")
35
+ Date.strptime(normalize_input(string, format), format, Date::ITALY)
36
+ end
37
+
38
+ # Parses a localized Russian date/time string with `Time.strptime`.
39
+ #
40
+ #
41
+ # Разбирает локализованную русскую строку даты и времени через
42
+ # `Time.strptime`.
43
+ #
44
+ # @param string [String] Localized date/time string.
45
+ # Локализованная строка даты и времени.
46
+ # @param format [String] `strptime` format string.
47
+ # Строка формата `strptime`.
48
+ # @param now [Time] Optional base time passed to `Time.strptime`.
49
+ # Базовое значение времени, передаваемое в `Time.strptime`.
50
+ # @return [Time] Parsed time.
51
+ # Разобранное время.
52
+ def time_strptime(string, format, now = Time.now)
53
+ Time.strptime(normalize_input(string, format), format, now)
54
+ end
55
+
56
+ # Parses a localized Russian date/time string with `DateTime.strptime`.
57
+ #
58
+ #
59
+ # Разбирает локализованную русскую строку даты и времени через
60
+ # `DateTime.strptime`.
61
+ #
62
+ # @param string [String] Localized date/time string.
63
+ # Локализованная строка даты и времени.
64
+ # @param format [String] Optional `strptime` format string.
65
+ # Строка формата `strptime`.
66
+ # @return [DateTime] Parsed datetime.
67
+ # Разобранный `DateTime`.
68
+ def datetime_strptime(string, format = "%FT%T%z")
69
+ DateTime.strptime(normalize_input(string, format), format, Date::ITALY)
70
+ end
71
+
72
+ # @private
73
+ def normalize_input(string, format)
74
+ return string unless string.is_a?(String) && format.is_a?(String)
75
+
76
+ replacements = localized_replacements(format)
77
+ return string if replacements.empty?
78
+
79
+ token_regexp = Regexp.new(
80
+ "(?<!\\p{L})(?:#{replacements.keys.sort_by(&:length).reverse.map do |token|
81
+ Regexp.escape(token)
82
+ end.join("|")})(?!\\p{L})",
83
+ Regexp::IGNORECASE
84
+ )
85
+
86
+ string.gsub(token_regexp) { |token| replacements.fetch(token.downcase) }
87
+ end
88
+
89
+ # @private
90
+ def localized_replacements(format)
91
+ directives = directives_in(format)
92
+ replacements = {}
93
+
94
+ replacements.merge!(localized_tokens(month_names_key(format), Date::MONTHNAMES)) if directives.include?("B")
95
+
96
+ if directives.include?("b")
97
+ replacements.merge!(localized_tokens(abbr_month_names_key(format), Date::ABBR_MONTHNAMES))
98
+ end
99
+
100
+ replacements.merge!(localized_tokens(day_names_key(format), Date::DAYNAMES)) if directives.include?("A")
101
+
102
+ replacements.merge!(localized_tokens(abbr_day_names_key(format), Date::ABBR_DAYNAMES)) if directives.include?("a")
103
+
104
+ replacements
105
+ end
106
+
107
+ # @private
108
+ def localized_tokens(key, english_names)
109
+ localized_names = I18n.t(key, locale: Russian::LOCALE).compact
110
+ english_names = english_names.compact
111
+
112
+ localized_names.zip(english_names).each_with_object({}) do |(localized_name, english_name), replacements|
113
+ replacements[localized_name.downcase] = english_name
114
+ end
115
+ end
116
+
117
+ # @private
118
+ def directives_in(format)
119
+ directives = []
120
+ index = 0
121
+
122
+ while index < format.length
123
+ if format[index] != "%"
124
+ index += 1
125
+ next
126
+ end
127
+
128
+ if format[index + 1] == "%"
129
+ index += 2
130
+ next
131
+ end
132
+
133
+ index += 1
134
+ index += 1 while index < format.length && Russian::STRPTIME_DIRECTIVE_MODIFIERS.include?(format[index])
135
+ index += 1 while index < format.length && format[index].match?(/\d/)
136
+
137
+ index += 1 if index < format.length && %w[E O].include?(format[index])
138
+
139
+ directives << format[index] if index < format.length
140
+ index += 1
141
+ end
142
+
143
+ directives
144
+ end
145
+
146
+ # @private
147
+ def month_names_key(format)
148
+ Russian::LOCALIZE_MONTH_NAMES_MATCH.match?(format) ? :"date.common_month_names" : :"date.standalone_month_names"
149
+ end
150
+
151
+ # @private
152
+ def abbr_month_names_key(format)
153
+ Russian::LOCALIZE_ABBR_MONTH_NAMES_MATCH.match?(format) ? :"date.common_abbr_month_names" : :"date.standalone_abbr_month_names"
154
+ end
155
+
156
+ # @private
157
+ def day_names_key(format)
158
+ Russian::LOCALIZE_STANDALONE_DAY_NAMES_MATCH.match?(format) ? :"date.standalone_day_names" : :"date.common_day_names"
159
+ end
160
+
161
+ # @private
162
+ def abbr_day_names_key(format)
163
+ Russian::LOCALIZE_STANDALONE_ABBR_DAY_NAMES_MATCH.match?(format) ? :"date.common_abbr_day_names" : :"date.standalone_abbr_day_names"
164
+ end
165
+ end
166
+ end
@@ -1,72 +1,106 @@
1
- # -*- encoding: utf-8 -*-
1
+ # frozen_string_literal: true
2
2
 
3
3
  module Russian
4
- # Russian transliteration
4
+ # Russian transliteration tables and helpers.
5
5
  #
6
- # Транслитерация для букв русского алфавита
6
+ # Transliteration is heavily based on the
7
+ # [rutils](https://github.com/julik/rutils) gem by
8
+ # Julian "julik" Tarkhanov and contributors, then cleaned up and
9
+ # adapted for this gem.
10
+ #
11
+ #
12
+ # Таблицы и хелперы для русской транслитерации.
13
+ #
14
+ # Транслитерация во многом основана на gem
15
+ # [rutils](https://github.com/julik/rutils) Юлика Тарханова и
16
+ # соавторов, а затем упрощена и адаптирована для этого gem'а.
17
+ #
18
+ # @example
19
+ # Russian::Transliteration.transliterate("Юлия")
20
+ # # => "Yuliya"
21
+ #
22
+ # Russian::Transliteration.transliterate("Н.П. Шерстяков")
23
+ # # => "N.P. Sherstyakov"
7
24
  module Transliteration
8
- extend self
9
-
10
- # Transliteration heavily based on rutils gem by Julian "julik" Tarkhanov and Co.
11
- # <http://rutils.rubyforge.org/>
12
- # Cleaned up and optimized.
25
+ module_function
13
26
 
27
+ # @private
14
28
  LOWER_SINGLE = {
15
- "і"=>"i","ґ"=>"g","ё"=>"yo","№"=>"#","є"=>"e",
16
- "ї"=>"yi","а"=>"a","б"=>"b",
17
- "в"=>"v","г"=>"g","д"=>"d","е"=>"e","ж"=>"zh",
18
- "з"=>"z","и"=>"i","й"=>"y","к"=>"k","л"=>"l",
19
- "м"=>"m","н"=>"n","о"=>"o","п"=>"p","р"=>"r",
20
- "с"=>"s","т"=>"t","у"=>"u","ф"=>"f","х"=>"h",
21
- "ц"=>"ts","ч"=>"ch","ш"=>"sh","щ"=>"sch","ъ"=>"'",
22
- "ы"=>"y","ь"=>"","э"=>"e","ю"=>"yu","я"=>"ya",
23
- }
29
+ "і" => "i", "ґ" => "g", "ё" => "yo", "№" => "#", "є" => "e",
30
+ "ї" => "yi", "а" => "a", "б" => "b",
31
+ "в" => "v", "г" => "g", "д" => "d", "е" => "e", "ж" => "zh",
32
+ "з" => "z", "и" => "i", "й" => "y", "к" => "k", "л" => "l",
33
+ "м" => "m", "н" => "n", "о" => "o", "п" => "p", "р" => "r",
34
+ "с" => "s", "т" => "t", "у" => "u", "ф" => "f", "х" => "h",
35
+ "ц" => "ts", "ч" => "ch", "ш" => "sh", "щ" => "sch", "ъ" => "'",
36
+ "ы" => "y", "ь" => "", "э" => "e", "ю" => "yu", "я" => "ya"
37
+ }.freeze
38
+ # @private
24
39
  LOWER_MULTI = {
25
- "ье"=>"ie",
26
- "ьё"=>"ie",
27
- }
40
+ "ье" => "ie",
41
+ "ьё" => "ie"
42
+ }.freeze
28
43
 
44
+ # @private
29
45
  UPPER_SINGLE = {
30
- "Ґ"=>"G","Ё"=>"YO","Є"=>"E","Ї"=>"YI","І"=>"I",
31
- "А"=>"A","Б"=>"B","В"=>"V","Г"=>"G",
32
- "Д"=>"D","Е"=>"E","Ж"=>"ZH","З"=>"Z","И"=>"I",
33
- "Й"=>"Y","К"=>"K","Л"=>"L","М"=>"M","Н"=>"N",
34
- "О"=>"O","П"=>"P","Р"=>"R","С"=>"S","Т"=>"T",
35
- "У"=>"U","Ф"=>"F","Х"=>"H","Ц"=>"TS","Ч"=>"CH",
36
- "Ш"=>"SH","Щ"=>"SCH","Ъ"=>"'","Ы"=>"Y","Ь"=>"",
37
- "Э"=>"E","Ю"=>"YU","Я"=>"YA",
38
- }
46
+ "Ґ" => "G", "Ё" => "YO", "Є" => "E", "Ї" => "YI", "І" => "I",
47
+ "А" => "A", "Б" => "B", "В" => "V", "Г" => "G",
48
+ "Д" => "D", "Е" => "E", "Ж" => "ZH", "З" => "Z", "И" => "I",
49
+ "Й" => "Y", "К" => "K", "Л" => "L", "М" => "M", "Н" => "N",
50
+ "О" => "O", "П" => "P", "Р" => "R", "С" => "S", "Т" => "T",
51
+ "У" => "U", "Ф" => "F", "Х" => "H", "Ц" => "TS", "Ч" => "CH",
52
+ "Ш" => "SH", "Щ" => "SCH", "Ъ" => "'", "Ы" => "Y", "Ь" => "",
53
+ "Э" => "E", "Ю" => "YU", "Я" => "YA"
54
+ }.freeze
55
+ # @private
39
56
  UPPER_MULTI = {
40
- "ЬЕ"=>"IE",
41
- "ЬЁ"=>"IE",
42
- }
57
+ "ЬЕ" => "IE",
58
+ "ЬЁ" => "IE"
59
+ }.freeze
43
60
 
44
- LOWER = (LOWER_SINGLE.merge(LOWER_MULTI)).freeze
45
- UPPER = (UPPER_SINGLE.merge(UPPER_MULTI)).freeze
46
- MULTI_KEYS = (LOWER_MULTI.merge(UPPER_MULTI)).keys.sort_by {|s| s.length}.reverse.freeze
61
+ # @private
62
+ LOWER = LOWER_SINGLE.merge(LOWER_MULTI).freeze
63
+ # @private
64
+ UPPER = UPPER_SINGLE.merge(UPPER_MULTI).freeze
65
+ # @private
66
+ TITLE = UPPER.transform_values(&:capitalize).freeze
67
+ # @private
68
+ MULTI_KEYS = LOWER_MULTI.merge(UPPER_MULTI).keys.sort_by(&:length).reverse.freeze
69
+ # @private
70
+ TOKEN_RE = /#{Regexp.union(MULTI_KEYS).source}|./m
47
71
 
48
- # Transliterate a string with russian characters
72
+ # Transliterates a string containing Cyrillic characters.
73
+ #
74
+ # The method preserves non-Cyrillic characters and follows the historical
75
+ # casing rules of the gem.
76
+ #
77
+ #
78
+ # Транслитерирует строку, содержащую кириллические символы.
79
+ #
80
+ # Метод сохраняет некириллические символы и следует историческим правилам
81
+ # gem'а для регистра.
82
+ #
83
+ # @param str [String] String to transliterate.
84
+ # Строка для транслитерации.
85
+ # @return [String] Transliteration result.
86
+ # Результат транслитерации.
49
87
  #
50
- # Возвращает строку, в которой все буквы русского алфавита заменены на похожую по звучанию латиницу
88
+ # @example
89
+ # Russian::Transliteration.transliterate("Привет, мир!")
90
+ # # => "Privet, mir!"
51
91
  def transliterate(str)
52
- chars = str.scan(%r{#{MULTI_KEYS.join '|'}|\w|.})
92
+ tokens = str.scan(TOKEN_RE)
53
93
 
54
- result = ""
94
+ tokens.each_with_index.map do |token, index|
95
+ lower = LOWER[token]
96
+ next lower if lower
55
97
 
56
- chars.each_with_index do |char, index|
57
- if UPPER.has_key?(char) && LOWER.has_key?(chars[index+1])
58
- # combined case
59
- result << UPPER[char].downcase.capitalize
60
- elsif UPPER.has_key?(char)
61
- result << UPPER[char]
62
- elsif LOWER.has_key?(char)
63
- result << LOWER[char]
64
- else
65
- result << char
66
- end
67
- end
98
+ upper = UPPER[token]
99
+ next TITLE[token] if upper && LOWER[tokens[index + 1]]
100
+ next upper if upper
68
101
 
69
- result
102
+ token
103
+ end.join
70
104
  end
71
105
  end
72
- end
106
+ end