diff options
| -rw-r--r-- | app/helpers/nodes_helper.rb | 16 | ||||
| -rw-r--r-- | app/models/concerns/rrule_humanizer.rb | 82 | ||||
| -rw-r--r-- | app/models/event.rb | 1 | ||||
| -rw-r--r-- | app/views/nodes/show.html.erb | 14 | ||||
| -rw-r--r-- | config/locales/de.yml | 3 | ||||
| -rw-r--r-- | config/locales/en.yml | 3 | ||||
| -rw-r--r-- | test/models/concerns/rrule_humanizer_test.rb | 84 |
7 files changed, 202 insertions, 1 deletions
diff --git a/app/helpers/nodes_helper.rb b/app/helpers/nodes_helper.rb index 4293628..329bcc5 100644 --- a/app/helpers/nodes_helper.rb +++ b/app/helpers/nodes_helper.rb | |||
| @@ -43,4 +43,20 @@ module NodesHelper | |||
| 43 | link_to('add event', new_event_path(:node_id => @node.id)) | 43 | link_to('add event', new_event_path(:node_id => @node.id)) |
| 44 | ]) | 44 | ]) |
| 45 | end | 45 | end |
| 46 | |||
| 47 | def event_schedule_text(event) | ||
| 48 | if event.rrule.present? | ||
| 49 | recurrence = event.humanize_rrule(I18n.locale) | ||
| 50 | if recurrence | ||
| 51 | time = event.start_time&.strftime("%H:%M") | ||
| 52 | time ? "#{recurrence} #{t(:event_schedule_time, time: time)}" : recurrence | ||
| 53 | else | ||
| 54 | "#{event.rrule} (#{t(:event_schedule_unrecognized)})" | ||
| 55 | end | ||
| 56 | elsif event.start_time | ||
| 57 | I18n.l(event.start_time, format: :long) | ||
| 58 | else | ||
| 59 | t(:event_schedule_none) | ||
| 60 | end | ||
| 61 | end | ||
| 46 | end | 62 | end |
diff --git a/app/models/concerns/rrule_humanizer.rb b/app/models/concerns/rrule_humanizer.rb new file mode 100644 index 0000000..6cee711 --- /dev/null +++ b/app/models/concerns/rrule_humanizer.rb | |||
| @@ -0,0 +1,82 @@ | |||
| 1 | module RruleHumanizer | ||
| 2 | extend ActiveSupport::Concern | ||
| 3 | |||
| 4 | WEEKDAY_NAMES = { | ||
| 5 | de: { "MO"=>"Montag","TU"=>"Dienstag","WE"=>"Mittwoch","TH"=>"Donnerstag","FR"=>"Freitag","SA"=>"Samstag","SU"=>"Sonntag" }, | ||
| 6 | en: { "MO"=>"Monday","TU"=>"Tuesday","WE"=>"Wednesday","TH"=>"Thursday","FR"=>"Friday","SA"=>"Saturday","SU"=>"Sunday" } | ||
| 7 | }.freeze | ||
| 8 | |||
| 9 | WEEKDAY_NAMES_ADVERBIAL = { | ||
| 10 | de: { "MO"=>"montags","TU"=>"dienstags","WE"=>"mittwochs","TH"=>"donnerstags","FR"=>"freitags","SA"=>"samstags","SU"=>"sonntags" } | ||
| 11 | }.freeze | ||
| 12 | |||
| 13 | ORDINAL_NAMES = { | ||
| 14 | de: { 1=>"ersten", 2=>"zweiten", 3=>"dritten", 4=>"vierten", -1=>"letzten", -2=>"vorletzten" }, | ||
| 15 | en: { 1=>"first", 2=>"second", 3=>"third", 4=>"fourth", -1=>"last", -2=>"second-to-last" } | ||
| 16 | }.freeze | ||
| 17 | |||
| 18 | MONTH_NAMES = { | ||
| 19 | de: %w[Januar Februar März April Mai Juni Juli August September Oktober November Dezember], | ||
| 20 | en: %w[January February March April May June July August September October November December] | ||
| 21 | }.freeze | ||
| 22 | |||
| 23 | def humanize_rrule(locale = I18n.locale) | ||
| 24 | return nil if rrule.blank? | ||
| 25 | parts = Hash[rrule.split(";").map { |p| p.split("=", 2) }] | ||
| 26 | return nil if parts["COUNT"] || parts["UNTIL"] # old one-off data, don't guess | ||
| 27 | |||
| 28 | freq, interval, byday, bymonth = parts["FREQ"], parts["INTERVAL"].to_i, parts["BYDAY"], parts["BYMONTH"] | ||
| 29 | loc = locale.to_sym | ||
| 30 | weekdays = WEEKDAY_NAMES[loc] || WEEKDAY_NAMES[:en] | ||
| 31 | ordinals = ORDINAL_NAMES[loc] || ORDINAL_NAMES[:en] | ||
| 32 | months = MONTH_NAMES[loc] || MONTH_NAMES[:en] | ||
| 33 | |||
| 34 | days = byday&.split(",")&.map do |d| | ||
| 35 | if d =~ /^(-?\d+)([A-Z]{2})$/ | ||
| 36 | "#{ordinals[$1.to_i]} #{weekdays[$2]}" | ||
| 37 | else | ||
| 38 | weekdays[d] | ||
| 39 | end | ||
| 40 | end | ||
| 41 | |||
| 42 | base = | ||
| 43 | case loc | ||
| 44 | when :de | ||
| 45 | case freq | ||
| 46 | when "WEEKLY" | ||
| 47 | if days | ||
| 48 | if interval == 2 | ||
| 49 | adverbial = byday.split(",").map { |d| WEEKDAY_NAMES_ADVERBIAL[:de][d] } | ||
| 50 | "Alle zwei Wochen #{adverbial.join(' und ')}" | ||
| 51 | else | ||
| 52 | "Jeden #{days.join(' und ')}" | ||
| 53 | end | ||
| 54 | else | ||
| 55 | interval == 2 ? "Alle zwei Wochen" : "Wöchentlich" | ||
| 56 | end | ||
| 57 | when "MONTHLY" | ||
| 58 | days ? "Jeden #{days.join(' und ')} im Monat" : "Monatlich" | ||
| 59 | end | ||
| 60 | else | ||
| 61 | case freq | ||
| 62 | when "WEEKLY" | ||
| 63 | days ? "#{interval == 2 ? 'Every other' : 'Every'} #{days.join(' and ')}" : (interval == 2 ? "Every other week" : "Weekly") | ||
| 64 | when "MONTHLY" | ||
| 65 | days ? "Every #{days.join(' and ')} of the month" : "Monthly" | ||
| 66 | end | ||
| 67 | end | ||
| 68 | return nil unless base | ||
| 69 | |||
| 70 | if bymonth | ||
| 71 | included = bymonth.split(",").map(&:to_i) | ||
| 72 | missing = ((1..12).to_a - included) | ||
| 73 | if missing.size == 1 | ||
| 74 | excluded_name = months[missing.first - 1] | ||
| 75 | base += (loc == :de ? ", außer im #{excluded_name}" : ", except in #{excluded_name}") | ||
| 76 | end | ||
| 77 | # more than one missing month: bymonth pattern more complex than we handle, leave base as-is silently | ||
| 78 | end | ||
| 79 | |||
| 80 | base | ||
| 81 | end | ||
| 82 | end | ||
diff --git a/app/models/event.rb b/app/models/event.rb index 26c79e4..de82674 100644 --- a/app/models/event.rb +++ b/app/models/event.rb | |||
| @@ -1,4 +1,5 @@ | |||
| 1 | class Event < ApplicationRecord | 1 | class Event < ApplicationRecord |
| 2 | include RruleHumanizer | ||
| 2 | 3 | ||
| 3 | belongs_to :node, optional: true | 4 | belongs_to :node, optional: true |
| 4 | has_many :occurrences | 5 | has_many :occurrences |
diff --git a/app/views/nodes/show.html.erb b/app/views/nodes/show.html.erb index de4d8a2..7223219 100644 --- a/app/views/nodes/show.html.erb +++ b/app/views/nodes/show.html.erb | |||
| @@ -42,6 +42,18 @@ | |||
| 42 | <td class="description">Tagged with:</td> | 42 | <td class="description">Tagged with:</td> |
| 43 | <td><%= @page.tag_list %></td> | 43 | <td><%= @page.tag_list %></td> |
| 44 | </tr> | 44 | </tr> |
| 45 | <% if @node.events.any? %> | ||
| 46 | <tr> | ||
| 47 | <td class="description">Events</td> | ||
| 48 | <td> | ||
| 49 | <ul> | ||
| 50 | <% @node.events.order(:start_time).each do |event| %> | ||
| 51 | <li><%= event_schedule_text(event) %><%= " (primary)" if event.is_primary? %></li> | ||
| 52 | <% end %> | ||
| 53 | </ul> | ||
| 54 | </td> | ||
| 55 | </tr> | ||
| 56 | <% end %> | ||
| 45 | <tr> | 57 | <tr> |
| 46 | <td class="description"><strong>Title</strong></td> | 58 | <td class="description"><strong>Title</strong></td> |
| 47 | <td><%= sanitize( @page.title ) %></td> | 59 | <td><%= sanitize( @page.title ) %></td> |
| @@ -59,4 +71,4 @@ | |||
| 59 | <td class="right"></td> | 71 | <td class="right"></td> |
| 60 | </tr> | 72 | </tr> |
| 61 | </table> | 73 | </table> |
| 62 | </div> \ No newline at end of file | 74 | </div> |
diff --git a/config/locales/de.yml b/config/locales/de.yml index 5f77d79..0b42dd3 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml | |||
| @@ -7,6 +7,9 @@ de: | |||
| 7 | sponsors: Sponsoren | 7 | sponsors: Sponsoren |
| 8 | show_tag_headline: "Seiten mit dem Tag:" | 8 | show_tag_headline: "Seiten mit dem Tag:" |
| 9 | old_ccc_de: das alte ccc.de | 9 | old_ccc_de: das alte ccc.de |
| 10 | event_schedule_time: "um %{time} Uhr" | ||
| 11 | event_schedule_unrecognized: "Termin-Regel nicht automatisch lesbar" | ||
| 12 | event_schedule_none: "Kein Termin" | ||
| 10 | date: | 13 | date: |
| 11 | formats: | 14 | formats: |
| 12 | default: "%d.%m.%Y" | 15 | default: "%d.%m.%Y" |
diff --git a/config/locales/en.yml b/config/locales/en.yml index 2458d4d..93a0d55 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml | |||
| @@ -8,6 +8,9 @@ en: | |||
| 8 | hello: "Hello world" | 8 | hello: "Hello world" |
| 9 | show_tag_headline: "Pages tagged with:" | 9 | show_tag_headline: "Pages tagged with:" |
| 10 | old_ccc_de: the old ccc.de | 10 | old_ccc_de: the old ccc.de |
| 11 | event_schedule_time: "at %{time}" | ||
| 12 | event_schedule_unrecognized: "Schedule not automatically readable" | ||
| 13 | event_schedule_none: "No schedule" | ||
| 11 | 14 | ||
| 12 | time: | 15 | time: |
| 13 | formats: | 16 | formats: |
diff --git a/test/models/concerns/rrule_humanizer_test.rb b/test/models/concerns/rrule_humanizer_test.rb new file mode 100644 index 0000000..500dbc7 --- /dev/null +++ b/test/models/concerns/rrule_humanizer_test.rb | |||
| @@ -0,0 +1,84 @@ | |||
| 1 | require 'test_helper' | ||
| 2 | |||
| 3 | class RruleHumanizerTest < ActiveSupport::TestCase | ||
| 4 | def humanize(rrule, locale = :de) | ||
| 5 | Event.new(rrule: rrule).humanize_rrule(locale) | ||
| 6 | end | ||
| 7 | |||
| 8 | test "weekly single day" do | ||
| 9 | assert_equal "Jeden Dienstag", humanize("FREQ=WEEKLY;BYDAY=TU") | ||
| 10 | assert_equal "Every Tuesday", humanize("FREQ=WEEKLY;BYDAY=TU", :en) | ||
| 11 | end | ||
| 12 | |||
| 13 | test "weekly two days" do | ||
| 14 | assert_equal "Jeden Mittwoch und Freitag", humanize("FREQ=WEEKLY;BYDAY=WE,FR") | ||
| 15 | assert_equal "Every Wednesday and Friday", humanize("FREQ=WEEKLY;BYDAY=WE,FR", :en) | ||
| 16 | end | ||
| 17 | |||
| 18 | test "weekly no byday" do | ||
| 19 | assert_equal "Wöchentlich", humanize("FREQ=WEEKLY") | ||
| 20 | assert_equal "Weekly", humanize("FREQ=WEEKLY", :en) | ||
| 21 | end | ||
| 22 | |||
| 23 | test "biweekly with day" do | ||
| 24 | assert_equal "Alle zwei Wochen donnerstags", humanize("FREQ=WEEKLY;INTERVAL=2;BYDAY=TH") | ||
| 25 | assert_equal "Every other Thursday", humanize("FREQ=WEEKLY;INTERVAL=2;BYDAY=TH", :en) | ||
| 26 | end | ||
| 27 | |||
| 28 | test "biweekly no day" do | ||
| 29 | assert_equal "Alle zwei Wochen", humanize("FREQ=WEEKLY;INTERVAL=2") | ||
| 30 | assert_equal "Every other week", humanize("FREQ=WEEKLY;INTERVAL=2", :en) | ||
| 31 | end | ||
| 32 | |||
| 33 | test "monthly nth weekday" do | ||
| 34 | assert_equal "Jeden ersten Dienstag im Monat", humanize("FREQ=MONTHLY;BYDAY=1TU") | ||
| 35 | assert_equal "Jeden zweiten Freitag im Monat", humanize("FREQ=MONTHLY;BYDAY=2FR") | ||
| 36 | assert_equal "Jeden dritten Sonntag im Monat", humanize("FREQ=MONTHLY;BYDAY=3SU") | ||
| 37 | assert_equal "Jeden letzten Mittwoch im Monat", humanize("FREQ=MONTHLY;BYDAY=-1WE") | ||
| 38 | end | ||
| 39 | |||
| 40 | test "monthly nth weekday english" do | ||
| 41 | assert_equal "Every first Tuesday of the month", humanize("FREQ=MONTHLY;BYDAY=1TU", :en) | ||
| 42 | assert_equal "Every last Wednesday of the month", humanize("FREQ=MONTHLY;BYDAY=-1WE", :en) | ||
| 43 | end | ||
| 44 | |||
| 45 | test "monthly second-to-last" do | ||
| 46 | assert_equal "Jeden vorletzten Donnerstag im Monat", humanize("FREQ=MONTHLY;BYDAY=-2TH") | ||
| 47 | assert_equal "Every second-to-last Thursday of the month", humanize("FREQ=MONTHLY;BYDAY=-2TH", :en) | ||
| 48 | end | ||
| 49 | |||
| 50 | test "monthly no byday" do | ||
| 51 | assert_equal "Monatlich", humanize("FREQ=MONTHLY") | ||
| 52 | assert_equal "Monthly", humanize("FREQ=MONTHLY", :en) | ||
| 53 | end | ||
| 54 | |||
| 55 | test "monthly with single excluded month" do | ||
| 56 | assert_equal "Jeden letzten Donnerstag im Monat, außer im Dezember", | ||
| 57 | humanize("FREQ=MONTHLY;BYDAY=-1TH;BYMONTH=1,2,3,4,5,6,7,8,9,10,11") | ||
| 58 | assert_equal "Every last Thursday of the month, except in December", | ||
| 59 | humanize("FREQ=MONTHLY;BYDAY=-1TH;BYMONTH=1,2,3,4,5,6,7,8,9,10,11", :en) | ||
| 60 | end | ||
| 61 | |||
| 62 | test "monthly excluding january" do | ||
| 63 | assert_equal "Jeden zweiten Mittwoch im Monat, außer im Januar", | ||
| 64 | humanize("FREQ=MONTHLY;BYMONTH=2,3,4,5,6,7,8,9,10,11,12;BYDAY=2WE") | ||
| 65 | end | ||
| 66 | |||
| 67 | test "blank rrule returns nil" do | ||
| 68 | assert_nil humanize(nil) | ||
| 69 | assert_nil humanize("") | ||
| 70 | end | ||
| 71 | |||
| 72 | test "count and until are not guessed at" do | ||
| 73 | assert_nil humanize("FREQ=MONTHLY;BYDAY=1WE;COUNT=36") | ||
| 74 | assert_nil humanize("FREQ=MONTHLY;BYDAY=1WE;UNTIL=20050105T222222Z") | ||
| 75 | end | ||
| 76 | |||
| 77 | test "unrecognized freq returns nil" do | ||
| 78 | assert_nil humanize("FREQ=YEARLY;BYMONTH=12") | ||
| 79 | end | ||
| 80 | |||
| 81 | test "falls back to english for unknown locale" do | ||
| 82 | assert_equal "Every Tuesday", humanize("FREQ=WEEKLY;BYDAY=TU", :fr) | ||
| 83 | end | ||
| 84 | end | ||
