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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +203 -0
- data/Gemfile +5 -6
- data/LICENSE +2 -2
- data/README.md +327 -0
- data/Rakefile +15 -8
- data/lib/russian/action_view_ext/helpers/date_helper.rb +136 -101
- data/lib/russian/active_model_ext/custom_error_message.rb +40 -59
- data/lib/russian/locale/actionview.yml +101 -84
- data/lib/russian/locale/activemodel.yml +28 -21
- data/lib/russian/locale/activerecord.yml +57 -52
- data/lib/russian/locale/activesupport.yml +5 -8
- data/lib/russian/locale/datetime.rb +34 -24
- data/lib/russian/locale/datetime.yml +85 -22
- data/lib/russian/locale/pluralization.rb +32 -11
- data/lib/russian/locale/transliterator.rb +10 -9
- data/lib/russian/russian_rails.rb +97 -6
- data/lib/russian/strptime.rb +166 -0
- data/lib/russian/transliteration.rb +87 -53
- data/lib/russian/version.rb +27 -5
- data/lib/russian.rb +378 -78
- data/russian.gemspec +37 -20
- data/sig/russian.rbs +46 -0
- metadata +163 -53
- data/CHANGELOG +0 -132
- data/README.textile +0 -295
- data/TODO +0 -4
- data/spec/fixtures/en.yml +0 -4
- data/spec/fixtures/ru.yml +0 -4
- data/spec/i18n/locale/datetime_spec.rb +0 -99
- data/spec/i18n/locale/pluralization_spec.rb +0 -28
- data/spec/locale_spec.rb +0 -47
- data/spec/russian_spec.rb +0 -118
- data/spec/spec_helper.rb +0 -7
- data/spec/transliteration_spec.rb +0 -51
|
@@ -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
|
-
|
|
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
|
-
#
|
|
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
|
-
|
|
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
|
-
#
|
|
1
|
+
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
#
|
|
3
|
+
# Russian language pluralization rules, taken from CLDR project,
|
|
4
|
+
# http://unicode.org/cldr/
|
|
4
5
|
#
|
|
5
|
-
#
|
|
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
|
-
:
|
|
20
|
-
:
|
|
21
|
-
:
|
|
22
|
-
:
|
|
23
|
-
|
|
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
|
-
#
|
|
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
|
-
#
|
|
6
|
+
#
|
|
7
|
+
# Правило транслитерации для I18n использует `Russian::Transliteration`
|
|
7
8
|
# (использовать обычный механизм и таблицу транслитерации I18n с
|
|
8
|
-
# русским языком не получится)
|
|
9
|
+
# русским языком не получится).
|
|
9
10
|
{
|
|
10
|
-
:
|
|
11
|
-
:
|
|
12
|
-
:
|
|
13
|
-
:
|
|
11
|
+
ru: {
|
|
12
|
+
i18n: {
|
|
13
|
+
transliterate: {
|
|
14
|
+
rule: ->(str) { Russian.transliterate(str) }
|
|
14
15
|
}
|
|
15
16
|
}
|
|
16
17
|
}
|
|
@@ -1,8 +1,99 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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
|
-
|
|
7
|
-
|
|
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
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
#
|
|
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
|
-
|
|
92
|
+
tokens = str.scan(TOKEN_RE)
|
|
53
93
|
|
|
54
|
-
|
|
94
|
+
tokens.each_with_index.map do |token, index|
|
|
95
|
+
lower = LOWER[token]
|
|
96
|
+
next lower if lower
|
|
55
97
|
|
|
56
|
-
|
|
57
|
-
if
|
|
58
|
-
|
|
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
|
-
|
|
102
|
+
token
|
|
103
|
+
end.join
|
|
70
104
|
end
|
|
71
105
|
end
|
|
72
|
-
end
|
|
106
|
+
end
|