diff --git a/.github/labeler.yml b/.github/labeler.yml
index a37a4a806d4..bd86fe18f50 100644
--- a/.github/labeler.yml
+++ b/.github/labeler.yml
@@ -106,6 +106,10 @@ discourse-gamification:
- changed-files:
- any-glob-to-any-file: plugins/discourse-gamification/**/*
+discourse-calendar:
+ - changed-files:
+ - any-glob-to-any-file: plugins/discourse-calendar/**/*
+
footnote:
- changed-files:
- any-glob-to-any-file: plugins/footnote/**/*
diff --git a/.gitignore b/.gitignore
index fda4321d2c8..57770038dd3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -69,6 +69,7 @@
!/plugins/discourse-subscriptions
!/plugins/discourse-hcaptcha
!/plugins/discourse-gamification
+!/plugins/discourse-calendar
/plugins/*/auto_generated
/spec/fixtures/plugins/my_plugin/auto_generated
diff --git a/.streerc b/.streerc
index 5c477379e17..a286bcf70a6 100644
--- a/.streerc
+++ b/.streerc
@@ -1,2 +1,3 @@
--print-width=100
--plugins=plugin/trailing_comma,plugin/disable_auto_ternary
+--ignore-files=plugins/discourse-calendar/vendor/*
diff --git a/plugins/discourse-calendar/.prettierignore b/plugins/discourse-calendar/.prettierignore
new file mode 100644
index 00000000000..f8f98305742
--- /dev/null
+++ b/plugins/discourse-calendar/.prettierignore
@@ -0,0 +1,2 @@
+assets/stylesheets/vendor/*.scss
+public/
diff --git a/plugins/discourse-calendar/README.md b/plugins/discourse-calendar/README.md
new file mode 100644
index 00000000000..e5e5819a166
--- /dev/null
+++ b/plugins/discourse-calendar/README.md
@@ -0,0 +1,42 @@
+# Discourse Calendar
+
+Adds the ability to create a dynamic calendar in the first post of a topic.
+
+Topic discussing the plugin itself can be found here: [https://meta.discourse.org/t/discourse-calendar/97376](https://meta.discourse.org/t/discourse-calendar/97376)
+
+## Customization
+
+### Events
+
+- `discourse_post_event_event_will_start` this DiscourseEvent will be triggered one hour before an event starts
+- `discourse_post_event_event_started` this DiscourseEvent will be triggered when an event starts
+- `discourse_post_event_event_ended` this DiscourseEvent will be triggered when an event ends
+
+### Custom Fields
+
+Custom fields can be set in plugin settings. Once added a new form will appear on event UI.
+These custom fields are available when a plugin event is triggered.
+
+### Holidays
+
+See an incorrect or missing holiday? Familiarize yourself with the [holiday definition Syntax](vendor/holidays/definitions/doc/SYNTAX.md). Then make your updates in the `vendor/holiday/definitions` directory.
+
+Generate updated holidays as follows.
+
+```sh
+cd vendor/holidays
+
+# Generate holiday definitions
+rake generate:definitions
+```
+
+Install the plugin and switch to the discourse root(not the plugin directory).
+
+```sh
+# Collect all holiday regions into assets/javascripts/lib/regions.js
+bin/rails javascript:update_constants
+```
+
+### Interactions with Other Plugins
+
+You can use an element of this plugin with the [Right Sidebar Blocks](https://github.com/discourse/discourse-right-sidebar-blocks) component. You'll want to ensure the desired route is enabled via the `events calendar categories` setting. In Right Sidebar Block's settings, the block name will be `upcoming-events-list`, and the params use this [syntax](https://momentjs.com/docs/#/displaying/format/), for example `MMMM D, YYYY`.
diff --git a/plugins/discourse-calendar/app/controllers/admin/discourse_calendar/admin_holidays_controller.rb b/plugins/discourse-calendar/app/controllers/admin/discourse_calendar/admin_holidays_controller.rb
new file mode 100644
index 00000000000..c153c123aa1
--- /dev/null
+++ b/plugins/discourse-calendar/app/controllers/admin/discourse_calendar/admin_holidays_controller.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+module Admin::DiscourseCalendar
+ class AdminHolidaysController < Admin::AdminController
+ requires_plugin DiscourseCalendar::PLUGIN_NAME
+
+ def index
+ region_code = params[:region_code]
+
+ begin
+ holidays = DiscourseCalendar::Holiday.find_holidays_for(region_code: region_code)
+ rescue Holidays::InvalidRegion
+ return(
+ render_json_error(
+ I18n.t("system_messages.discourse_calendar_holiday_region_invalid"),
+ 422,
+ )
+ )
+ end
+
+ render json: { region_code: region_code, holidays: holidays }
+ end
+
+ def disable
+ DiscourseCalendar::DisabledHoliday.create!(disabled_holiday_params)
+ CalendarEvent.destroy_by(
+ description: disabled_holiday_params[:holiday_name],
+ region: disabled_holiday_params[:region_code],
+ )
+
+ render json: success_json
+ end
+
+ def enable
+ if DiscourseCalendar::DisabledHoliday.destroy_by(disabled_holiday_params).present?
+ render json: success_json
+ else
+ render_json_error(I18n.t("system_messages.discourse_calendar_enable_holiday_failed"), 422)
+ end
+ end
+
+ private
+
+ def disabled_holiday_params
+ params.require(:disabled_holiday).permit(:holiday_name, :region_code)
+ end
+ end
+end
diff --git a/plugins/discourse-calendar/app/controllers/discourse_post_event/discourse_post_event_controller.rb b/plugins/discourse-calendar/app/controllers/discourse_post_event/discourse_post_event_controller.rb
new file mode 100644
index 00000000000..ab6efc05e73
--- /dev/null
+++ b/plugins/discourse-calendar/app/controllers/discourse_post_event/discourse_post_event_controller.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module DiscoursePostEvent
+ class DiscoursePostEventController < ::ApplicationController
+ requires_plugin DiscourseCalendar::PLUGIN_NAME
+ before_action :ensure_discourse_post_event_enabled
+
+ private
+
+ def ensure_discourse_post_event_enabled
+ raise Discourse::NotFound if !SiteSetting.discourse_post_event_enabled
+ end
+ end
+end
diff --git a/plugins/discourse-calendar/app/controllers/discourse_post_event/events_controller.rb b/plugins/discourse-calendar/app/controllers/discourse_post_event/events_controller.rb
new file mode 100644
index 00000000000..a54b0dd3cd0
--- /dev/null
+++ b/plugins/discourse-calendar/app/controllers/discourse_post_event/events_controller.rb
@@ -0,0 +1,130 @@
+# frozen_string_literal: true
+
+module DiscoursePostEvent
+ class EventsController < DiscoursePostEventController
+ def index
+ @events =
+ DiscoursePostEvent::EventFinder.search(current_user, filtered_events_params).includes(
+ post: :topic,
+ )
+
+ # The detailed serializer is currently not used anywhere in the frontend, but available via API
+ serializer = params[:include_details] == "true" ? EventSerializer : EventSummarySerializer
+
+ render json:
+ ActiveModel::ArraySerializer.new(
+ @events,
+ each_serializer: serializer,
+ scope: guardian,
+ ).as_json
+ end
+
+ def invite
+ event = Event.find(params[:id])
+ guardian.ensure_can_act_on_discourse_post_event!(event)
+ invites = Array(params.permit(invites: [])[:invites])
+ users = User.real.where(username: invites)
+
+ users.each { |user| event.create_notification!(user, event.post) }
+
+ render json: success_json
+ end
+
+ def show
+ event = Event.find(params[:id])
+ guardian.ensure_can_see!(event.post)
+ serializer = EventSerializer.new(event, scope: guardian)
+ render_json_dump(serializer)
+ end
+
+ def destroy
+ event = Event.find(params[:id])
+ guardian.ensure_can_act_on_discourse_post_event!(event)
+ event.publish_update!
+ event.destroy
+ render json: success_json
+ end
+
+ def csv_bulk_invite
+ require "csv"
+
+ event = Event.find(params[:id])
+ guardian.ensure_can_edit!(event.post)
+ guardian.ensure_can_create_discourse_post_event!
+
+ file = params[:file] || (params[:files] || []).first
+ raise Discourse::InvalidParameters.new(:file) if file.blank?
+
+ hijack do
+ begin
+ invitees = []
+
+ CSV.foreach(file.tempfile) do |row|
+ invitees << { identifier: row[0], attendance: row[1] || "going" } if row[0].present?
+ end
+
+ if invitees.present?
+ Jobs.enqueue(
+ :discourse_post_event_bulk_invite,
+ event_id: event.id,
+ invitees: invitees,
+ current_user_id: current_user.id,
+ )
+ render json: success_json
+ else
+ render json:
+ failed_json.merge(
+ errors: [I18n.t("discourse_post_event.errors.bulk_invite.error")],
+ ),
+ status: 422
+ end
+ rescue StandardError
+ render json:
+ failed_json.merge(
+ errors: [I18n.t("discourse_post_event.errors.bulk_invite.error")],
+ ),
+ status: 422
+ end
+ end
+ end
+
+ def bulk_invite
+ event = Event.find(params[:id])
+ guardian.ensure_can_edit!(event.post)
+ guardian.ensure_can_create_discourse_post_event!
+
+ invitees = Array(params[:invitees]).reject { |x| x.empty? }
+ raise Discourse::InvalidParameters.new(:invitees) if invitees.blank?
+
+ begin
+ Jobs.enqueue(
+ :discourse_post_event_bulk_invite,
+ event_id: event.id,
+ invitees: invitees.as_json,
+ current_user_id: current_user.id,
+ )
+ render json: success_json
+ rescue StandardError
+ render json:
+ failed_json.merge(
+ errors: [I18n.t("discourse_post_event.errors.bulk_invite.error")],
+ ),
+ status: 422
+ end
+ end
+
+ private
+
+ def filtered_events_params
+ params.permit(
+ :post_id,
+ :category_id,
+ :include_subcategories,
+ :include_expired,
+ :limit,
+ :before,
+ :attending_user,
+ )
+ end
+ end
+end
diff --git a/plugins/discourse-calendar/app/controllers/discourse_post_event/invitees_controller.rb b/plugins/discourse-calendar/app/controllers/discourse_post_event/invitees_controller.rb
new file mode 100644
index 00000000000..96bb04da5ce
--- /dev/null
+++ b/plugins/discourse-calendar/app/controllers/discourse_post_event/invitees_controller.rb
@@ -0,0 +1,99 @@
+# frozen_string_literal: true
+
+module DiscoursePostEvent
+ class InviteesController < DiscoursePostEventController
+ def index
+ event = Event.find(params[:post_id])
+ guardian.ensure_can_see!(event.post)
+
+ filter = params[:filter].downcase if params[:filter]
+
+ event_invitees = event.invitees
+ event_invitees = event_invitees.with_status(params[:type].to_sym) if params[:type]
+
+ suggested_users = []
+ if filter.present? && guardian.can_act_on_discourse_post_event?(event)
+ missing_users = event.missing_users(event_invitees.select(:user_id))
+
+ if filter
+ missing_users = missing_users.where("LOWER(username) LIKE :filter", filter: "%#{filter}%")
+
+ custom_order = <<~SQL
+ CASE
+ WHEN LOWER(username) = ? THEN 0
+ ELSE 1
+ END ASC,
+ LOWER(username) ASC
+ SQL
+
+ custom_order = ActiveRecord::Base.sanitize_sql_array([custom_order, filter])
+ missing_users = missing_users.order(custom_order).limit(10)
+ else
+ missing_users = missing_users.order(:username_lower).limit(10)
+ end
+
+ suggested_users = missing_users
+ end
+
+ if filter
+ event_invitees =
+ event_invitees.joins(:user).where(
+ "LOWER(users.username) LIKE :filter",
+ filter: "%#{filter}%",
+ )
+ end
+
+ event_invitees = event_invitees.order(%i[status username_lower]).limit(200)
+
+ render json:
+ InviteeListSerializer.new(invitees: event_invitees, suggested_users: suggested_users)
+ end
+
+ def update
+ invitee = Invitee.find_by(id: params[:invitee_id], post_id: params[:event_id])
+ guardian.ensure_can_act_on_invitee!(invitee)
+ invitee.update_attendance!(invitee_params[:status])
+ render json: InviteeSerializer.new(invitee)
+ end
+
+ def create
+ event = Event.find(params[:event_id])
+ guardian.ensure_can_see!(event.post)
+
+ invitee_params = invitee_params(event)
+
+ user = current_user
+ if user_id = invitee_params[:user_id]
+ user = User.find(user_id.to_i)
+ end
+
+ raise Discourse::InvalidAccess if !event.can_user_update_attendance(user)
+
+ if current_user.id != user.id
+ raise Discourse::InvalidAccess if !guardian.can_act_on_discourse_post_event?(event)
+ end
+
+ invitee = Invitee.create_attendance!(user.id, params[:event_id], invitee_params[:status])
+ render json: InviteeSerializer.new(invitee)
+ end
+
+ def destroy
+ event = Event.find_by(id: params[:post_id])
+ invitee = event.invitees.find_by(id: params[:id])
+ guardian.ensure_can_act_on_invitee!(invitee)
+ invitee.destroy!
+ event.publish_update!
+ render json: success_json
+ end
+
+ private
+
+ def invitee_params(event = nil)
+ if event && guardian.can_act_on_discourse_post_event?(event)
+ params.require(:invitee).permit(:status, :user_id)
+ else
+ params.require(:invitee).permit(:status)
+ end
+ end
+ end
+end
diff --git a/plugins/discourse-calendar/app/controllers/discourse_post_event/upcoming_events_controller.rb b/plugins/discourse-calendar/app/controllers/discourse_post_event/upcoming_events_controller.rb
new file mode 100644
index 00000000000..9c8e972b02d
--- /dev/null
+++ b/plugins/discourse-calendar/app/controllers/discourse_post_event/upcoming_events_controller.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+module DiscoursePostEvent
+ class UpcomingEventsController < DiscoursePostEventController
+ def index
+ end
+ end
+end
diff --git a/plugins/discourse-calendar/app/models/calendar_event.rb b/plugins/discourse-calendar/app/models/calendar_event.rb
new file mode 100644
index 00000000000..e97abc5a47f
--- /dev/null
+++ b/plugins/discourse-calendar/app/models/calendar_event.rb
@@ -0,0 +1,131 @@
+# frozen_string_literal: true
+
+class CalendarEvent < ActiveRecord::Base
+ belongs_to :topic
+ belongs_to :post
+ belongs_to :user
+
+ after_save do
+ if SiteSetting.enable_user_status && is_holiday? && underway?
+ DiscourseCalendar::HolidayStatus.set!(user, ends_at)
+ end
+ end
+
+ after_destroy { DiscourseCalendar::HolidayStatus.clear!(user) if SiteSetting.enable_user_status }
+
+ def ends_at
+ end_date || (start_date + 24.hours)
+ end
+
+ def underway?
+ now = Time.zone.now
+ start_date <= now && now < ends_at
+ end
+
+ def is_holiday?
+ SiteSetting.holiday_calendar_topic_id.to_i == topic_id
+ end
+
+ def in_future?
+ start_date > Time.zone.now
+ end
+
+ def self.update(post)
+ CalendarEvent.where(post_id: post.id).destroy_all
+
+ dates = post.local_dates
+ return if !dates || dates.size < 1 || dates.size > 2
+
+ first_post = post.topic&.first_post
+ return if !first_post || !first_post.custom_fields[DiscourseCalendar::CALENDAR_CUSTOM_FIELD]
+
+ from = self.convert_to_date_time(dates[0])
+ to = self.convert_to_date_time(dates[1]) if dates.size == 2
+
+ adjust_to = !to || !dates[1]["time"]
+ if !to && dates[0]["time"]
+ to = from + 1.hour
+ artificial_to = true
+ end
+
+ if SiteSetting.all_day_event_start_time.present? && SiteSetting.all_day_event_end_time.present?
+ from = from.change(hour_adjustment(SiteSetting.all_day_event_start_time)) if !dates[0]["time"]
+ to = (to || from).change(hour_adjustment(SiteSetting.all_day_event_end_time)) if adjust_to &&
+ !artificial_to
+ end
+
+ doc = Nokogiri::HTML5.fragment(post.cooked)
+ doc.css(".discourse-local-date").each(&:remove)
+ html = doc.to_html.sub(/\s*→\s*/, "")
+
+ description =
+ PrettyText.excerpt(
+ html,
+ 1000,
+ strip_links: true,
+ text_entities: true,
+ keep_emoji_images: true,
+ )
+ recurrence = dates[0]["recurring"].presence
+ timezone = dates[0]["timezone"].presence
+
+ CalendarEvent.create!(
+ topic_id: post.topic_id,
+ post_id: post.id,
+ post_number: post.post_number,
+ user_id: post.user_id,
+ username: post.user.username,
+ description: description,
+ start_date: from,
+ end_date: to,
+ recurrence: recurrence,
+ timezone: timezone,
+ )
+
+ post.publish_change_to_clients!(:calendar_change)
+ end
+
+ private
+
+ def self.convert_to_date_time(value)
+ return if value.blank?
+
+ datetime = value["date"].to_s
+ datetime << " #{value["time"]}" if value["time"]
+ timezone = value["timezone"] || "UTC"
+
+ ActiveSupport::TimeZone[timezone].parse(datetime)
+ end
+
+ def self.hour_adjustment(setting)
+ setting = setting.split(":")
+
+ { hour: setting.first, min: setting.last }
+ end
+end
+
+# == Schema Information
+#
+# Table name: calendar_events
+#
+# id :bigint not null, primary key
+# topic_id :integer not null
+# post_id :integer
+# post_number :integer
+# user_id :integer
+# username :string
+# description :string
+# start_date :datetime not null
+# end_date :datetime
+# recurrence :string
+# region :string
+# created_at :datetime not null
+# updated_at :datetime not null
+# timezone :string
+#
+# Indexes
+#
+# index_calendar_events_on_post_id (post_id)
+# index_calendar_events_on_topic_id (topic_id)
+# index_calendar_events_on_user_id (user_id)
+#
diff --git a/plugins/discourse-calendar/app/models/discourse_calendar/disabled_holiday.rb b/plugins/discourse-calendar/app/models/discourse_calendar/disabled_holiday.rb
new file mode 100644
index 00000000000..e8407883ed8
--- /dev/null
+++ b/plugins/discourse-calendar/app/models/discourse_calendar/disabled_holiday.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module DiscourseCalendar
+ class DisabledHoliday < ActiveRecord::Base
+ validates :holiday_name, presence: true
+ validates :region_code, presence: true
+ end
+end
+
+# == Schema Information
+#
+# Table name: discourse_calendar_disabled_holidays
+#
+# id :bigint not null, primary key
+# holiday_name :string not null
+# region_code :string not null
+# disabled :boolean default(TRUE), not null
+# created_at :datetime not null
+# updated_at :datetime not null
+#
+# Indexes
+#
+# index_disabled_holidays_on_holiday_name_and_region_code (holiday_name,region_code)
+#
diff --git a/plugins/discourse-calendar/app/models/discourse_post_event/event.rb b/plugins/discourse-calendar/app/models/discourse_post_event/event.rb
new file mode 100644
index 00000000000..7b690cc0925
--- /dev/null
+++ b/plugins/discourse-calendar/app/models/discourse_post_event/event.rb
@@ -0,0 +1,437 @@
+# frozen_string_literal: true
+
+module DiscoursePostEvent
+ class Event < ActiveRecord::Base
+ PUBLIC_GROUP = "trust_level_0"
+ MIN_NAME_LENGTH = 5
+ MAX_NAME_LENGTH = 255
+ self.table_name = "discourse_post_event_events"
+ self.ignored_columns = %w[starts_at ends_at]
+
+ has_many :event_dates, dependent: :destroy
+ # this is a cross plugin dependency, only called if chat is enabled
+ belongs_to :chat_channel, class_name: "Chat::Channel"
+ has_many :invitees, foreign_key: :post_id, dependent: :delete_all
+ belongs_to :post, foreign_key: :id
+
+ scope :visible, -> { where(deleted_at: nil) }
+
+ after_commit :destroy_topic_custom_field, on: %i[destroy]
+ after_commit :create_or_update_event_date, on: %i[create update]
+ before_save :chat_channel_sync
+
+ validate :raw_invitees_are_groups
+ validates :original_starts_at, presence: true
+ validates :name,
+ length: {
+ in: MIN_NAME_LENGTH..MAX_NAME_LENGTH,
+ },
+ unless: ->(event) { event.name.blank? }
+
+ validate :raw_invitees_length
+ validate :ends_before_start
+ validate :allowed_custom_fields
+
+ def self.attributes_protected_by_default
+ super - %w[id]
+ end
+
+ def destroy_topic_custom_field
+ if self.post && self.post.is_first_post?
+ TopicCustomField.where(
+ topic_id: self.post.topic_id,
+ name: TOPIC_POST_EVENT_STARTS_AT,
+ ).delete_all
+
+ TopicCustomField.where(
+ topic_id: self.post.topic_id,
+ name: TOPIC_POST_EVENT_ENDS_AT,
+ ).delete_all
+ end
+ end
+
+ def create_or_update_event_date
+ starts_at_changed = saved_change_to_original_starts_at
+ ends_at_changed = saved_change_to_original_ends_at
+
+ return if !starts_at_changed && !ends_at_changed
+
+ event_dates.update_all(finished_at: Time.current)
+ set_next_date
+ end
+
+ def set_next_date
+ next_dates = calculate_next_date
+ return if !next_dates
+
+ event_dates.create!(
+ starts_at: next_dates[:starts_at],
+ ends_at: next_dates[:ends_at],
+ ) do |event_date|
+ if next_dates[:ends_at] && next_dates[:ends_at] < Time.current
+ event_date.finished_at = next_dates[:ends_at]
+ end
+ end
+
+ invitees.where.not(status: Invitee.statuses[:going]).update_all(status: nil, notified: false)
+
+ if !next_dates[:rescheduled]
+ notify_invitees!
+ notify_missing_invitees!
+ end
+
+ publish_update!
+ end
+
+ def set_topic_bump
+ date = nil
+
+ return if reminders.blank?
+ reminders
+ .split(",")
+ .each do |reminder|
+ type, value, unit = reminder.split(".")
+ next if type != "bumpTopic" || !validate_reminder_unit(unit)
+ date = starts_at - value.to_i.public_send(unit)
+ break
+ end
+
+ return if date.blank?
+ Jobs.enqueue(:discourse_post_event_bump_topic, topic_id: self.post.topic_id, date: date)
+ end
+
+ def validate_reminder_unit(input)
+ ActiveSupport::Duration::PARTS.any? { |part| part.to_s == input }
+ end
+
+ def expired?
+ (ends_at || starts_at.end_of_day) <= Time.now
+ end
+
+ def starts_at
+ event_dates.pending.order(:starts_at).last&.starts_at ||
+ event_dates.order(:updated_at, :id).last&.starts_at
+ end
+
+ def ends_at
+ event_dates.pending.order(:starts_at).last&.ends_at ||
+ event_dates.order(:updated_at, :id).last&.ends_at
+ end
+
+ def on_going_event_invitees
+ return [] if !self.ends_at && self.starts_at < Time.now
+
+ if self.ends_at
+ extended_ends_at =
+ self.ends_at + SiteSetting.discourse_post_event_edit_notifications_time_extension.minutes
+ return [] if !(self.starts_at..extended_ends_at).cover?(Time.now)
+ end
+
+ invitees.where(status: DiscoursePostEvent::Invitee.statuses[:going])
+ end
+
+ def raw_invitees_length
+ if self.raw_invitees && self.raw_invitees.length > 10
+ errors.add(
+ :base,
+ I18n.t("discourse_post_event.errors.models.event.raw_invitees_length", count: 10),
+ )
+ end
+ end
+
+ def raw_invitees_are_groups
+ if self.raw_invitees && User.select(:id).where(username: self.raw_invitees).limit(1).count > 0
+ errors.add(
+ :base,
+ I18n.t("discourse_post_event.errors.models.event.raw_invitees.only_group"),
+ )
+ end
+ end
+
+ def ends_before_start
+ if self.original_starts_at && self.original_ends_at &&
+ self.original_starts_at >= self.original_ends_at
+ errors.add(
+ :base,
+ I18n.t("discourse_post_event.errors.models.event.ends_at_before_starts_at"),
+ )
+ end
+ end
+
+ def allowed_custom_fields
+ allowed_custom_fields = SiteSetting.discourse_post_event_allowed_custom_fields.split("|")
+ self.custom_fields.each do |key, value|
+ if !allowed_custom_fields.include?(key)
+ errors.add(
+ :base,
+ I18n.t("discourse_post_event.errors.models.event.custom_field_is_invalid", field: key),
+ )
+ end
+ end
+ end
+
+ def create_invitees(attrs)
+ timestamp = Time.now
+ attrs.map! do |attr|
+ { post_id: self.id, created_at: timestamp, updated_at: timestamp }.merge(attr)
+ end
+ result = self.invitees.insert_all!(attrs)
+
+ # batch event does not call calleback
+ ChatChannelSync.sync(self) if chat_enabled?
+
+ result
+ end
+
+ def notify_invitees!(predefined_attendance: false)
+ self
+ .invitees
+ .where(notified: false)
+ .find_each do |invitee|
+ create_notification!(
+ invitee.user,
+ self.post,
+ predefined_attendance: predefined_attendance,
+ )
+ invitee.update!(notified: true)
+ end
+ end
+
+ def notify_missing_invitees!
+ self.missing_users.each { |user| create_notification!(user, self.post) } if self.private?
+ end
+
+ def create_notification!(user, post, predefined_attendance: false)
+ return if post.event.starts_at < Time.current
+
+ message =
+ if predefined_attendance
+ "discourse_post_event.notifications.invite_user_predefined_attendance_notification"
+ else
+ "discourse_post_event.notifications.invite_user_notification"
+ end
+
+ attrs = {
+ notification_type: Notification.types[:event_invitation] || Notification.types[:custom],
+ topic_id: post.topic_id,
+ post_number: post.post_number,
+ data: {
+ user_id: user.id,
+ topic_title: self.name || post.topic.title,
+ display_username: post.user.username,
+ message: message,
+ }.to_json,
+ }
+
+ user.notifications.consolidate_or_create!(attrs)
+ end
+
+ def ongoing?
+ return false if self.closed || self.expired?
+ finishes_at = self.ends_at || self.starts_at.end_of_day
+ (self.starts_at..finishes_at).cover?(Time.now)
+ end
+
+ def self.statuses
+ @statuses ||= Enum.new(standalone: 0, public: 1, private: 2)
+ end
+
+ def public?
+ status == Event.statuses[:public]
+ end
+
+ def standalone?
+ status == Event.statuses[:standalone]
+ end
+
+ def private?
+ status == Event.statuses[:private]
+ end
+
+ def recurring?
+ recurrence.present?
+ end
+
+ def most_likely_going(limit = SiteSetting.displayed_invitees_limit)
+ going = self.invitees.order(%i[status user_id]).limit(limit)
+
+ if self.private? && going.count < limit
+ # invitees are only created when an attendance is set
+ # so we create a dummy invitee object with only what's needed for serializer
+ going =
+ going +
+ missing_users(going.pluck(:user_id))
+ .limit(limit - going.count)
+ .map { |user| Invitee.new(user: user, post_id: self.id) }
+ end
+
+ going
+ end
+
+ def publish_update!
+ self.post.publish_message!("/discourse-post-event/#{self.post.topic_id}", id: self.id)
+ end
+
+ def fetch_users
+ @fetched_users ||= Invitee.extract_uniq_usernames(self.raw_invitees)
+ end
+
+ def enforce_private_invitees!
+ self.invitees.where.not(user_id: fetch_users.select(:id)).delete_all
+ end
+
+ def can_user_update_attendance(user)
+ return false if self.closed || self.expired?
+ return true if self.public?
+
+ self.private? &&
+ (
+ self.invitees.exists?(user_id: user.id) ||
+ (user.groups.pluck(:name) & self.raw_invitees).any?
+ )
+ end
+
+ def self.update_from_raw(post)
+ events = DiscoursePostEvent::EventParser.extract_events(post)
+
+ if events.present?
+ event_params = events.first
+ event = post.event || DiscoursePostEvent::Event.new(id: post.id)
+
+ tz = ActiveSupport::TimeZone[event_params[:timezone] || "UTC"]
+ parsed_starts_at = tz.parse(event_params[:start])
+ parsed_ends_at = event_params[:end] ? tz.parse(event_params[:end]) : nil
+ parsed_recurrence_until =
+ event_params[:"recurrence-until"] ? tz.parse(event_params[:"recurrence-until"]) : nil
+
+ params = {
+ name: event_params[:name],
+ original_starts_at: parsed_starts_at,
+ original_ends_at: parsed_ends_at,
+ url: event_params[:url],
+ description: event_params[:description],
+ location: event_params[:location],
+ recurrence: event_params[:recurrence],
+ recurrence_until: parsed_recurrence_until,
+ timezone: event_params[:timezone],
+ show_local_time: event_params[:"show-local-time"] == "true",
+ status: Event.statuses[event_params[:status]&.to_sym] || event.status,
+ reminders: event_params[:reminders],
+ raw_invitees: event_params[:"allowed-groups"]&.split(","),
+ minimal: event_params[:minimal],
+ closed: event_params[:closed] || false,
+ chat_enabled: event_params[:"chat-enabled"]&.downcase == "true",
+ }
+
+ params[:custom_fields] = {}
+ SiteSetting
+ .discourse_post_event_allowed_custom_fields
+ .split("|")
+ .each do |setting|
+ if event_params[setting.to_sym].present?
+ params[:custom_fields][setting] = event_params[setting.to_sym]
+ end
+ end
+
+ event.update_with_params!(params)
+ event.set_topic_bump
+ elsif post.event
+ post.event.destroy!
+ end
+ end
+
+ def missing_users(excluded_ids = self.invitees.select(:user_id))
+ users = User.real.activated.not_silenced.not_suspended.not_staged
+
+ if self.raw_invitees.present?
+ user_ids =
+ users
+ .joins(:groups)
+ .where("groups.name" => self.raw_invitees)
+ .where.not(id: excluded_ids)
+ .select(:id)
+ User.where(id: user_ids)
+ else
+ users.where.not(id: excluded_ids)
+ end
+ end
+
+ def update_with_params!(params)
+ case params[:status] ? params[:status].to_i : self.status
+ when Event.statuses[:private]
+ if params.key?(:raw_invitees)
+ params = params.merge(raw_invitees: Array(params[:raw_invitees]) - [PUBLIC_GROUP])
+ else
+ params = params.merge(raw_invitees: Array(self.raw_invitees) - [PUBLIC_GROUP])
+ end
+ self.update!(params)
+ self.enforce_private_invitees!
+ when Event.statuses[:public]
+ self.update!(params.merge(raw_invitees: [PUBLIC_GROUP]))
+ when Event.statuses[:standalone]
+ self.update!(params.merge(raw_invitees: []))
+ self.invitees.destroy_all
+ end
+
+ self.publish_update!
+ end
+
+ def chat_channel_sync
+ if self.chat_enabled && self.chat_channel_id.blank? && post.last_editor_id.present?
+ DiscoursePostEvent::ChatChannelSync.sync(
+ self,
+ guardian: Guardian.new(User.find_by(id: post.last_editor_id)),
+ )
+ end
+ end
+
+ def calculate_next_date
+ if self.recurrence.blank? || original_starts_at > Time.current
+ return { starts_at: original_starts_at, ends_at: original_ends_at, rescheduled: false }
+ end
+
+ next_starts_at =
+ RRuleGenerator.generate(
+ starts_at: original_starts_at.in_time_zone(timezone),
+ timezone:,
+ recurrence:,
+ recurrence_until:,
+ ).first
+
+ if original_ends_at
+ difference = original_ends_at - original_starts_at
+ next_ends_at = next_starts_at + difference.seconds
+ else
+ next_ends_at = nil
+ end
+
+ { starts_at: next_starts_at, ends_at: next_ends_at, rescheduled: true }
+ end
+ end
+end
+
+# == Schema Information
+#
+# Table name: discourse_post_event_events
+#
+# id :bigint not null, primary key
+# status :integer default(0), not null
+# original_starts_at :datetime not null
+# original_ends_at :datetime
+# deleted_at :datetime
+# raw_invitees :string is an Array
+# name :string
+# url :string(1000)
+# description :string(1000)
+# location :string(1000)
+# custom_fields :jsonb not null
+# reminders :string
+# recurrence :string
+# timezone :string
+# minimal :boolean
+# closed :boolean default(FALSE), not null
+# chat_enabled :boolean default(FALSE), not null
+# chat_channel_id :bigint
+# recurrence_until :datetime
+# show_local_time :boolean default(FALSE), not null
+#
diff --git a/plugins/discourse-calendar/app/models/discourse_post_event/event_date.rb b/plugins/discourse-calendar/app/models/discourse_post_event/event_date.rb
new file mode 100644
index 00000000000..8f820914a9d
--- /dev/null
+++ b/plugins/discourse-calendar/app/models/discourse_post_event/event_date.rb
@@ -0,0 +1,73 @@
+# frozen_string_literal: true
+
+module DiscoursePostEvent
+ class EventDate < ActiveRecord::Base
+ self.table_name = "discourse_calendar_post_event_dates"
+ belongs_to :event
+
+ scope :pending,
+ -> do
+ where(finished_at: nil).joins(:event).where(
+ "discourse_post_event_events.deleted_at is NULL",
+ )
+ end
+ scope :expired, -> { where("ends_at IS NOT NULL AND ends_at < ?", Time.now) }
+ scope :not_expired, -> { where("ends_at IS NULL OR ends_at > ?", Time.now) }
+
+ after_commit :upsert_topic_custom_field, on: %i[create]
+ def upsert_topic_custom_field
+ if self.event.post && self.event.post.is_first_post?
+ TopicCustomField.upsert(
+ {
+ topic_id: self.event.post.topic_id,
+ name: TOPIC_POST_EVENT_STARTS_AT,
+ value: self.starts_at,
+ created_at: Time.now,
+ updated_at: Time.now,
+ },
+ unique_by: "idx_topic_custom_fields_topic_post_event_starts_at",
+ )
+
+ TopicCustomField.upsert(
+ {
+ topic_id: self.event.post.topic_id,
+ name: TOPIC_POST_EVENT_ENDS_AT,
+ value: self.ends_at,
+ created_at: Time.now,
+ updated_at: Time.now,
+ },
+ unique_by: "idx_topic_custom_fields_topic_post_event_ends_at",
+ )
+ end
+ end
+
+ def started?
+ starts_at <= Time.current
+ end
+
+ def ended?
+ (ends_at || starts_at.end_of_day) <= Time.current
+ end
+ end
+end
+
+# == Schema Information
+#
+# Table name: discourse_calendar_post_event_dates
+#
+# id :bigint not null, primary key
+# event_id :integer
+# starts_at :datetime
+# ends_at :datetime
+# reminder_counter :integer default(0)
+# event_will_start_sent_at :datetime
+# event_started_sent_at :datetime
+# finished_at :datetime
+# created_at :datetime not null
+# updated_at :datetime not null
+#
+# Indexes
+#
+# index_discourse_calendar_post_event_dates_on_event_id (event_id)
+# index_discourse_calendar_post_event_dates_on_finished_at (finished_at)
+#
diff --git a/plugins/discourse-calendar/app/models/discourse_post_event/invitee.rb b/plugins/discourse-calendar/app/models/discourse_post_event/invitee.rb
new file mode 100644
index 00000000000..570d38657ee
--- /dev/null
+++ b/plugins/discourse-calendar/app/models/discourse_post_event/invitee.rb
@@ -0,0 +1,90 @@
+# frozen_string_literal: true
+
+module DiscoursePostEvent
+ class Invitee < ActiveRecord::Base
+ UNKNOWN_ATTENDANCE = "unknown"
+
+ self.table_name = "discourse_post_event_invitees"
+
+ belongs_to :event, foreign_key: :post_id
+ belongs_to :user
+
+ default_scope { joins(:user).includes(:user).where("users.id IS NOT NULL") }
+ scope :with_status, ->(status) { where(status: Invitee.statuses[status]) }
+
+ after_commit :sync_chat_channel_members
+
+ def self.statuses
+ @statuses ||= Enum.new(going: 0, interested: 1, not_going: 2)
+ end
+
+ def self.create_attendance!(user_id, post_id, status)
+ invitee =
+ Invitee.create!(status: Invitee.statuses[status.to_sym], post_id: post_id, user_id: user_id)
+ invitee.event.publish_update!
+ invitee.update_topic_tracking!
+ DiscourseEvent.trigger(:discourse_calendar_post_event_invitee_status_changed, invitee)
+ invitee
+ rescue ActiveRecord::RecordNotUnique
+ # do nothing in case multiple new attendances would be created very fast
+ Invitee.find_by(post_id: post_id, user_id: user_id)
+ end
+
+ def update_attendance!(status)
+ new_status = Invitee.statuses[status.to_sym]
+ status_changed = self.status != new_status
+ self.update(status: new_status)
+ self.event.publish_update!
+ self.update_topic_tracking! if status_changed
+ DiscourseEvent.trigger(:discourse_calendar_post_event_invitee_status_changed, self)
+ self
+ end
+
+ def self.extract_uniq_usernames(groups)
+ User.real.where(
+ id: GroupUser.where(group_id: Group.where(name: groups).select(:id)).select(:user_id),
+ )
+ end
+
+ def sync_chat_channel_members
+ return if !self.event.chat_enabled?
+ ChatChannelSync.sync(self.event)
+ end
+
+ def update_topic_tracking!
+ topic_id = self.event.post.topic.id
+ user_id = self.user.id
+ tracking = :regular
+
+ case self.status
+ when Invitee.statuses[:going]
+ tracking = :watching
+ when Invitee.statuses[:interested]
+ tracking = :tracking
+ end
+
+ TopicUser.change(
+ user_id,
+ topic_id,
+ notification_level: TopicUser.notification_levels[tracking],
+ )
+ end
+ end
+end
+
+# == Schema Information
+#
+# Table name: discourse_post_event_invitees
+#
+# id :bigint not null, primary key
+# post_id :integer not null
+# user_id :integer not null
+# status :integer
+# created_at :datetime not null
+# updated_at :datetime not null
+# notified :boolean default(FALSE), not null
+#
+# Indexes
+#
+# discourse_post_event_invitees_post_id_user_id_idx (post_id,user_id) UNIQUE
+#
diff --git a/plugins/discourse-calendar/app/serializers/discourse_post_event/event_serializer.rb b/plugins/discourse-calendar/app/serializers/discourse_post_event/event_serializer.rb
new file mode 100644
index 00000000000..ac48921d607
--- /dev/null
+++ b/plugins/discourse-calendar/app/serializers/discourse_post_event/event_serializer.rb
@@ -0,0 +1,156 @@
+# frozen_string_literal: true
+
+module DiscoursePostEvent
+ class EventSerializer < ApplicationSerializer
+ attributes :can_act_on_discourse_post_event
+ attributes :can_update_attendance
+ attributes :category_id
+ attributes :creator
+ attributes :custom_fields
+ attributes :ends_at
+ attributes :id
+ attributes :is_closed
+ attributes :is_expired
+ attributes :is_ongoing
+ attributes :is_private
+ attributes :is_public
+ attributes :is_standalone
+ attributes :minimal
+ attributes :name
+ attributes :post
+ attributes :raw_invitees
+ attributes :recurrence
+ attributes :recurrence_rule
+ attributes :recurrence_until
+ attributes :reminders
+ attributes :sample_invitees
+ attributes :should_display_invitees
+ attributes :starts_at
+ attributes :stats
+ attributes :status
+ attributes :timezone
+ attributes :show_local_time
+ attributes :url
+ attributes :description
+ attributes :location
+ attributes :watching_invitee
+ attributes :chat_enabled
+ attributes :channel
+
+ def channel
+ ::Chat::ChannelSerializer.new(object.chat_channel, root: false, scope:)
+ end
+
+ def include_channel?
+ object.chat_enabled && defined?(::Chat::ChannelSerializer) && object.chat_channel.present?
+ end
+
+ def can_act_on_discourse_post_event
+ scope.can_act_on_discourse_post_event?(object)
+ end
+
+ def reminders
+ (object.reminders || "")
+ .split(",")
+ .map do |reminder|
+ unit, value, type = reminder.split(".").reverse
+ type ||= "notification"
+
+ value = value.to_i
+ { value: value.to_i.abs, unit: unit, period: value > 0 ? "before" : "after", type: type }
+ end
+ end
+
+ def is_expired
+ object.expired?
+ end
+
+ def is_ongoing
+ object.ongoing?
+ end
+
+ def is_public
+ object.public?
+ end
+
+ def is_private
+ object.private?
+ end
+
+ def is_standalone
+ object.standalone?
+ end
+
+ def is_closed
+ object.closed
+ end
+
+ def status
+ Event.statuses[object.status]
+ end
+
+ # lightweight post object containing
+ # only needed info for client
+ def post
+ {
+ id: object.post.id,
+ post_number: object.post.post_number,
+ url: object.post.url,
+ topic: {
+ id: object.post.topic.id,
+ title: object.post.topic.title,
+ },
+ }
+ end
+
+ def can_update_attendance
+ scope.current_user && object.can_user_update_attendance(scope.current_user)
+ end
+
+ def creator
+ BasicUserSerializer.new(object.post.user, embed: :objects, root: false)
+ end
+
+ def stats
+ EventStatsSerializer.new(object, root: false).as_json
+ end
+
+ def watching_invitee
+ if scope.current_user
+ watching_invitee = Invitee.find_by(user_id: scope.current_user.id, post_id: object.id)
+ end
+
+ InviteeSerializer.new(watching_invitee, root: false) if watching_invitee
+ end
+
+ def sample_invitees
+ invitees = object.most_likely_going
+ ActiveModel::ArraySerializer.new(invitees, each_serializer: InviteeSerializer)
+ end
+
+ def should_display_invitees
+ (object.public? && object.invitees.count > 0) ||
+ (object.private? && object.raw_invitees.count > 0)
+ end
+
+ def category_id
+ object.post.topic.category_id
+ end
+
+ def include_url?
+ object.url.present?
+ end
+
+ def include_recurrence_rule?
+ object.recurring?
+ end
+
+ def recurrence_rule
+ RRuleConfigurator.rule(
+ recurrence: object.recurrence,
+ starts_at: object.starts_at.in_time_zone(object.timezone),
+ recurrence_until: object.recurrence_until&.in_time_zone(object.timezone),
+ )
+ end
+ end
+end
diff --git a/plugins/discourse-calendar/app/serializers/discourse_post_event/event_stats_serializer.rb b/plugins/discourse-calendar/app/serializers/discourse_post_event/event_stats_serializer.rb
new file mode 100644
index 00000000000..4e3674df602
--- /dev/null
+++ b/plugins/discourse-calendar/app/serializers/discourse_post_event/event_stats_serializer.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+module DiscoursePostEvent
+ class EventStatsSerializer < ApplicationSerializer
+ attributes :going
+ attributes :interested
+ attributes :not_going
+ attributes :invited
+
+ def invited
+ unanswered = counts[nil] || 0
+
+ # when a group is private we know the list of possible users
+ # even if an invitee has not been created yet
+ unanswered += object.missing_users.count if object.private?
+
+ going + interested + not_going + unanswered
+ end
+
+ def going
+ @going ||= counts[Invitee.statuses[:going]] || 0
+ end
+
+ def interested
+ @interested ||= counts[Invitee.statuses[:interested]] || 0
+ end
+
+ def not_going
+ @not_going ||= counts[Invitee.statuses[:not_going]] || 0
+ end
+
+ def counts
+ @counts ||= object.invitees.group(:status).count
+ end
+ end
+end
diff --git a/plugins/discourse-calendar/app/serializers/discourse_post_event/event_summary_serializer.rb b/plugins/discourse-calendar/app/serializers/discourse_post_event/event_summary_serializer.rb
new file mode 100644
index 00000000000..04940c27280
--- /dev/null
+++ b/plugins/discourse-calendar/app/serializers/discourse_post_event/event_summary_serializer.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+module DiscoursePostEvent
+ class EventSummarySerializer < ApplicationSerializer
+ attributes :id
+ attributes :starts_at
+ attributes :ends_at
+ attributes :show_local_time
+ attributes :timezone
+ attributes :post
+ attributes :name
+ attributes :category_id
+ attributes :upcoming_dates
+
+ # lightweight post object containing
+ # only needed info for client
+ def post
+ post_hash = {
+ id: object.post.id,
+ post_number: object.post.post_number,
+ url: object.post.url,
+ topic: {
+ id: object.post.topic.id,
+ title: object.post.topic.title,
+ },
+ }
+
+ if post_hash[:topic][:title].match?(/:[\w\-+]+:/)
+ post_hash[:topic][:title] = Emoji.gsub_emoji_to_unicode(post_hash[:topic][:title])
+ end
+
+ if JSON.parse(SiteSetting.map_events_to_color).size > 0
+ post_hash[:topic][:category_slug] = object.post.topic&.category&.slug
+ post_hash[:topic][:tags] = object.post.topic.tags&.map(&:name)
+ end
+
+ post_hash
+ end
+
+ def category_id
+ object.post.topic.category_id
+ end
+
+ def include_upcoming_dates?
+ object.recurring?
+ end
+
+ def upcoming_dates
+ difference = object.original_ends_at ? object.original_ends_at - object.original_starts_at : 0
+
+ RRuleGenerator
+ .generate(
+ starts_at: object.original_starts_at.in_time_zone(object.timezone),
+ timezone: object.timezone,
+ max_years: 1,
+ recurrence: object.recurrence,
+ recurrence_until: object.recurrence_until,
+ )
+ .map { |date| { starts_at: date, ends_at: date + difference.seconds } }
+ end
+ end
+end
diff --git a/plugins/discourse-calendar/app/serializers/discourse_post_event/invitee_list_serializer.rb b/plugins/discourse-calendar/app/serializers/discourse_post_event/invitee_list_serializer.rb
new file mode 100644
index 00000000000..ac4c949d9f8
--- /dev/null
+++ b/plugins/discourse-calendar/app/serializers/discourse_post_event/invitee_list_serializer.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+module DiscoursePostEvent
+ class InviteeListSerializer < ApplicationSerializer
+ root false
+ attributes :meta
+ has_many :invitees, serializer: InviteeSerializer, embed: :objects
+
+ def invitees
+ object[:invitees]
+ end
+
+ def meta
+ {
+ suggested_users:
+ ActiveModel::ArraySerializer.new(
+ suggested_users,
+ each_serializer: BasicUserSerializer,
+ scope: scope,
+ ),
+ }
+ end
+
+ def include_meta?
+ suggested_users.present?
+ end
+
+ def suggested_users
+ object[:suggested_users]
+ end
+ end
+end
diff --git a/plugins/discourse-calendar/app/serializers/discourse_post_event/invitee_serializer.rb b/plugins/discourse-calendar/app/serializers/discourse_post_event/invitee_serializer.rb
new file mode 100644
index 00000000000..c2c6b8cc447
--- /dev/null
+++ b/plugins/discourse-calendar/app/serializers/discourse_post_event/invitee_serializer.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+module DiscoursePostEvent
+ class InviteeSerializer < ApplicationSerializer
+ attributes :id, :status, :user, :post_id, :meta
+
+ def status
+ object.status ? Invitee.statuses[object.status] : nil
+ end
+
+ def include_id?
+ object.id
+ end
+
+ def user
+ BasicUserSerializer.new(object.user, embed: :objects, root: false)
+ end
+
+ def meta
+ {
+ event_should_display_invitees:
+ (object.event.public? && object.event.invitees.count > 0) ||
+ (object.event.private? && object.event.raw_invitees.count > 0),
+ event_stats: EventStatsSerializer.new(object.event, root: false),
+ }
+ end
+ end
+end
diff --git a/plugins/discourse-calendar/app/serializers/user_timezone_serializer.rb b/plugins/discourse-calendar/app/serializers/user_timezone_serializer.rb
new file mode 100644
index 00000000000..f1625229132
--- /dev/null
+++ b/plugins/discourse-calendar/app/serializers/user_timezone_serializer.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class UserTimezoneSerializer < BasicUserSerializer
+ attributes :timezone, :on_holiday
+
+ def on_holiday
+ @options[:on_holiday] || false
+ end
+end
diff --git a/plugins/discourse-calendar/app/services/discourse_calendar/holiday.rb b/plugins/discourse-calendar/app/services/discourse_calendar/holiday.rb
new file mode 100644
index 00000000000..acb54346ea1
--- /dev/null
+++ b/plugins/discourse-calendar/app/services/discourse_calendar/holiday.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+require "holidays"
+
+module DiscourseCalendar
+ class Holiday
+ def self.find_holidays_for(
+ region_code:,
+ start_date: Date.current.beginning_of_year,
+ end_date: Date.current.end_of_year,
+ show_holiday_observed_on_dates: false
+ )
+ holidays =
+ Holidays.between(
+ start_date,
+ end_date,
+ [region_code],
+ show_holiday_observed_on_dates ? :observed : [],
+ )
+
+ holidays.map do |holiday|
+ holiday[:disabled] = DiscourseCalendar::DisabledHoliday.where(
+ region_code: region_code,
+ ).exists?(holiday_name: holiday[:name])
+ end
+
+ holidays
+ end
+ end
+end
diff --git a/plugins/discourse-calendar/app/services/discourse_post_event/chat_channel_sync.rb b/plugins/discourse-calendar/app/services/discourse_post_event/chat_channel_sync.rb
new file mode 100644
index 00000000000..37f9320a66f
--- /dev/null
+++ b/plugins/discourse-calendar/app/services/discourse_post_event/chat_channel_sync.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+#
+module DiscoursePostEvent
+ class ChatChannelSync
+ def self.sync(event, guardian: nil)
+ return if !event.chat_enabled?
+ if !event.chat_channel_id && guardian&.can_create_chat_channel?
+ ensure_chat_channel!(event, guardian:)
+ end
+ sync_chat_channel_members!(event) if event.chat_channel_id
+ end
+
+ def self.sync_chat_channel_members!(event)
+ missing_members_sql = <<~SQL
+ SELECT user_id
+ FROM discourse_post_event_invitees
+ WHERE post_id = :post_id
+ AND status in (:statuses)
+ AND user_id NOT IN (
+ SELECT user_id
+ FROM user_chat_channel_memberships
+ WHERE chat_channel_id = :chat_channel_id
+ )
+ SQL
+
+ missing_user_ids =
+ DB.query_single(
+ missing_members_sql,
+ post_id: event.post.id,
+ statuses: [
+ DiscoursePostEvent::Invitee.statuses[:going],
+ DiscoursePostEvent::Invitee.statuses[:interested],
+ ],
+ chat_channel_id: event.chat_channel_id,
+ )
+
+ if missing_user_ids.present?
+ ActiveRecord::Base.transaction do
+ missing_user_ids.each do |user_id|
+ event.chat_channel.user_chat_channel_memberships.create!(
+ user_id:,
+ chat_channel_id: event.chat_channel_id,
+ following: true,
+ )
+ end
+ end
+ end
+ end
+
+ def self.ensure_chat_channel!(event, guardian:)
+ name = event.name
+
+ channel = nil
+ Chat::CreateCategoryChannel.call(
+ guardian:,
+ params: {
+ name:,
+ category_id: event.post.topic.category_id,
+ },
+ ) do |result|
+ on_success { channel = result.channel }
+ on_failure { raise StandardError, result.inspect_steps }
+ end
+
+ # event creator will be a member of the channel
+ event.chat_channel_id = channel.id
+ end
+ end
+end
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/adapters/discourse-post-event-adapter.js b/plugins/discourse-calendar/assets/javascripts/discourse/adapters/discourse-post-event-adapter.js
new file mode 100644
index 00000000000..455506cf72f
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/adapters/discourse-post-event-adapter.js
@@ -0,0 +1,7 @@
+import RestAdapter from "discourse/adapters/rest";
+
+export default class DiscoursePostEventAdapter extends RestAdapter {
+ basePath() {
+ return "/discourse-post-event/";
+ }
+}
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/adapters/discourse-post-event-event.js b/plugins/discourse-calendar/assets/javascripts/discourse/adapters/discourse-post-event-event.js
new file mode 100644
index 00000000000..6c80730092b
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/adapters/discourse-post-event-event.js
@@ -0,0 +1,15 @@
+import { underscore } from "@ember/string";
+import DiscoursePostEventAdapter from "./discourse-post-event-adapter";
+
+export default class DiscoursePostEventEvent extends DiscoursePostEventAdapter {
+ pathFor(store, type, findArgs) {
+ const path =
+ this.basePath(store, type, findArgs) +
+ underscore(store.pluralize(this.apiNameFor(type)));
+ return this.appendQueryParams(path, findArgs);
+ }
+
+ apiNameFor() {
+ return "event";
+ }
+}
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/adapters/discourse-post-event-invitee.js b/plugins/discourse-calendar/assets/javascripts/discourse/adapters/discourse-post-event-invitee.js
new file mode 100644
index 00000000000..b4ca774f4ab
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/adapters/discourse-post-event-invitee.js
@@ -0,0 +1,7 @@
+import DiscoursePostEventNestedAdapter from "./discourse-post-event-nested-adapter";
+
+export default class DiscoursePostEventInvitee extends DiscoursePostEventNestedAdapter {
+ apiNameFor() {
+ return "invitee";
+ }
+}
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/adapters/discourse-post-event-nested-adapter.js b/plugins/discourse-calendar/assets/javascripts/discourse/adapters/discourse-post-event-nested-adapter.js
new file mode 100644
index 00000000000..891cd5c2fac
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/adapters/discourse-post-event-nested-adapter.js
@@ -0,0 +1,65 @@
+import { underscore } from "@ember/string";
+import { Result } from "discourse/adapters/rest";
+import { ajax } from "discourse/lib/ajax";
+import DiscoursePostEventAdapter from "./discourse-post-event-adapter";
+
+export default class DiscoursePostEventNestedAdapter extends DiscoursePostEventAdapter {
+ // TODO: destroy/update/create should be improved in core to allow for nested models
+ destroyRecord(store, type, record) {
+ return ajax(
+ this.pathFor(store, type, {
+ post_id: record.post_id,
+ id: record.id,
+ }),
+ {
+ type: "DELETE",
+ }
+ );
+ }
+
+ update(store, type, id, attrs) {
+ const data = {};
+ const typeField = underscore(this.apiNameFor(type));
+ data[typeField] = attrs;
+
+ return ajax(
+ this.pathFor(store, type, { id, post_id: attrs.post_id }),
+ this.getPayload("PUT", data)
+ ).then(function (json) {
+ return new Result(json[typeField], json);
+ });
+ }
+
+ createRecord(store, type, attrs) {
+ const data = {};
+ const typeField = underscore(this.apiNameFor(type));
+ data[typeField] = attrs;
+ return ajax(
+ this.pathFor(store, type, attrs),
+ this.getPayload("POST", data)
+ ).then(function (json) {
+ return new Result(json[typeField], json);
+ });
+ }
+
+ pathFor(store, type, findArgs) {
+ const post_id = findArgs["post_id"];
+ delete findArgs["post_id"];
+
+ const id = findArgs["id"];
+ delete findArgs["id"];
+
+ let path =
+ this.basePath(store, type, {}) +
+ "events/" +
+ post_id +
+ "/" +
+ underscore(store.pluralize(this.apiNameFor()));
+
+ if (id) {
+ path += `/${id}`;
+ }
+
+ return this.appendQueryParams(path, findArgs);
+ }
+}
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/adapters/discourse-post-event-reminder.js b/plugins/discourse-calendar/assets/javascripts/discourse/adapters/discourse-post-event-reminder.js
new file mode 100644
index 00000000000..a0a71c543e7
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/adapters/discourse-post-event-reminder.js
@@ -0,0 +1,7 @@
+import DiscoursePostEventNestedAdapter from "./discourse-post-event-nested-adapter";
+
+export default class DiscoursePostEventReminder extends DiscoursePostEventNestedAdapter {
+ apiNameFor() {
+ return "reminder";
+ }
+}
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/admin-calendar-route-map.js b/plugins/discourse-calendar/assets/javascripts/discourse/admin-calendar-route-map.js
new file mode 100644
index 00000000000..5a49b3a5082
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/admin-calendar-route-map.js
@@ -0,0 +1,7 @@
+export default {
+ resource: "admin.adminPlugins",
+ path: "/plugins",
+ map() {
+ this.route("calendar");
+ },
+};
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/api-initializers/discourse-group-timezones.gjs b/plugins/discourse-calendar/assets/javascripts/discourse/api-initializers/discourse-group-timezones.gjs
new file mode 100644
index 00000000000..e07a8320428
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/api-initializers/discourse-group-timezones.gjs
@@ -0,0 +1,35 @@
+import { apiInitializer } from "discourse/lib/api";
+import GroupTimezones from "../components/group-timezones";
+
+const GroupTimezonesShim =
+
+;
+
+export default apiInitializer((api) => {
+ api.decorateCookedElement((element, helper) => {
+ element.querySelectorAll(".group-timezones").forEach((el) => {
+ const post = helper.getModel();
+
+ if (!post) {
+ return;
+ }
+
+ const group = el.dataset.group;
+ if (!group) {
+ throw new Error(
+ "Group timezone element is missing 'data-group' attribute"
+ );
+ }
+
+ helper.renderGlimmer(el, GroupTimezonesShim, {
+ group,
+ members: (post.group_timezones || {})[group] || [],
+ size: el.dataset.size || "medium",
+ });
+ });
+ });
+});
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/components/admin-holidays-list-item.gjs b/plugins/discourse-calendar/assets/javascripts/discourse/components/admin-holidays-list-item.gjs
new file mode 100644
index 00000000000..407ad74fcb4
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/components/admin-holidays-list-item.gjs
@@ -0,0 +1,69 @@
+import Component from "@ember/component";
+import { action } from "@ember/object";
+import { classNameBindings, tagName } from "@ember-decorators/component";
+import DButton from "discourse/components/d-button";
+import { ajax } from "discourse/lib/ajax";
+import { popupAjaxError } from "discourse/lib/ajax-error";
+
+@tagName("tr")
+@classNameBindings("isHolidayDisabled:disabled")
+export default class AdminHolidaysListItem extends Component {
+ loading = false;
+ isHolidayDisabled = false;
+
+ @action
+ disableHoliday(holiday, region_code) {
+ if (this.loading) {
+ return;
+ }
+
+ this.set("loading", true);
+
+ return ajax({
+ url: `/admin/discourse-calendar/holidays/disable`,
+ type: "POST",
+ data: { disabled_holiday: { holiday_name: holiday.name, region_code } },
+ })
+ .then(() => this.set("isHolidayDisabled", true))
+ .catch(popupAjaxError)
+ .finally(() => this.set("loading", false));
+ }
+
+ @action
+ enableHoliday(holiday, region_code) {
+ if (this.loading) {
+ return;
+ }
+
+ this.set("loading", true);
+
+ return ajax({
+ url: `/admin/discourse-calendar/holidays/enable`,
+ type: "DELETE",
+ data: { disabled_holiday: { holiday_name: holiday.name, region_code } },
+ })
+ .then(() => this.set("isHolidayDisabled", false))
+ .catch(popupAjaxError)
+ .finally(() => this.set("loading", false));
+ }
+
+
+ | {{this.holiday.date}} |
+ {{this.holiday.name}} |
+
+ {{#if this.isHolidayDisabled}}
+
+ {{else}}
+
+ {{/if}}
+ |
+
+}
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/components/admin-holidays-list.gjs b/plugins/discourse-calendar/assets/javascripts/discourse/components/admin-holidays-list.gjs
new file mode 100644
index 00000000000..058beed6848
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/components/admin-holidays-list.gjs
@@ -0,0 +1,25 @@
+import { i18n } from "discourse-i18n";
+import AdminHolidaysListItem from "./admin-holidays-list-item";
+
+const AdminHolidaysList =
+
+
+
+ | {{i18n "discourse_calendar.date"}} |
+ {{i18n "discourse_calendar.holiday"}} |
+
+
+
+
+ {{#each @holidays as |holiday|}}
+
+ {{/each}}
+
+
+;
+
+export default AdminHolidaysList;
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/components/bulk-invite-sample-csv-file.gjs b/plugins/discourse-calendar/assets/javascripts/discourse/components/bulk-invite-sample-csv-file.gjs
new file mode 100644
index 00000000000..b18ddd14211
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/components/bulk-invite-sample-csv-file.gjs
@@ -0,0 +1,36 @@
+import Component from "@ember/component";
+import { action } from "@ember/object";
+import DButton from "discourse/components/d-button";
+
+export default class BulkInviteSampleCsvFile extends Component {
+ @action
+ downloadSampleCsv() {
+ const sampleData = [
+ ["my_awesome_group", "going"],
+ ["lucy", "interested"],
+ ["mark", "not_going"],
+ ["sam", "unknown"],
+ ];
+
+ let csv = "";
+ sampleData.forEach((row) => {
+ csv += row.join(",");
+ csv += "\n";
+ });
+
+ const btn = document.createElement("a");
+ btn.href = `data:text/csv;charset=utf-8,${encodeURI(csv)}`;
+ btn.target = "_blank";
+ btn.rel = "noopener noreferrer";
+ btn.download = "bulk-invite-sample.csv";
+ btn.click();
+ }
+
+
+
+
+}
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/components/csv-uploader.gjs b/plugins/discourse-calendar/assets/javascripts/discourse/components/csv-uploader.gjs
new file mode 100644
index 00000000000..d828f61879d
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/components/csv-uploader.gjs
@@ -0,0 +1,64 @@
+import Component from "@glimmer/component";
+import { getOwner } from "@ember/owner";
+import didInsert from "@ember/render-modifiers/modifiers/did-insert";
+import { service } from "@ember/service";
+import { or } from "truth-helpers";
+import icon from "discourse/helpers/d-icon";
+import UppyUpload from "discourse/lib/uppy/uppy-upload";
+import { i18n } from "discourse-i18n";
+
+export default class CsvUploader extends Component {
+ @service dialog;
+
+ uppyUpload = new UppyUpload(getOwner(this), {
+ type: "csv",
+ id: "discourse-post-event-csv-uploader",
+ autoStartUploads: false,
+ uploadUrl: this.args.uploadUrl,
+ uppyReady: () => {
+ this.uppyUpload.uppyWrapper.uppyInstance.on("file-added", () => {
+ this.dialog.confirm({
+ message: i18n(`${this.args.i18nPrefix}.confirmation_message`),
+ didConfirm: () => this.uppyUpload.startUpload(),
+ didCancel: () => this.uppyUpload.reset(),
+ });
+ });
+ },
+ uploadDone: () => {
+ this.dialog.alert(i18n(`${this.args.i18nPrefix}.success`));
+ },
+ validateUploadedFilesOptions: {
+ csvOnly: true,
+ },
+ });
+
+ get uploadButtonText() {
+ return this.uppyUpload.uploading
+ ? i18n("uploading")
+ : i18n(`${this.args.i18nPrefix}.text`);
+ }
+
+ get uploadButtonDisabled() {
+ // https://github.com/emberjs/ember.js/issues/10976#issuecomment-132417731
+ return this.uppyUpload.uploading || this.uppyUpload.processing || null;
+ }
+
+
+
+
+ {{#if (or this.uppyUpload.uploading this.uppyUpload.processing)}}
+ {{i18n "upload_selector.uploading"}}
+ {{this.uppyUpload.uploadProgress}}%
+ {{/if}}
+
+
+}
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/components/discourse-post-event/chat-channel.gjs b/plugins/discourse-calendar/assets/javascripts/discourse/components/discourse-post-event/chat-channel.gjs
new file mode 100644
index 00000000000..c1740da8e10
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/components/discourse-post-event/chat-channel.gjs
@@ -0,0 +1,20 @@
+import { LinkTo } from "@ember/routing";
+import { and } from "truth-helpers";
+import { optionalRequire } from "discourse/lib/utilities";
+
+const ChannelTitle = optionalRequire(
+ "discourse/plugins/chat/discourse/components/channel-title"
+);
+
+const DiscoursePostEventChatChannel =
+ {{#if (and @event.channel ChannelTitle)}}
+
+ {{/if}}
+;
+
+export default DiscoursePostEventChatChannel;
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/components/discourse-post-event/creator.gjs b/plugins/discourse-calendar/assets/javascripts/discourse/components/discourse-post-event/creator.gjs
new file mode 100644
index 00000000000..44dc031fd66
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/components/discourse-post-event/creator.gjs
@@ -0,0 +1,23 @@
+import Component from "@glimmer/component";
+import avatar from "discourse/helpers/avatar";
+import { formatUsername } from "discourse/lib/utilities";
+import { i18n } from "discourse-i18n";
+
+export default class DiscoursePostEventCreator extends Component {
+ get username() {
+ return this.args.user.name || formatUsername(this.args.user.username);
+ }
+
+
+
+ {{i18n "discourse_post_event.created_by"}}
+
+
+
+ {{avatar @user imageSize="tiny"}}
+ {{this.username}}
+
+
+
+
+}
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/components/discourse-post-event/dates.gjs b/plugins/discourse-calendar/assets/javascripts/discourse/components/discourse-post-event/dates.gjs
new file mode 100644
index 00000000000..48f2e911030
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/components/discourse-post-event/dates.gjs
@@ -0,0 +1,154 @@
+import Component from "@glimmer/component";
+import { tracked } from "@glimmer/tracking";
+import { action } from "@ember/object";
+import didInsert from "@ember/render-modifiers/modifiers/did-insert";
+import { next } from "@ember/runloop";
+import { service } from "@ember/service";
+import { htmlSafe } from "@ember/template";
+import icon from "discourse/helpers/d-icon";
+import { applyLocalDates } from "discourse/lib/local-dates";
+import { cook } from "discourse/lib/text";
+
+export default class DiscoursePostEventDates extends Component {
+ @service siteSettings;
+
+ @tracked htmlDates = "";
+
+ get startsAt() {
+ return moment(this.args.event.startsAt).tz(this.timezone);
+ }
+
+ get endsAt() {
+ return (
+ this.args.event.endsAt && moment(this.args.event.endsAt).tz(this.timezone)
+ );
+ }
+
+ get timezone() {
+ return this.args.event.timezone || "UTC";
+ }
+
+ get startsAtFormat() {
+ return this._buildFormat(this.startsAt, {
+ includeYear: !this.isSameYear(this.startsAt),
+ includeTime: this.hasTime(this.startsAt) || this.isSingleDayEvent,
+ });
+ }
+
+ get endsAtFormat() {
+ if (this.isSingleDayEvent) {
+ return "LT";
+ }
+
+ return this._buildFormat(this.endsAt, {
+ includeYear:
+ !this.isSameYear(this.endsAt) ||
+ !this.isSameYear(this.endsAt, this.startsAt),
+ includeTime: this.hasTime(this.endsAt),
+ });
+ }
+
+ _buildFormat(date, { includeYear, includeTime }) {
+ const formatParts = ["ddd, MMM D"];
+ if (includeYear) {
+ formatParts.push("YYYY");
+ }
+
+ const dateString = formatParts.join(", ");
+ const timeString = includeTime ? " LT" : "";
+
+ return `\u0022${dateString}${timeString}\u0022`;
+ }
+
+ get isSingleDayEvent() {
+ return this.startsAt.isSame(this.endsAt, "day");
+ }
+
+ get datesBBCode() {
+ const dates = [];
+
+ dates.push(
+ this.buildDateBBCode({
+ date: this.startsAt,
+ format: this.startsAtFormat,
+ range: !!this.endsAt && "from",
+ })
+ );
+
+ if (this.endsAt) {
+ dates.push(
+ this.buildDateBBCode({
+ date: this.endsAt,
+ format: this.endsAtFormat,
+ range: "to",
+ })
+ );
+ }
+
+ return dates;
+ }
+
+ isSameYear(date1, date2) {
+ return date1.isSame(date2 || moment(), "year");
+ }
+
+ hasTime(date) {
+ return date.hour() || date.minute();
+ }
+
+ buildDateBBCode({ date, format, range }) {
+ const bbcode = {
+ date: date.format("YYYY-MM-DD"),
+ time: date.format("HH:mm"),
+ format,
+ timezone: this.timezone,
+ hideTimezone: this.args.event.showLocalTime,
+ };
+
+ if (this.args.event.showLocalTime) {
+ bbcode.displayedTimezone = this.args.event.timezone;
+ }
+
+ if (range) {
+ bbcode.range = range;
+ }
+
+ const content = Object.entries(bbcode)
+ .map(([key, value]) => `${key}=${value}`)
+ .join(" ");
+
+ return `[${content}]`;
+ }
+
+ @action
+ async computeDates(element) {
+ if (this.siteSettings.discourse_local_dates_enabled) {
+ const result = await cook(this.datesBBCode.join(" → "));
+ this.htmlDates = htmlSafe(result.toString());
+
+ next(() => {
+ if (this.isDestroying || this.isDestroyed) {
+ return;
+ }
+
+ applyLocalDates(
+ element.querySelectorAll(
+ `[data-post-id="${this.args.event.id}"] .discourse-local-date`
+ ),
+ this.siteSettings
+ );
+ });
+ } else {
+ let dates = `${this.startsAt.format(this.startsAtFormat)}`;
+ if (this.endsAt) {
+ dates += ` → ${moment(this.endsAt).format(this.endsAtFormat)}`;
+ }
+ this.htmlDates = htmlSafe(dates);
+ }
+ }
+
+
+
+ {{icon "clock"}}{{this.htmlDates}}
+
+}
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/components/discourse-post-event/description.gjs b/plugins/discourse-calendar/assets/javascripts/discourse/components/discourse-post-event/description.gjs
new file mode 100644
index 00000000000..c83c1a55dbe
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/components/discourse-post-event/description.gjs
@@ -0,0 +1,11 @@
+import CookText from "discourse/components/cook-text";
+
+const DiscoursePostEventDescription =
+ {{#if @description}}
+
+ {{/if}}
+;
+
+export default DiscoursePostEventDescription;
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/components/discourse-post-event/event-status.gjs b/plugins/discourse-calendar/assets/javascripts/discourse/components/discourse-post-event/event-status.gjs
new file mode 100644
index 00000000000..c773732f0e9
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/components/discourse-post-event/event-status.gjs
@@ -0,0 +1,36 @@
+import Component from "@glimmer/component";
+import { i18n } from "discourse-i18n";
+
+export default class EventStatus extends Component {
+ get eventStatusLabel() {
+ return i18n(
+ `discourse_post_event.models.event.status.${this.args.event.status}.title`
+ );
+ }
+
+ get eventStatusDescription() {
+ return i18n(
+ `discourse_post_event.models.event.status.${this.args.event.status}.description`
+ );
+ }
+
+ get statusClass() {
+ return `status ${this.args.event.status}`;
+ }
+
+
+ {{#if @event.isExpired}}
+
+ {{i18n "discourse_post_event.models.event.expired"}}
+
+ {{else if @event.isClosed}}
+
+ {{i18n "discourse_post_event.models.event.closed"}}
+
+ {{else}}
+
+ {{this.eventStatusLabel}}
+
+ {{/if}}
+
+}
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/components/discourse-post-event/index.gjs b/plugins/discourse-calendar/assets/javascripts/discourse/components/discourse-post-event/index.gjs
new file mode 100644
index 00000000000..df47d81fd04
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/components/discourse-post-event/index.gjs
@@ -0,0 +1,157 @@
+import Component from "@glimmer/component";
+import { service } from "@ember/service";
+import { modifier } from "ember-modifier";
+import PluginOutlet from "discourse/components/plugin-outlet";
+import concatClass from "discourse/helpers/concat-class";
+import icon from "discourse/helpers/d-icon";
+import lazyHash from "discourse/helpers/lazy-hash";
+import replaceEmoji from "discourse/helpers/replace-emoji";
+import routeAction from "discourse/helpers/route-action";
+import ChatChannel from "./chat-channel";
+import Creator from "./creator";
+import Dates from "./dates";
+import Description from "./description";
+import EventStatus from "./event-status";
+import Invitees from "./invitees";
+import Location from "./location";
+import MoreMenu from "./more-menu";
+import Status from "./status";
+import Url from "./url";
+
+const StatusSeparator =
+ ·
+;
+
+const InfoSection =
+
+ {{#if @icon}}
+ {{icon @icon}}
+ {{/if}}
+
+ {{yield}}
+
+;
+
+export default class DiscoursePostEvent extends Component {
+ @service currentUser;
+ @service discoursePostEventApi;
+ @service messageBus;
+
+ setupMessageBus = modifier(() => {
+ const { event } = this.args;
+ const path = `/discourse-post-event/${event.post.topic.id}`;
+ this.messageBus.subscribe(path, async (msg) => {
+ const eventData = await this.discoursePostEventApi.event(msg.id);
+ event.updateFromEvent(eventData);
+ });
+
+ return () => this.messageBus.unsubscribe(path);
+ });
+
+ get localStartsAtTime() {
+ let time = moment(this.args.event.startsAt);
+ if (this.args.event.showLocalTime && this.args.event.timezone) {
+ time = time.tz(this.args.event.timezone);
+ }
+ return time;
+ }
+
+ get startsAtMonth() {
+ return this.localStartsAtTime.format("MMM");
+ }
+
+ get startsAtDay() {
+ return this.localStartsAtTime.format("D");
+ }
+
+ get eventName() {
+ return this.args.event.name || this.args.event.post.topic.title;
+ }
+
+ get isPublicEvent() {
+ return this.args.event.status === "public";
+ }
+
+ get isStandaloneEvent() {
+ return this.args.event.status === "standalone";
+ }
+
+ get canActOnEvent() {
+ return this.currentUser && this.args.event.can_act_on_discourse_post_event;
+ }
+
+ get watchingInviteeStatus() {
+ return this.args.event.watchingInvitee?.status;
+ }
+
+
+
+
+ {{#if @event}}
+
+
+
+
+
+
+
+
+
+ {{#if @event.canUpdateAttendance}}
+
+ {{/if}}
+
+ {{/if}}
+
+
+
+}
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/components/discourse-post-event/invitee.gjs b/plugins/discourse-calendar/assets/javascripts/discourse/components/discourse-post-event/invitee.gjs
new file mode 100644
index 00000000000..76b08b0bb40
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/components/discourse-post-event/invitee.gjs
@@ -0,0 +1,53 @@
+import Component from "@glimmer/component";
+import { concat } from "@ember/helper";
+import { service } from "@ember/service";
+import { eq } from "truth-helpers";
+import AvatarFlair from "discourse/components/avatar-flair";
+import avatar from "discourse/helpers/avatar";
+import concatClass from "discourse/helpers/concat-class";
+import { i18n } from "discourse-i18n";
+
+export default class DiscoursePostEventInvitee extends Component {
+ @service site;
+ @service currentUser;
+
+ get statusIcon() {
+ switch (this.args.invitee.status) {
+ case "going":
+ return "check";
+ case "interested":
+ return "star";
+ case "not_going":
+ return "xmark";
+ }
+ }
+
+ get flairName() {
+ const string = `discourse_post_event.models.invitee.status.${this.args.invitee.status}`;
+
+ return i18n(string);
+ }
+
+
+
+
+ {{avatar
+ @invitee.user
+ imageSize=(if this.site.mobileView "tiny" "large")
+ }}
+ {{#if this.statusIcon}}
+
+ {{/if}}
+
+
+
+}
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/components/discourse-post-event/invitees.gjs b/plugins/discourse-calendar/assets/javascripts/discourse/components/discourse-post-event/invitees.gjs
new file mode 100644
index 00000000000..4e6d8377484
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/components/discourse-post-event/invitees.gjs
@@ -0,0 +1,55 @@
+import Component from "@glimmer/component";
+import { service } from "@ember/service";
+import icon from "discourse/helpers/d-icon";
+import { i18n } from "discourse-i18n";
+import Invitee from "./invitee";
+
+export default class DiscoursePostEventInvitees extends Component {
+ @service modal;
+ @service siteSettings;
+
+ get hasAttendees() {
+ return this.args.event.stats.going > 0;
+ }
+
+ get statsInfo() {
+ return this.args.event.stats.going;
+ }
+
+ get inviteesTitle() {
+ return i18n("discourse_post_event.models.invitee.status.going_count", {
+ count: this.args.event.stats.going,
+ });
+ }
+
+
+ {{#unless @event.minimal}}
+ {{#if @event.shouldDisplayInvitees}}
+
+
+
+ {{icon "users"}}
+ {{#if this.hasAttendees}}
+ {{this.statsInfo}}
+ {{/if}}
+
+
+ {{#each @event.sampleInvitees as |invitee|}}
+
+ {{/each}}
+
+
+
+ {{else}}
+ {{#unless @event.isStandalone}}
+
+ {{i18n
+ "discourse_post_event.models.invitee.status.going_count.other"
+ count="0"
+ }}
+
+ {{/unless}}
+ {{/if}}
+ {{/unless}}
+
+}
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/components/discourse-post-event/location.gjs b/plugins/discourse-calendar/assets/javascripts/discourse/components/discourse-post-event/location.gjs
new file mode 100644
index 00000000000..a0c48ad00e1
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/components/discourse-post-event/location.gjs
@@ -0,0 +1,14 @@
+import CookText from "discourse/components/cook-text";
+import icon from "discourse/helpers/d-icon";
+
+const DiscoursePostEventLocation =
+ {{#if @location}}
+
+ {{icon "location-pin"}}
+
+
+
+ {{/if}}
+;
+
+export default DiscoursePostEventLocation;
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/components/discourse-post-event/more-menu.gjs b/plugins/discourse-calendar/assets/javascripts/discourse/components/discourse-post-event/more-menu.gjs
new file mode 100644
index 00000000000..ea75d468205
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/components/discourse-post-event/more-menu.gjs
@@ -0,0 +1,382 @@
+import Component from "@glimmer/component";
+import { tracked } from "@glimmer/tracking";
+import { hash } from "@ember/helper";
+import EmberObject, { action } from "@ember/object";
+import { service } from "@ember/service";
+import DButton from "discourse/components/d-button";
+import DropdownMenu from "discourse/components/dropdown-menu";
+import concatClass from "discourse/helpers/concat-class";
+import { popupAjaxError } from "discourse/lib/ajax-error";
+import { downloadCalendar } from "discourse/lib/download-calendar";
+import { exportEntity } from "discourse/lib/export-csv";
+import { getAbsoluteURL } from "discourse/lib/get-url";
+import { cook } from "discourse/lib/text";
+import { applyValueTransformer } from "discourse/lib/transformer";
+import { i18n } from "discourse-i18n";
+import DMenu from "float-kit/components/d-menu";
+import { buildParams, replaceRaw } from "../../lib/raw-event-helper";
+import PostEventBuilder from "../modal/post-event-builder";
+import PostEventBulkInvite from "../modal/post-event-bulk-invite";
+import PostEventInviteUserOrGroup from "../modal/post-event-invite-user-or-group";
+import PostEventInvitees from "../modal/post-event-invitees";
+
+export default class DiscoursePostEventMoreMenu extends Component {
+ @service currentUser;
+ @service dialog;
+ @service discoursePostEventApi;
+ @service modal;
+ @service router;
+ @service siteSettings;
+ @service store;
+
+ @tracked isSavingEvent = false;
+
+ get expiredOrClosed() {
+ return this.args.event.isExpired || this.args.event.isClosed;
+ }
+
+ get canActOnEvent() {
+ return this.currentUser && this.args.event.canActOnDiscoursePostEvent;
+ }
+
+ get shouldShowParticipants() {
+ return applyValueTransformer(
+ "discourse-calendar-event-more-menu-should-show-participants",
+ this.canActOnEvent && !this.args.isStandaloneEvent,
+ {
+ event: this.args.event,
+ }
+ );
+ }
+
+ get canInvite() {
+ return (
+ !this.expiredOrClosed && this.canActOnEvent && this.args.event.isPublic
+ );
+ }
+
+ get canSeeUpcomingEvents() {
+ return !this.args.event.isClosed && this.args.event.recurrence;
+ }
+
+ get canBulkInvite() {
+ return !this.expiredOrClosed && !this.args.event.isStandalone;
+ }
+
+ get canSendPmToCreator() {
+ return (
+ this.currentUser &&
+ this.currentUser.username !== this.args.event.creator.username
+ );
+ }
+
+ @action
+ addToCalendar() {
+ this.menuApi.close();
+
+ const event = this.args.event;
+
+ downloadCalendar(
+ event.name || event.post.topic.title,
+ [
+ {
+ startsAt: event.startsAt,
+ endsAt: event.endsAt,
+ },
+ ],
+ {
+ recurrenceRule: event.recurrenceRule,
+ location: event.url,
+ details: getAbsoluteURL(event.post.url),
+ }
+ );
+ }
+
+ @action
+ sendPMToCreator() {
+ this.menuApi.close();
+
+ this.args.composePrivateMessage(
+ EmberObject.create(this.args.event.creator),
+ EmberObject.create(this.args.event.post)
+ );
+ }
+
+ @action
+ upcomingEvents() {
+ this.router.transitionTo("discourse-post-event-upcoming-events");
+ }
+
+ @action
+ registerMenuApi(api) {
+ this.menuApi = api;
+ }
+
+ @action
+ async inviteUserOrGroup() {
+ this.menuApi.close();
+
+ try {
+ this.modal.show(PostEventInviteUserOrGroup, {
+ model: { event: this.args.event },
+ });
+ } catch (e) {
+ popupAjaxError(e);
+ }
+ }
+
+ @action
+ exportPostEvent() {
+ this.menuApi.close();
+
+ exportEntity("post_event", {
+ name: "post_event",
+ id: this.args.event.id,
+ });
+ }
+
+ @action
+ bulkInvite() {
+ this.menuApi.close();
+
+ this.modal.show(PostEventBulkInvite, {
+ model: { event: this.args.event },
+ });
+ }
+
+ @action
+ async openEvent() {
+ this.menuApi.close();
+
+ this.dialog.yesNoConfirm({
+ message: i18n("discourse_post_event.builder_modal.confirm_open"),
+ didConfirm: async () => {
+ this.isSavingEvent = true;
+
+ try {
+ const post = await this.store.find("post", this.args.event.id);
+ this.args.event.isClosed = false;
+
+ const eventParams = buildParams(
+ this.args.event.startsAt,
+ this.args.event.endsAt,
+ this.args.event,
+ this.siteSettings
+ );
+
+ const newRaw = replaceRaw(eventParams, post.raw);
+
+ if (newRaw) {
+ const props = {
+ raw: newRaw,
+ edit_reason: i18n("discourse_post_event.edit_reason_opened"),
+ };
+
+ const cooked = await cook(newRaw);
+ props.cooked = cooked.string;
+ await post.save(props);
+ }
+ } catch (e) {
+ popupAjaxError(e);
+ } finally {
+ this.isSavingEvent = false;
+ }
+ },
+ });
+ }
+
+ @action
+ async editPostEvent() {
+ this.menuApi.close();
+
+ this.modal.show(PostEventBuilder, {
+ model: {
+ event: this.args.event,
+ },
+ });
+ }
+
+ @action
+ showParticipants() {
+ this.menuApi.close();
+
+ this.modal.show(PostEventInvitees, {
+ model: {
+ event: this.args.event,
+ title: this.args.event.title,
+ extraClass: this.args.event.extraClass,
+ },
+ });
+ }
+
+ @action
+ async closeEvent() {
+ this.menuApi.close();
+
+ this.dialog.yesNoConfirm({
+ message: i18n("discourse_post_event.builder_modal.confirm_close"),
+ didConfirm: () => {
+ this.isSavingEvent = true;
+ return this.store.find("post", this.args.event.id).then((post) => {
+ this.args.event.isClosed = true;
+
+ const eventParams = buildParams(
+ this.args.event.startsAt,
+ this.args.event.endsAt,
+ this.args.event,
+ this.siteSettings
+ );
+
+ const newRaw = replaceRaw(eventParams, post.raw);
+
+ if (newRaw) {
+ const props = {
+ raw: newRaw,
+ edit_reason: i18n("discourse_post_event.edit_reason_closed"),
+ };
+
+ return cook(newRaw)
+ .then((cooked) => {
+ props.cooked = cooked.string;
+ return post.save(props);
+ })
+ .finally(() => {
+ this.isSavingEvent = false;
+ });
+ }
+ });
+ },
+ });
+ }
+
+
+
+ <:content>
+
+ {{#unless this.expiredOrClosed}}
+
+
+
+ {{/unless}}
+
+ {{#if this.canSendPmToCreator}}
+
+
+
+ {{/if}}
+
+ {{#if this.canInvite}}
+
+
+
+ {{/if}}
+
+ {{#if this.canSeeUpcomingEvents}}
+
+
+
+ {{/if}}
+
+ {{#if this.shouldShowParticipants}}
+
+
+
+
+
+ {{/if}}
+ {{#if this.canActOnEvent}}
+
+
+
+
+ {{#if this.canBulkInvite}}
+
+
+
+ {{/if}}
+
+ {{#if @event.isClosed}}
+
+
+
+ {{else}}
+
+
+
+
+ {{#unless @event.isExpired}}
+
+
+
+ {{/unless}}
+ {{/if}}
+ {{/if}}
+
+
+
+
+}
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/components/discourse-post-event/status.gjs b/plugins/discourse-calendar/assets/javascripts/discourse/components/discourse-post-event/status.gjs
new file mode 100644
index 00000000000..191eceec464
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/components/discourse-post-event/status.gjs
@@ -0,0 +1,183 @@
+import Component from "@glimmer/component";
+import { concat, fn } from "@ember/helper";
+import { action } from "@ember/object";
+import { service } from "@ember/service";
+import DButton from "discourse/components/d-button";
+import PluginOutlet from "discourse/components/plugin-outlet";
+import concatClass from "discourse/helpers/concat-class";
+import lazyHash from "discourse/helpers/lazy-hash";
+import { popupAjaxError } from "discourse/lib/ajax-error";
+
+export default class DiscoursePostEventStatus extends Component {
+ @service appEvents;
+ @service discoursePostEventApi;
+ @service siteSettings;
+
+ get eventButtons() {
+ return this.siteSettings.event_participation_buttons.split("|");
+ }
+
+ get showGoingButton() {
+ return !!this.eventButtons.find((button) => button === "going");
+ }
+
+ get showInterestedButton() {
+ return !!this.eventButtons.find((button) => button === "interested");
+ }
+
+ get showNotGoingButton() {
+ return !!this.eventButtons.find((button) => button === "not going");
+ }
+
+ get canLeave() {
+ return this.args.event.watchingInvitee && this.args.event.isPublic;
+ }
+
+ get watchingInviteeStatus() {
+ return this.args.event.watchingInvitee?.status;
+ }
+
+ @action
+ async leaveEvent() {
+ try {
+ const invitee = this.args.event.watchingInvitee;
+
+ await this.discoursePostEventApi.leaveEvent(this.args.event, invitee);
+
+ this.appEvents.trigger("calendar:invitee-left-event", {
+ invitee,
+ postId: this.args.event.id,
+ });
+ } catch (e) {
+ popupAjaxError(e);
+ }
+ }
+
+ @action
+ async updateEventAttendance(status) {
+ try {
+ await this.discoursePostEventApi.updateEventAttendance(this.args.event, {
+ status,
+ });
+
+ this.appEvents.trigger("calendar:update-invitee-status", {
+ status,
+ postId: this.args.event.id,
+ });
+ } catch (e) {
+ popupAjaxError(e);
+ }
+ }
+
+ @action
+ async joinEventWithStatus(status) {
+ try {
+ await this.discoursePostEventApi.joinEvent(this.args.event, {
+ status,
+ });
+
+ this.appEvents.trigger("calendar:create-invitee-status", {
+ status,
+ postId: this.args.event.id,
+ });
+ } catch (e) {
+ popupAjaxError(e);
+ }
+ }
+
+ @action
+ async changeWatchingInviteeStatus(status) {
+ if (this.args.event.watchingInvitee) {
+ const currentStatus = this.args.event.watchingInvitee.status;
+ if (this.canLeave) {
+ if (status === currentStatus) {
+ await this.leaveEvent();
+ } else {
+ await this.updateEventAttendance(status);
+ }
+ } else {
+ if (status === currentStatus) {
+ status = null;
+ }
+
+ await this.updateEventAttendance(status);
+ }
+ } else {
+ await this.joinEventWithStatus(status);
+ }
+ }
+
+
+
+
+ {{#if this.showGoingButton}}
+ {{#unless @event.minimal}}
+
+
+
+ {{/unless}}
+ {{/if}}
+
+ {{#if this.showInterestedButton}}
+
+
+
+ {{/if}}
+
+ {{#if this.showNotGoingButton}}
+ {{#unless @event.minimal}}
+
+
+
+ {{/unless}}
+ {{/if}}
+
+
+
+}
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/components/discourse-post-event/url.gjs b/plugins/discourse-calendar/assets/javascripts/discourse/components/discourse-post-event/url.gjs
new file mode 100644
index 00000000000..57c5f2dba38
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/components/discourse-post-event/url.gjs
@@ -0,0 +1,26 @@
+import Component from "@glimmer/component";
+import icon from "discourse/helpers/d-icon";
+
+export default class DiscoursePostEventUrl extends Component {
+ get url() {
+ return this.args.url.includes("://") || this.args.url.includes("mailto:")
+ ? this.args.url
+ : `https://${this.args.url}`;
+ }
+
+
+ {{#if @url}}
+
+ {{/if}}
+
+}
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/components/event-date.gjs b/plugins/discourse-calendar/assets/javascripts/discourse/components/event-date.gjs
new file mode 100644
index 00000000000..16ed4958b1c
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/components/event-date.gjs
@@ -0,0 +1,112 @@
+import Component from "@glimmer/component";
+import { service } from "@ember/service";
+import { i18n } from "discourse-i18n";
+import guessDateFormat from "../lib/guess-best-date-format";
+
+export default class EventDate extends Component {
+ @service siteSettings;
+
+
+ {{~#if this.shouldRender~}}
+
+ {{~/if~}}
+
+
+ get shouldRender() {
+ return (
+ this.siteSettings.discourse_post_event_enabled &&
+ this.args.topic.event_starts_at
+ );
+ }
+
+ get eventStartedAt() {
+ return this._parsedDate(this.args.topic.event_starts_at);
+ }
+
+ get eventEndedAt() {
+ return this.args.topic.event_ends_at
+ ? this._parsedDate(this.args.topic.event_ends_at)
+ : this.eventStartedAt;
+ }
+
+ get dateRange() {
+ return this.args.topic.event_ends_at
+ ? `${this._formattedDate(this.eventStartedAt)} → ${this._formattedDate(
+ this.eventEndedAt
+ )}`
+ : this._formattedDate(this.eventStartedAt);
+ }
+
+ get localDateContent() {
+ return this._formattedDate(this.eventStartedAt);
+ }
+
+ get relativeDateType() {
+ if (this.isWithinDateRange) {
+ return "current";
+ }
+ if (this.eventStartedAt.isAfter(moment())) {
+ return "future";
+ }
+ return "past";
+ }
+
+ get isWithinDateRange() {
+ return (
+ this.eventStartedAt.isBefore(moment()) &&
+ this.eventEndedAt.isAfter(moment())
+ );
+ }
+
+ get relativeDateContent() {
+ // dateType "current" uses a different implementation
+ const relativeDates = {
+ future: this.eventStartedAt.from(moment()),
+ past: this.eventEndedAt.from(moment()),
+ };
+ return relativeDates[this.relativeDateType];
+ }
+
+ get timeRemainingContent() {
+ return i18n("discourse_post_event.topic_title.ends_in_duration", {
+ duration: this.eventEndedAt.from(moment()),
+ });
+ }
+
+ _parsedDate(date) {
+ return moment.utc(date).tz(moment.tz.guess());
+ }
+
+ _guessedDateFormat() {
+ return guessDateFormat(this.eventStartedAt);
+ }
+
+ _formattedDate(date) {
+ return date.format(this._guessedDateFormat());
+ }
+}
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/components/event-field.gjs b/plugins/discourse-calendar/assets/javascripts/discourse/components/event-field.gjs
new file mode 100644
index 00000000000..ef23a09e14c
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/components/event-field.gjs
@@ -0,0 +1,20 @@
+import { notEq } from "truth-helpers";
+import { i18n } from "discourse-i18n";
+
+const EventField =
+ {{#if (notEq @enabled false)}}
+
+ {{#if @label}}
+
+ {{i18n @label}}
+
+ {{/if}}
+
+
+ {{yield}}
+
+
+ {{/if}}
+;
+
+export default EventField;
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/components/group-timezones/index.gjs b/plugins/discourse-calendar/assets/javascripts/discourse/components/group-timezones/index.gjs
new file mode 100644
index 00000000000..1a1910d030c
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/components/group-timezones/index.gjs
@@ -0,0 +1,184 @@
+import Component from "@glimmer/component";
+import { tracked } from "@glimmer/tracking";
+import { fn } from "@ember/helper";
+import { on } from "@ember/modifier";
+import { action } from "@ember/object";
+import { service } from "@ember/service";
+import { eq } from "truth-helpers";
+import { i18n } from "discourse-i18n";
+import roundTime from "../../lib/round-time";
+import NewDay from "./new-day";
+import TimeTraveller from "./time-traveller";
+import Timezone from "./timezone";
+
+const nbsp = "\xa0";
+
+export default class GroupTimezones extends Component {
+ @service siteSettings;
+
+ @tracked filter = "";
+ @tracked localTimeOffset = 0;
+
+ get groupedTimezones() {
+ let groupedTimezones = [];
+
+ this.args.members.filterBy("timezone").forEach((member) => {
+ if (this.#shouldAddMemberToGroup(this.filter, member)) {
+ const timezone = member.timezone;
+ const identifier = parseInt(moment.tz(timezone).format("YYYYMDHm"), 10);
+ let groupedTimezone = groupedTimezones.findBy("identifier", identifier);
+
+ if (groupedTimezone) {
+ groupedTimezone.members.push(member);
+ } else {
+ const now = this.#roundMoment(moment.tz(timezone));
+ const workingDays = this.#workingDays();
+ const offset = moment.tz(moment.utc(), timezone).utcOffset();
+
+ groupedTimezone = {
+ identifier,
+ offset,
+ type: "discourse-group-timezone",
+ nowWithOffset: now.add(this.localTimeOffset, "minutes"),
+ closeToWorkingHours: this.#closeToWorkingHours(now, workingDays),
+ inWorkingHours: this.#inWorkingHours(now, workingDays),
+ utcOffset: this.#utcOffset(offset),
+ members: [member],
+ };
+ groupedTimezones.push(groupedTimezone);
+ }
+ }
+ });
+
+ groupedTimezones = groupedTimezones
+ .sortBy("offset")
+ .filter((g) => g.members.length);
+
+ let newDayIndex;
+ groupedTimezones.forEach((groupedTimezone, index) => {
+ if (index > 0) {
+ if (
+ groupedTimezones[index - 1].nowWithOffset.format("dddd") !==
+ groupedTimezone.nowWithOffset.format("dddd")
+ ) {
+ newDayIndex = index;
+ }
+ }
+ });
+
+ if (newDayIndex) {
+ groupedTimezones.splice(newDayIndex, 0, {
+ type: "discourse-group-timezone-new-day",
+ beforeDate:
+ groupedTimezones[newDayIndex - 1].nowWithOffset.format("dddd"),
+ afterDate: groupedTimezones[newDayIndex].nowWithOffset.format("dddd"),
+ });
+ }
+
+ return groupedTimezones;
+ }
+
+ #shouldAddMemberToGroup(filter, member) {
+ if (filter) {
+ filter = filter.toLowerCase();
+ if (
+ member.username.toLowerCase().indexOf(filter) > -1 ||
+ (member.name && member.name.toLowerCase().indexOf(filter) > -1)
+ ) {
+ return true;
+ }
+ } else {
+ return true;
+ }
+
+ return false;
+ }
+
+ #roundMoment(date) {
+ if (this.localTimeOffset) {
+ date = roundTime(date);
+ }
+
+ return date;
+ }
+
+ #closeToWorkingHours(moment, workingDays) {
+ const hours = moment.hours();
+ const startHour = this.siteSettings.working_day_start_hour;
+ const endHour = this.siteSettings.working_day_end_hour;
+ const extension = this.siteSettings.close_to_working_day_hours_extension;
+
+ return (
+ ((hours >= Math.max(startHour - extension, 0) && hours <= startHour) ||
+ (hours <= Math.min(endHour + extension, 23) && hours >= endHour)) &&
+ workingDays.includes(moment.isoWeekday())
+ );
+ }
+
+ #inWorkingHours(moment, workingDays) {
+ const hours = moment.hours();
+ return (
+ hours > this.siteSettings.working_day_start_hour &&
+ hours < this.siteSettings.working_day_end_hour &&
+ workingDays.includes(moment.isoWeekday())
+ );
+ }
+
+ #utcOffset(offset) {
+ const sign = Math.sign(offset) === 1 ? "+" : "-";
+ offset = Math.abs(offset);
+ let hours = Math.floor(offset / 60).toString();
+ hours = hours.length === 1 ? `0${hours}` : hours;
+ let minutes = (offset % 60).toString();
+ minutes = minutes.length === 1 ? `:${minutes}0` : `:${minutes}`;
+ return `${sign}${hours.replace(/^0(\d)/, "$1")}${minutes.replace(
+ /:00$/,
+ ""
+ )}`.replace(/-0/, nbsp);
+ }
+
+ #workingDays() {
+ const enMoment = moment().locale("en");
+ const getIsoWeekday = (day) =>
+ enMoment.localeData()._weekdays.indexOf(day) || 7;
+ return this.siteSettings.working_days
+ .split("|")
+ .filter(Boolean)
+ .map((x) => getIsoWeekday(x));
+ }
+
+ @action
+ handleFilterChange(event) {
+ this.filter = event.target.value;
+ }
+
+
+
+
+ {{#each this.groupedTimezones key="identifier" as |groupedTimezone|}}
+ {{#if (eq groupedTimezone.type "discourse-group-timezone-new-day")}}
+
+ {{else}}
+
+ {{/if}}
+ {{/each}}
+
+
+}
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/components/group-timezones/new-day.gjs b/plugins/discourse-calendar/assets/javascripts/discourse/components/group-timezones/new-day.gjs
new file mode 100644
index 00000000000..121c7cb931d
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/components/group-timezones/new-day.gjs
@@ -0,0 +1,16 @@
+import icon from "discourse/helpers/d-icon";
+
+const NewDay =
+
+
+ {{icon "chevron-left"}}
+ {{@beforeDate}}
+
+
+ {{@afterDate}}
+ {{icon "chevron-right"}}
+
+
+;
+
+export default NewDay;
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/components/group-timezones/time-traveller.gjs b/plugins/discourse-calendar/assets/javascripts/discourse/components/group-timezones/time-traveller.gjs
new file mode 100644
index 00000000000..c6f07176d9d
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/components/group-timezones/time-traveller.gjs
@@ -0,0 +1,58 @@
+import Component from "@glimmer/component";
+import { on } from "@ember/modifier";
+import { action } from "@ember/object";
+import { not } from "truth-helpers";
+import DButton from "discourse/components/d-button";
+import roundTime from "../../lib/round-time";
+
+export default class TimeTraveller extends Component {
+ get localTimeWithOffset() {
+ let date = moment().add(this.args.localTimeOffset, "minutes");
+
+ if (this.args.localTimeOffset) {
+ date = roundTime(date);
+ }
+
+ return date.format("HH:mm");
+ }
+
+ @action
+ reset() {
+ this.args.setOffset(0);
+ }
+
+ @action
+ sliderMoved(event) {
+ const value = parseInt(event.target.value, 10);
+ const offset = value * 15;
+ this.args.setOffset(offset);
+ }
+
+
+
+
+ {{this.localTimeWithOffset}}
+
+
+
+
+
+
+
+
+
+
+
+}
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/components/group-timezones/timezone.gjs b/plugins/discourse-calendar/assets/javascripts/discourse/components/group-timezones/timezone.gjs
new file mode 100644
index 00000000000..2e1f854a511
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/components/group-timezones/timezone.gjs
@@ -0,0 +1,44 @@
+import Component from "@glimmer/component";
+import UserAvatar from "discourse/components/user-avatar";
+import concatClass from "discourse/helpers/concat-class";
+
+export default class GroupTimezone extends Component {
+ get formattedTime() {
+ return this.args.groupedTimezone.nowWithOffset.format("LT");
+ }
+
+
+
+
+
+ {{this.formattedTime}}
+
+
+ {{@groupedTimezone.utcOffset}}
+
+
+
+ {{#each @groupedTimezone.members key="username" as |member|}}
+ -
+
+
+ {{/each}}
+
+
+
+}
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/components/modal/post-event-builder.gjs b/plugins/discourse-calendar/assets/javascripts/discourse/components/modal/post-event-builder.gjs
new file mode 100644
index 00000000000..f2438c59ea5
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/components/modal/post-event-builder.gjs
@@ -0,0 +1,690 @@
+import Component from "@glimmer/component";
+import { tracked } from "@glimmer/tracking";
+import { Input, Textarea } from "@ember/component";
+import { concat, fn, get } from "@ember/helper";
+import { on } from "@ember/modifier";
+import { action } from "@ember/object";
+import { service } from "@ember/service";
+import { eq } from "truth-helpers";
+import ConditionalLoadingSection from "discourse/components/conditional-loading-section";
+import DButton from "discourse/components/d-button";
+import DModal from "discourse/components/d-modal";
+import DateInput from "discourse/components/date-input";
+import DateTimeInputRange from "discourse/components/date-time-input-range";
+import GroupSelector from "discourse/components/group-selector";
+import PluginOutlet from "discourse/components/plugin-outlet";
+import RadioButton from "discourse/components/radio-button";
+import lazyHash from "discourse/helpers/lazy-hash";
+import { extractError } from "discourse/lib/ajax-error";
+import { cook } from "discourse/lib/text";
+import Group from "discourse/models/group";
+import { i18n } from "discourse-i18n";
+import ComboBox from "select-kit/components/combo-box";
+import TimezoneInput from "select-kit/components/timezone-input";
+import { buildParams, replaceRaw } from "../../lib/raw-event-helper";
+import EventField from "../event-field";
+
+export default class PostEventBuilder extends Component {
+ @service dialog;
+ @service siteSettings;
+ @service store;
+ @service currentUser;
+
+ @tracked flash = null;
+ @tracked isSaving = false;
+
+ @tracked startsAt = moment(this.event.startsAt).tz(
+ this.event.timezone || "UTC"
+ );
+
+ @tracked
+ endsAt =
+ this.event.endsAt &&
+ moment(this.event.endsAt).tz(this.event.timezone || "UTC");
+
+ get recurrenceUntil() {
+ return (
+ this.event.recurrenceUntil &&
+ moment(this.event.recurrenceUntil).tz(this.event.timezone || "UTC")
+ );
+ }
+
+ get event() {
+ return this.args.model.event;
+ }
+
+ get reminderTypes() {
+ return [
+ {
+ value: "notification",
+ name: i18n(
+ "discourse_post_event.builder_modal.reminders.types.notification"
+ ),
+ },
+ {
+ value: "bumpTopic",
+ name: i18n(
+ "discourse_post_event.builder_modal.reminders.types.bump_topic"
+ ),
+ },
+ ];
+ }
+
+ get reminderUnits() {
+ return [
+ {
+ value: "minutes",
+ name: i18n(
+ "discourse_post_event.builder_modal.reminders.units.minutes"
+ ),
+ },
+ {
+ value: "hours",
+ name: i18n("discourse_post_event.builder_modal.reminders.units.hours"),
+ },
+ {
+ value: "days",
+ name: i18n("discourse_post_event.builder_modal.reminders.units.days"),
+ },
+ {
+ value: "weeks",
+ name: i18n("discourse_post_event.builder_modal.reminders.units.weeks"),
+ },
+ ];
+ }
+
+ get reminderPeriods() {
+ return [
+ {
+ value: "before",
+ name: i18n(
+ "discourse_post_event.builder_modal.reminders.periods.before"
+ ),
+ },
+ {
+ value: "after",
+ name: i18n(
+ "discourse_post_event.builder_modal.reminders.periods.after"
+ ),
+ },
+ ];
+ }
+
+ get shouldRenderUrl() {
+ return this.args.model.event.url !== undefined;
+ }
+
+ get availableRecurrences() {
+ return [
+ {
+ id: "every_day",
+ name: i18n("discourse_post_event.builder_modal.recurrence.every_day"),
+ },
+ {
+ id: "every_month",
+ name: i18n("discourse_post_event.builder_modal.recurrence.every_month"),
+ },
+ {
+ id: "every_weekday",
+ name: i18n(
+ "discourse_post_event.builder_modal.recurrence.every_weekday"
+ ),
+ },
+ {
+ id: "every_week",
+ name: i18n("discourse_post_event.builder_modal.recurrence.every_week"),
+ },
+ {
+ id: "every_two_weeks",
+ name: i18n(
+ "discourse_post_event.builder_modal.recurrence.every_two_weeks"
+ ),
+ },
+ {
+ id: "every_four_weeks",
+ name: i18n(
+ "discourse_post_event.builder_modal.recurrence.every_four_weeks"
+ ),
+ },
+ ];
+ }
+
+ get allowedCustomFields() {
+ return this.siteSettings.discourse_post_event_allowed_custom_fields
+ .split("|")
+ .filter(Boolean);
+ }
+
+ get addReminderDisabled() {
+ return this.event.reminders?.length >= 5;
+ }
+
+ get showChat() {
+ // As of June 2025, chat channel creation is only available to admins and moderators
+ return (
+ this.siteSettings.chat_enabled &&
+ (this.currentUser.admin || this.currentUser.moderator)
+ );
+ }
+
+ @action
+ groupFinder(term) {
+ return Group.findAll({ term, ignore_automatic: true });
+ }
+
+ @action
+ setCustomField(field, e) {
+ this.event.customFields[field] = e.target.value;
+ }
+
+ @action
+ onChangeDates(dates) {
+ this.event.startsAt = dates.from;
+ this.event.endsAt = dates.to;
+ this.startsAt = dates.from;
+ this.endsAt = dates.to;
+ }
+
+ @action
+ onChangeStatus(newStatus) {
+ this.event.rawInvitees = [];
+ this.event.status = newStatus;
+ }
+
+ @action
+ setRecurrence(newRecurrence) {
+ if (!newRecurrence) {
+ this.event.recurrence = null;
+ this.event.recurrenceUntil = null;
+ return;
+ }
+
+ this.event.recurrence = newRecurrence;
+ }
+
+ @action
+ setRecurrenceUntil(until) {
+ if (!until) {
+ this.event.recurrenceUntil = null;
+ } else {
+ this.event.recurrenceUntil = moment(until).endOf("day").toDate();
+ }
+ }
+
+ @action
+ setRawInvitees(_, newInvitees) {
+ this.event.rawInvitees = newInvitees;
+ }
+
+ @action
+ setNewTimezone(newTz) {
+ this.event.timezone = newTz;
+ this.event.startsAt = moment.tz(
+ this.startsAt.format("YYYY-MM-DDTHH:mm"),
+ newTz
+ );
+ this.event.endsAt = this.endsAt
+ ? moment.tz(this.endsAt.format("YYYY-MM-DDTHH:mm"), newTz)
+ : null;
+ this.startsAt = moment(this.event.startsAt).tz(newTz);
+ this.endsAt = this.event.endsAt
+ ? moment(this.event.endsAt).tz(newTz)
+ : null;
+ }
+
+ @action
+ async destroyPostEvent() {
+ try {
+ const confirmResult = await this.dialog.yesNoConfirm({
+ message: "Confirm delete",
+ });
+
+ if (confirmResult) {
+ const post = await this.store.find("post", this.event.id);
+ const raw = post.raw;
+ const newRaw = this._removeRawEvent(raw);
+ const props = {
+ raw: newRaw,
+ edit_reason: "Destroy event",
+ };
+
+ const cooked = await cook(newRaw);
+ props.cooked = cooked.string;
+
+ const result = await post.save(props);
+ if (result) {
+ this.args.closeModal();
+ }
+ }
+ } catch (e) {
+ this.flash = extractError(e);
+ }
+ }
+
+ @action
+ createEvent() {
+ if (!this.startsAt) {
+ this.args.closeModal();
+ return;
+ }
+
+ const eventParams = buildParams(
+ this.startsAt,
+ this.endsAt,
+ this.event,
+ this.siteSettings
+ );
+ const markdownParams = [];
+ Object.keys(eventParams).forEach((key) => {
+ let value = eventParams[key];
+ markdownParams.push(`${key}="${value}"`);
+ });
+
+ this.args.model.toolbarEvent.addText(
+ `[event ${markdownParams.join(" ")}]\n[/event]`
+ );
+ this.args.closeModal();
+ }
+
+ @action
+ async updateEvent() {
+ try {
+ this.isSaving = true;
+
+ const post = await this.store.find("post", this.event.id);
+ const raw = post.raw;
+ const eventParams = buildParams(
+ this.startsAt,
+ this.endsAt,
+ this.event,
+ this.siteSettings
+ );
+ const newRaw = replaceRaw(eventParams, raw);
+ if (newRaw) {
+ const props = {
+ raw: newRaw,
+ edit_reason: i18n("discourse_post_event.edit_reason"),
+ };
+
+ const cooked = await cook(newRaw);
+ props.cooked = cooked.string;
+
+ const result = await post.save(props);
+ if (result) {
+ this.args.closeModal();
+ }
+ }
+ } catch (e) {
+ this.flash = extractError(e);
+ } finally {
+ this.isSaving = false;
+ }
+ }
+
+ _removeRawEvent(raw) {
+ const eventRegex = new RegExp(`\\[event\\s(.*?)\\]\\n\\[\\/event\\]`, "m");
+ return raw.replace(eventRegex, "");
+ }
+
+
+
+ <:body>
+
+
+
+
+ <:footer>
+ {{#if @model.event.id}}
+
+
+
+ {{else}}
+
+ {{/if}}
+
+
+
+}
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/components/modal/post-event-bulk-invite.gjs b/plugins/discourse-calendar/assets/javascripts/discourse/components/modal/post-event-bulk-invite.gjs
new file mode 100644
index 00000000000..cfaf2a379b9
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/components/modal/post-event-bulk-invite.gjs
@@ -0,0 +1,227 @@
+import Component from "@glimmer/component";
+import { tracked } from "@glimmer/tracking";
+import { concat, fn, hash } from "@ember/helper";
+import EmberObject, { action } from "@ember/object";
+import { service } from "@ember/service";
+import { isPresent } from "@ember/utils";
+import { TrackedArray } from "@ember-compat/tracked-built-ins";
+import DButton from "discourse/components/d-button";
+import DModal from "discourse/components/d-modal";
+import GroupSelector from "discourse/components/group-selector";
+import { ajax } from "discourse/lib/ajax";
+import { extractError } from "discourse/lib/ajax-error";
+import Group from "discourse/models/group";
+import { i18n } from "discourse-i18n";
+import ComboBox from "select-kit/components/combo-box";
+import EmailGroupUserChooser from "select-kit/components/email-group-user-chooser";
+import BulkInviteSampleCsvFile from "../bulk-invite-sample-csv-file";
+import CsvUploader from "../csv-uploader";
+
+export default class PostEventBulkInvite extends Component {
+ @service dialog;
+
+ @tracked
+ bulkInvites = new TrackedArray([
+ EmberObject.create({ identifier: null, attendance: "unknown" }),
+ ]);
+ @tracked bulkInviteDisabled = true;
+ @tracked flash = null;
+
+ get bulkInviteStatuses() {
+ return [
+ {
+ label: i18n("discourse_post_event.models.invitee.status.unknown"),
+ name: "unknown",
+ },
+ {
+ label: i18n("discourse_post_event.models.invitee.status.going"),
+ name: "going",
+ },
+ {
+ label: i18n("discourse_post_event.models.invitee.status.not_going"),
+ name: "not_going",
+ },
+ {
+ label: i18n("discourse_post_event.models.invitee.status.interested"),
+ name: "interested",
+ },
+ ];
+ }
+
+ @action
+ groupFinder(term) {
+ return Group.findAll({ term, ignore_automatic: true });
+ }
+
+ @action
+ setBulkInviteDisabled() {
+ this.bulkInviteDisabled =
+ this.bulkInvites.filter((x) => isPresent(x.identifier)).length === 0;
+ }
+
+ @action
+ async sendBulkInvites() {
+ try {
+ const response = await ajax(
+ `/discourse-post-event/events/${this.args.model.event.id}/bulk-invite.json`,
+ {
+ type: "POST",
+ dataType: "json",
+ contentType: "application/json",
+ data: JSON.stringify({
+ invitees: this.bulkInvites.filter((x) => isPresent(x.identifier)),
+ }),
+ }
+ );
+
+ if (response.success) {
+ this.args.closeModal();
+ }
+ } catch (e) {
+ this.flash = extractError(e);
+ }
+ }
+
+ @action
+ removeBulkInvite(bulkInvite) {
+ this.bulkInvites.removeObject(bulkInvite);
+
+ if (!this.bulkInvites.length) {
+ this.bulkInvites.pushObject(
+ EmberObject.create({ identifier: null, attendance: "unknown" })
+ );
+ }
+ }
+
+ @action
+ addBulkInvite() {
+ const attendance =
+ this.bulkInvites[this.bulkInvites.length - 1]?.attendance || "unknown";
+ this.bulkInvites.pushObject(
+ EmberObject.create({ identifier: null, attendance })
+ );
+ }
+
+ @action
+ async uploadDone() {
+ await this.dialog.alert(
+ i18n("discourse_post_event.bulk_invite_modal.success")
+ );
+ this.args.closeModal();
+ }
+
+ @action
+ updateInviteIdentifier(bulkInvite, selected) {
+ bulkInvite.set("identifier", selected[0]);
+ this.setBulkInviteDisabled();
+ }
+
+ @action
+ updateBulkGroupInviteIdentifier(bulkInvite, _, groupNames) {
+ bulkInvite.set("identifier", groupNames[0]);
+ this.setBulkInviteDisabled();
+ }
+
+
+
+ <:body>
+
+
+ {{i18n
+ (concat
+ "discourse_post_event.bulk_invite_modal.description_"
+ @model.event.status
+ )
+ }}
+
+
{{i18n
+ "discourse_post_event.bulk_invite_modal.inline_title"
+ }}
+
+
+ {{#each this.bulkInvites as |bulkInvite|}}
+
+ {{#if @model.event.isPrivate}}
+
+ {{/if}}
+ {{#if @model.event.isPublic}}
+
+ {{/if}}
+
+
+
+
+
+ {{/each}}
+
+
+
+
+
+
+
+
+
+
{{i18n "discourse_post_event.bulk_invite_modal.csv_title"}}
+
+
+
+
+
+
+
+
+
+
+}
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/components/modal/post-event-invite-user-or-group.gjs b/plugins/discourse-calendar/assets/javascripts/discourse/components/modal/post-event-invite-user-or-group.gjs
new file mode 100644
index 00000000000..30c79795044
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/components/modal/post-event-invite-user-or-group.gjs
@@ -0,0 +1,64 @@
+import Component from "@glimmer/component";
+import { tracked } from "@glimmer/tracking";
+import { hash } from "@ember/helper";
+import { action } from "@ember/object";
+import DButton from "discourse/components/d-button";
+import DModal from "discourse/components/d-modal";
+import { ajax } from "discourse/lib/ajax";
+import { extractError } from "discourse/lib/ajax-error";
+import { i18n } from "discourse-i18n";
+import EmailGroupUserChooser from "select-kit/components/email-group-user-chooser";
+import EventField from "../event-field";
+
+export default class PostEventInviteUserOrGroup extends Component {
+ @tracked invitedNames = [];
+ @tracked flash = null;
+
+ @action
+ async invite() {
+ try {
+ await ajax(
+ `/discourse-post-event/events/${this.args.model.event.id}/invite.json`,
+ {
+ data: { invites: this.invitedNames },
+ type: "POST",
+ }
+ );
+ this.args.closeModal();
+ } catch (e) {
+ this.flash = extractError(e);
+ }
+ }
+
+
+
+ <:body>
+
+
+ <:footer>
+
+
+
+
+}
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/components/modal/post-event-invitees/index.gjs b/plugins/discourse-calendar/assets/javascripts/discourse/components/modal/post-event-invitees/index.gjs
new file mode 100644
index 00000000000..10c90b0f5f2
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/components/modal/post-event-invitees/index.gjs
@@ -0,0 +1,157 @@
+import Component from "@glimmer/component";
+import { tracked } from "@glimmer/tracking";
+import { fn } from "@ember/helper";
+import { on } from "@ember/modifier";
+import { action } from "@ember/object";
+import { service } from "@ember/service";
+import { or } from "truth-helpers";
+import ConditionalLoadingSpinner from "discourse/components/conditional-loading-spinner";
+import DButton from "discourse/components/d-button";
+import DModal from "discourse/components/d-modal";
+import concatClass from "discourse/helpers/concat-class";
+import { debounce } from "discourse/lib/decorators";
+import { i18n } from "discourse-i18n";
+import ToggleInvitees from "../../toggle-invitees";
+import User from "./user";
+
+export default class PostEventInviteesModal extends Component {
+ @service store;
+ @service discoursePostEventApi;
+
+ @tracked filter;
+ @tracked isLoading = false;
+ @tracked type = "going";
+ @tracked inviteesList;
+
+ constructor() {
+ super(...arguments);
+ this._fetchInvitees();
+ }
+
+ get hasSuggestedUsers() {
+ return this.inviteesList?.suggestedUsers?.length > 0;
+ }
+
+ get hasResults() {
+ return this.inviteesList?.invitees?.length > 0 || this.hasSuggestedUsers;
+ }
+
+ get title() {
+ return i18n(
+ `discourse_post_event.invitees_modal.${
+ this.args.model.title || "title_invited"
+ }`
+ );
+ }
+
+ @action
+ toggleType(type) {
+ this.type = type;
+ this._fetchInvitees(this.filter);
+ }
+
+ @debounce(250)
+ onFilterChanged(event) {
+ this.filter = event.target.value;
+ this._fetchInvitees(this.filter);
+ }
+
+ @action
+ async removeInvitee(invitee) {
+ await this.discoursePostEventApi.leaveEvent(this.args.model.event, invitee);
+
+ this.inviteesList.remove(invitee);
+ }
+
+ @action
+ async addInvitee(user) {
+ const invitee = await this.discoursePostEventApi.joinEvent(
+ this.args.model.event,
+ {
+ status: this.type,
+ user_id: user.id,
+ }
+ );
+
+ this.inviteesList.add(invitee);
+ }
+
+ async _fetchInvitees(filter) {
+ try {
+ this.isLoading = true;
+
+ this.inviteesList = await this.discoursePostEventApi.listEventInvitees(
+ this.args.model.event,
+ { type: this.type, filter }
+ );
+ } finally {
+ this.isLoading = false;
+ }
+ }
+
+
+
+ <:body>
+
+
+
+
+ {{#if this.hasResults}}
+
+ {{#each this.inviteesList.invitees as |invitee|}}
+ -
+
+ {{#if @model.event.canActOnDiscoursePostEvent}}
+
+ {{/if}}
+
+ {{/each}}
+
+ {{#if this.hasSuggestedUsers}}
+
+ {{#each this.inviteesList.suggestedUsers as |user|}}
+ -
+
+
+
+ {{/each}}
+
+ {{/if}}
+ {{else}}
+
+ {{i18n "discourse_post_event.models.invitee.no_users"}}
+
+ {{/if}}
+
+
+
+
+}
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/components/modal/post-event-invitees/user.gjs b/plugins/discourse-calendar/assets/javascripts/discourse/components/modal/post-event-invitees/user.gjs
new file mode 100644
index 00000000000..d923300c4e5
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/components/modal/post-event-invitees/user.gjs
@@ -0,0 +1,16 @@
+import avatar from "discourse/helpers/avatar";
+import { userPath } from "discourse/lib/url";
+import { formatUsername } from "discourse/lib/utilities";
+
+const User =
+
+
+ {{avatar @user imageSize="medium"}}
+
+ {{formatUsername @user.username}}
+
+
+
+;
+
+export default User;
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/components/region-input.js b/plugins/discourse-calendar/assets/javascripts/discourse/components/region-input.js
new file mode 100644
index 00000000000..372bf508946
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/components/region-input.js
@@ -0,0 +1,44 @@
+import { computed } from "@ember/object";
+import { classNames } from "@ember-decorators/component";
+import { i18n } from "discourse-i18n";
+import ComboBoxComponent from "select-kit/components/combo-box";
+import {
+ pluginApiIdentifiers,
+ selectKitOptions,
+} from "select-kit/components/select-kit";
+import { HOLIDAY_REGIONS } from "../lib/regions";
+
+@selectKitOptions({
+ filterable: true,
+ allowAny: false,
+})
+@pluginApiIdentifiers("timezone-input")
+@classNames("timezone-input", "region-input")
+export default class RegionInput extends ComboBoxComponent {
+ allowNoneRegion = false;
+
+ @computed
+ get content() {
+ const localeNames = {};
+ let regions = [];
+
+ JSON.parse(this.siteSettings.available_locales).forEach((locale) => {
+ localeNames[locale.value] = locale.name;
+ });
+
+ if (this.allowNoneRegion === true) {
+ regions.push({
+ name: i18n("discourse_calendar.region.none"),
+ id: null,
+ });
+ }
+
+ regions = regions.concat(
+ HOLIDAY_REGIONS.map((region) => ({
+ name: i18n(`discourse_calendar.region.names.${region}`),
+ id: region,
+ })).sort((a, b) => a.name.localeCompare(b.name))
+ );
+ return regions;
+ }
+}
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/components/toggle-invitees.gjs b/plugins/discourse-calendar/assets/javascripts/discourse/components/toggle-invitees.gjs
new file mode 100644
index 00000000000..51f6d15072e
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/components/toggle-invitees.gjs
@@ -0,0 +1,37 @@
+import { fn } from "@ember/helper";
+import { eq } from "truth-helpers";
+import DButton from "discourse/components/d-button";
+import concatClass from "discourse/helpers/concat-class";
+
+const ToggleInvitees =
+
+
+
+
+
+
+
+;
+
+export default ToggleInvitees;
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/components/upcoming-events-calendar.gjs b/plugins/discourse-calendar/assets/javascripts/discourse/components/upcoming-events-calendar.gjs
new file mode 100644
index 00000000000..11e0d925787
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/components/upcoming-events-calendar.gjs
@@ -0,0 +1,212 @@
+import Component from "@glimmer/component";
+import { action } from "@ember/object";
+import didInsert from "@ember/render-modifiers/modifiers/did-insert";
+import willDestroy from "@ember/render-modifiers/modifiers/will-destroy";
+import { LinkTo } from "@ember/routing";
+import { schedule } from "@ember/runloop";
+import { service } from "@ember/service";
+import { Promise } from "rsvp";
+import getURL from "discourse/lib/get-url";
+import loadScript from "discourse/lib/load-script";
+import Category from "discourse/models/category";
+import { i18n } from "discourse-i18n";
+import { formatEventName } from "../helpers/format-event-name";
+import addRecurrentEvents from "../lib/add-recurrent-events";
+import fullCalendarDefaultOptions from "../lib/full-calendar-default-options";
+import { isNotFullDayEvent } from "../lib/guess-best-date-format";
+
+export default class UpcomingEventsCalendar extends Component {
+ @service currentUser;
+ @service site;
+ @service router;
+
+ _calendar = null;
+
+ get displayFilters() {
+ return this.currentUser && this.args.controller;
+ }
+
+ @action
+ teardown() {
+ this._calendar?.destroy?.();
+ this._calendar = null;
+ }
+
+ @action
+ async renderCalendar() {
+ const siteSettings = this.site.siteSettings;
+ const isMobileView = this.site.mobileView;
+
+ const calendarNode = document.getElementById("upcoming-events-calendar");
+ if (!calendarNode) {
+ return;
+ }
+
+ calendarNode.innerHTML = "";
+
+ await this._loadCalendar();
+
+ const view =
+ this.args.controller?.view || (isMobileView ? "listNextYear" : "month");
+
+ const fullCalendar = new window.FullCalendar.Calendar(calendarNode, {
+ ...fullCalendarDefaultOptions(),
+ timeZone: this.currentUser?.user_option?.timezone || "local",
+ firstDay: 1,
+ height: "auto",
+ defaultView: view,
+ views: {
+ listNextYear: {
+ type: "list",
+ duration: { days: 365 },
+ buttonText: "list",
+ listDayFormat: {
+ month: "long",
+ year: "numeric",
+ day: "numeric",
+ weekday: "long",
+ },
+ },
+ },
+ header: {
+ left: "prev,next today",
+ center: "title",
+ right: "month,basicWeek,listNextYear",
+ },
+ datesRender: (info) => {
+ // this is renamed in FullCalendar v5 / v6 to datesSet
+ // in unit tests we skip
+ if (this.router?.transitionTo) {
+ this.router.transitionTo({ queryParams: { view: info.view.type } });
+ }
+ },
+ eventPositioned: (info) => {
+ if (siteSettings.events_max_rows === 0) {
+ return;
+ }
+
+ let fcContent = info.el.querySelector(".fc-content");
+
+ if (!fcContent) {
+ return;
+ }
+
+ let computedStyle = window.getComputedStyle(fcContent);
+ let lineHeight = parseInt(computedStyle.lineHeight, 10);
+
+ if (lineHeight === 0) {
+ lineHeight = 20;
+ }
+ let maxHeight = lineHeight * siteSettings.events_max_rows;
+
+ if (fcContent) {
+ fcContent.style.maxHeight = `${maxHeight}px`;
+ }
+
+ let fcTitle = info.el.querySelector(".fc-title");
+ if (fcTitle) {
+ fcTitle.style.overflow = "hidden";
+ fcTitle.style.whiteSpace = "pre-wrap";
+ }
+ fullCalendar.updateSize();
+ },
+ });
+ this._calendar = fullCalendar;
+
+ const tagsColorsMap = JSON.parse(siteSettings.map_events_to_color);
+
+ const resolvedEvents = this.args.events
+ ? await this.args.events
+ : await this.args.controller.model;
+ const originalEventAndRecurrents = addRecurrentEvents(resolvedEvents);
+
+ (originalEventAndRecurrents || []).forEach((event) => {
+ const { startsAt, endsAt, post, categoryId } = event;
+
+ let backgroundColor;
+
+ if (post.topic.tags) {
+ const tagColorEntry = tagsColorsMap.find(
+ (entry) =>
+ entry.type === "tag" && post.topic.tags.includes(entry.slug)
+ );
+ backgroundColor = tagColorEntry?.color;
+ }
+
+ if (!backgroundColor) {
+ const categoryColorEntry = tagsColorsMap.find(
+ (entry) =>
+ entry.type === "category" && entry.slug === post.topic.category_slug
+ );
+ backgroundColor = categoryColorEntry?.color;
+ }
+
+ const categoryColor = Category.findById(categoryId)?.color;
+ if (!backgroundColor && categoryColor) {
+ backgroundColor = `#${categoryColor}`;
+ }
+
+ let classNames;
+ if (moment(endsAt || startsAt).isBefore(moment())) {
+ classNames = "fc-past-event";
+ }
+
+ this._calendar.addEvent({
+ title: formatEventName(event, this.currentUser?.user_option?.timezone),
+ start: startsAt,
+ end: endsAt || startsAt,
+ allDay: !isNotFullDayEvent(moment(startsAt), moment(endsAt)),
+ url: getURL(`/t/-/${post.topic.id}/${post.post_number}`),
+ backgroundColor,
+ classNames,
+ });
+ });
+
+ this._calendar.render();
+ }
+
+ _loadCalendar() {
+ return new Promise((resolve) => {
+ loadScript(
+ "/plugins/discourse-calendar/javascripts/fullcalendar-with-moment-timezone.min.js"
+ ).then(() => {
+ schedule("afterRender", () => {
+ if (this.isDestroying || this.isDestroyed) {
+ return;
+ }
+
+ resolve();
+ });
+ });
+ });
+ }
+
+
+ {{#if this.displayFilters}}
+
+ -
+
+ {{i18n "discourse_post_event.upcoming_events.all_events"}}
+
+
+ -
+
+ {{i18n "discourse_post_event.upcoming_events.my_events"}}
+
+
+
+ {{/if}}
+
+
+
+}
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/components/upcoming-events-list.gjs b/plugins/discourse-calendar/assets/javascripts/discourse/components/upcoming-events-list.gjs
new file mode 100644
index 00000000000..6eb7d6aac65
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/components/upcoming-events-list.gjs
@@ -0,0 +1,222 @@
+import Component from "@glimmer/component";
+import { tracked } from "@glimmer/tracking";
+import { action } from "@ember/object";
+import { LinkTo } from "@ember/routing";
+import { service } from "@ember/service";
+import { or } from "truth-helpers";
+import ConditionalLoadingSpinner from "discourse/components/conditional-loading-spinner";
+import DButton from "discourse/components/d-button";
+import PluginOutlet from "discourse/components/plugin-outlet";
+import { ajax } from "discourse/lib/ajax";
+import { i18n } from "discourse-i18n";
+import { isNotFullDayEvent } from "../lib/guess-best-date-format";
+
+export const DEFAULT_TIME_FORMAT = "LT";
+const DEFAULT_UPCOMING_DAYS = 180;
+const DEFAULT_COUNT = 8;
+
+function addToResult(date, item, result) {
+ const day = date.format("DD");
+ const monthKey = date.format("YYYY-MM");
+
+ result[monthKey] = result[monthKey] ?? {};
+ result[monthKey][day] = result[monthKey][day] ?? [];
+ result[monthKey][day].push(item);
+}
+
+export default class UpcomingEventsList extends Component {
+ @service appEvents;
+ @service siteSettings;
+ @service router;
+
+ @tracked isLoading = true;
+ @tracked hasError = false;
+ @tracked eventsByMonth = {};
+
+ timeFormat = this.args.params?.timeFormat ?? DEFAULT_TIME_FORMAT;
+ count = this.args.params?.count ?? DEFAULT_COUNT;
+ upcomingDays = this.args.params?.upcomingDays ?? DEFAULT_UPCOMING_DAYS;
+
+ emptyMessage = i18n("discourse_post_event.upcoming_events_list.empty");
+ allDayLabel = i18n("discourse_post_event.upcoming_events_list.all_day");
+ errorMessage = i18n("discourse_post_event.upcoming_events_list.error");
+ viewAllLabel = i18n("discourse_post_event.upcoming_events_list.view_all");
+
+ constructor() {
+ super(...arguments);
+ this.appEvents.on("page:changed", this, this.updateEventsList);
+ }
+
+ get categoryId() {
+ return this.router.currentRoute.attributes?.category?.id;
+ }
+
+ get hasEmptyResponse() {
+ return (
+ !this.isLoading &&
+ !this.hasError &&
+ Object.keys(this.eventsByMonth).length === 0
+ );
+ }
+
+ get title() {
+ const categorySlug = this.router.currentRoute.attributes?.category?.slug;
+ const titleSetting = this.siteSettings.map_events_title;
+
+ if (titleSetting === "") {
+ return i18n("discourse_post_event.upcoming_events_list.title");
+ }
+
+ const categories = JSON.parse(titleSetting).map(
+ ({ category_slug }) => category_slug
+ );
+
+ if (categories.includes(categorySlug)) {
+ const titleMap = JSON.parse(titleSetting);
+ const customTitleLookup = titleMap.find(
+ (o) => o.category_slug === categorySlug
+ );
+ return customTitleLookup?.custom_title;
+ } else {
+ return i18n("discourse_post_event.upcoming_events_list.title");
+ }
+ }
+
+ @action
+ async updateEventsList() {
+ this.isLoading = true;
+ this.hasError = false;
+
+ const data = {
+ limit: this.count,
+ before: moment().add(this.upcomingDays, "days").toISOString(),
+ };
+
+ if (this.categoryId) {
+ data.category_id = this.categoryId;
+ }
+
+ try {
+ const { events } = await ajax("/discourse-post-event/events", {
+ data,
+ });
+
+ this.eventsByMonth = this.groupByMonthAndDay(events);
+ } catch {
+ this.hasError = true;
+ } finally {
+ this.isLoading = false;
+ }
+ }
+
+ @action
+ formatTime({ starts_at, ends_at }) {
+ return isNotFullDayEvent(moment(starts_at), moment(ends_at))
+ ? moment(starts_at).format(this.timeFormat)
+ : this.allDayLabel;
+ }
+
+ @action
+ startsAtMonth(month, day) {
+ return moment(`${month}-${day}`).format("MMM");
+ }
+
+ @action
+ startsAtDay(month, day) {
+ return moment(`${month}-${day}`).format("D");
+ }
+
+ groupByMonthAndDay(data) {
+ return data.reduce((result, item) => {
+ const startDate = moment(item.starts_at);
+ const endDate = item.ends_at ? moment(item.ends_at) : null;
+ const today = moment();
+
+ if (!endDate) {
+ addToResult(startDate, item, result);
+ return result;
+ }
+
+ while (startDate.isSameOrBefore(endDate, "day")) {
+ if (startDate.isAfter(today)) {
+ addToResult(startDate, item, result);
+ }
+
+ startDate.add(1, "day");
+ }
+
+ return result;
+ }, {});
+ }
+
+
+
+
+ {{this.title}}
+
+
+
+
+
+
+
+}
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/connectors/before-topic-list-body/category-calendar.gjs b/plugins/discourse-calendar/assets/javascripts/discourse/connectors/before-topic-list-body/category-calendar.gjs
new file mode 100644
index 00000000000..ff982b81869
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/connectors/before-topic-list-body/category-calendar.gjs
@@ -0,0 +1,13 @@
+import Component from "@glimmer/component";
+
+export default class CategoryCalendar extends Component {
+ static shouldRender(_, ctx) {
+ return (
+ ctx.siteSettings.calendar_categories_outlet === "before-topic-list-body"
+ );
+ }
+
+
+
+
+}
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/connectors/category-custom-settings/show-event-category-sorting-settings.gjs b/plugins/discourse-calendar/assets/javascripts/discourse/connectors/category-custom-settings/show-event-category-sorting-settings.gjs
new file mode 100644
index 00000000000..7a048ad47e3
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/connectors/category-custom-settings/show-event-category-sorting-settings.gjs
@@ -0,0 +1,48 @@
+import Component, { Input } from "@ember/component";
+import { tagName } from "@ember-decorators/component";
+import { or } from "truth-helpers";
+import { i18n } from "discourse-i18n";
+
+@tagName("")
+export default class ShowEventCategorySortingSettings extends Component {
+
+ {{#if
+ (or
+ this.siteSettings.sort_categories_by_event_start_date_enabled
+ this.siteSettings.disable_resorting_on_categories_enabled
+ )
+ }}
+
+ {{i18n
+ "discourse_post_event.category.settings_sections.event_sorting"
+ }}
+
+ {{#if this.siteSettings.sort_categories_by_event_start_date_enabled}}
+
+ {{/if}}
+
+ {{#if this.siteSettings.disable_resorting_on_categories_enabled}}
+
+ {{/if}}
+
+ {{/if}}
+
+}
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/connectors/discovery-list-container-top/category-events-calendar.gjs b/plugins/discourse-calendar/assets/javascripts/discourse/connectors/discovery-list-container-top/category-events-calendar.gjs
new file mode 100644
index 00000000000..86bb2100082
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/connectors/discovery-list-container-top/category-events-calendar.gjs
@@ -0,0 +1,14 @@
+import Component from "@glimmer/component";
+
+export default class CategoryEventsCalendar extends Component {
+ static shouldRender(_, ctx) {
+ return (
+ ctx.siteSettings.calendar_categories_outlet ===
+ "discovery-list-container-top"
+ );
+ }
+
+
+
+
+}
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/connectors/header-topic-title-suffix/event-date-container.gjs b/plugins/discourse-calendar/assets/javascripts/discourse/connectors/header-topic-title-suffix/event-date-container.gjs
new file mode 100644
index 00000000000..fead52c6a92
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/connectors/header-topic-title-suffix/event-date-container.gjs
@@ -0,0 +1,7 @@
+import EventDate from "../../components/event-date";
+
+const EventDateContainer =
+
+;
+
+export default EventDateContainer;
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/connectors/topic-list-after-title/event-badge.gjs b/plugins/discourse-calendar/assets/javascripts/discourse/connectors/topic-list-after-title/event-badge.gjs
new file mode 100644
index 00000000000..c0efff2d7d7
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/connectors/topic-list-after-title/event-badge.gjs
@@ -0,0 +1,15 @@
+import Component from "@glimmer/component";
+import { service } from "@ember/service";
+import EventDate from "../../components/event-date";
+
+export default class EventBadge extends Component {
+ @service siteSettings;
+
+
+ {{~#if this.siteSettings.discourse_post_event_enabled~}}
+ {{~#if @outletArgs.topic.event_starts_at~}}
+
+ {{~/if~}}
+ {{~/if~}}
+
+}
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/connectors/user-custom-preferences/region.gjs b/plugins/discourse-calendar/assets/javascripts/discourse/connectors/user-custom-preferences/region.gjs
new file mode 100644
index 00000000000..018423f4baa
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/connectors/user-custom-preferences/region.gjs
@@ -0,0 +1,50 @@
+import Component from "@glimmer/component";
+import { action } from "@ember/object";
+import { service } from "@ember/service";
+import DButton from "discourse/components/d-button";
+import { i18n } from "discourse-i18n";
+import RegionInput from "../../components/region-input";
+import { TIME_ZONE_TO_REGION } from "../../lib/regions";
+
+export default class Region extends Component {
+ static shouldRender(args, component) {
+ return component.siteSettings.calendar_enabled;
+ }
+
+ @service siteSettings;
+
+ @action
+ onChange(value) {
+ this.args.outletArgs.model.set("custom_fields.holidays-region", value);
+ }
+
+ @action
+ useCurrentRegion() {
+ this.args.outletArgs.model.set(
+ "custom_fields.holidays-region",
+ TIME_ZONE_TO_REGION[moment.tz.guess()] || "us"
+ );
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+}
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/controllers/admin-plugins-calendar.js b/plugins/discourse-calendar/assets/javascripts/discourse/controllers/admin-plugins-calendar.js
new file mode 100644
index 00000000000..f5c3d83c828
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/controllers/admin-plugins-calendar.js
@@ -0,0 +1,28 @@
+import Controller from "@ember/controller";
+import { action } from "@ember/object";
+import { ajax } from "discourse/lib/ajax";
+import { popupAjaxError } from "discourse/lib/ajax-error";
+
+export default class AdminPluginsCalendarController extends Controller {
+ selectedRegion = null;
+ loading = false;
+
+ @action
+ async getHolidays(region_code) {
+ if (this.loading) {
+ return;
+ }
+
+ this.set("selectedRegion", region_code);
+ this.set("loading", true);
+
+ return ajax(
+ `/admin/discourse-calendar/holiday-regions/${region_code}/holidays`
+ )
+ .then((response) => {
+ this.model.set("holidays", response.holidays);
+ })
+ .catch(popupAjaxError)
+ .finally(() => this.set("loading", false));
+ }
+}
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/controllers/discourse-post-event-upcoming-events-index.js b/plugins/discourse-calendar/assets/javascripts/discourse/controllers/discourse-post-event-upcoming-events-index.js
new file mode 100644
index 00000000000..23c986f1ee7
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/controllers/discourse-post-event-upcoming-events-index.js
@@ -0,0 +1,5 @@
+import Controller from "@ember/controller";
+
+export default class DiscoursePostEventUpcomingEventsIndexController extends Controller {
+ queryParams = ["view"];
+}
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/controllers/discourse-post-event-upcoming-events-mine.js b/plugins/discourse-calendar/assets/javascripts/discourse/controllers/discourse-post-event-upcoming-events-mine.js
new file mode 100644
index 00000000000..db7bd2501d5
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/controllers/discourse-post-event-upcoming-events-mine.js
@@ -0,0 +1,5 @@
+import Controller from "@ember/controller";
+
+export default class DiscoursePostEventUpcomingEventsMineController extends Controller {
+ queryParams = ["view"];
+}
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/discourse-event-upcoming-events-route-map.js b/plugins/discourse-calendar/assets/javascripts/discourse/discourse-event-upcoming-events-route-map.js
new file mode 100644
index 00000000000..401de34aed2
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/discourse-event-upcoming-events-route-map.js
@@ -0,0 +1,10 @@
+export default function () {
+ this.route(
+ "discourse-post-event-upcoming-events",
+ { path: "/upcoming-events" },
+ function () {
+ this.route("index", { path: "/" });
+ this.route("mine");
+ }
+ );
+}
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/helpers/format-event-name.js b/plugins/discourse-calendar/assets/javascripts/discourse/helpers/format-event-name.js
new file mode 100644
index 00000000000..b06982bfba2
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/helpers/format-event-name.js
@@ -0,0 +1,28 @@
+import { i18n } from "discourse-i18n";
+
+function sameTimezoneOffset(timezone1, timezone2) {
+ if (!timezone1 || !timezone2) {
+ return false;
+ }
+
+ const offset1 = moment.tz(timezone1).utcOffset();
+ const offset2 = moment.tz(timezone2).utcOffset();
+ return offset1 === offset2;
+}
+
+export function formatEventName(event, userTimezone) {
+ let output = event.name || event.post.topic.title;
+
+ if (
+ event.showLocalTime &&
+ event.timezone &&
+ !sameTimezoneOffset(event.timezone, userTimezone)
+ ) {
+ output +=
+ ` (${i18n("discourse_calendar.local_time")}: ` +
+ moment(event.startsAt).tz(event.timezone).format("H:mma") +
+ ")";
+ }
+
+ return output;
+}
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/helpers/format-future-date.js b/plugins/discourse-calendar/assets/javascripts/discourse/helpers/format-future-date.js
new file mode 100644
index 00000000000..59071752f06
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/helpers/format-future-date.js
@@ -0,0 +1,8 @@
+import { htmlSafe } from "@ember/template";
+import guessDateFormat from "../lib/guess-best-date-format";
+
+export default function (date) {
+ date = moment.utc(date).tz(moment.tz.guess());
+ const format = guessDateFormat(date);
+ return htmlSafe(date.format(format));
+}
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/initializers/add-event-ui-builder.js b/plugins/discourse-calendar/assets/javascripts/discourse/initializers/add-event-ui-builder.js
new file mode 100644
index 00000000000..deaf65d8122
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/initializers/add-event-ui-builder.js
@@ -0,0 +1,51 @@
+import { withPluginApi } from "discourse/lib/plugin-api";
+import DiscoursePostEventEvent from "discourse/plugins/discourse-calendar/discourse/models/discourse-post-event-event";
+import PostEventBuilder from "../components/modal/post-event-builder";
+
+function initializeEventBuilder(api) {
+ const currentUser = api.getCurrentUser();
+ const modal = api.container.lookup("service:modal");
+
+ api.addComposerToolbarPopupMenuOption({
+ action: (toolbarEvent) => {
+ const event = DiscoursePostEventEvent.create({
+ status: "public",
+ starts_at: moment(),
+ timezone: moment.tz.guess(),
+ });
+
+ modal.show(PostEventBuilder, {
+ model: { event, toolbarEvent },
+ });
+ },
+ group: "insertions",
+ icon: "calendar-day",
+ label: "discourse_post_event.builder_modal.attach",
+ condition: (composer) => {
+ if (!currentUser || !currentUser.can_create_discourse_post_event) {
+ return false;
+ }
+
+ const composerModel = composer.model;
+ return (
+ composerModel &&
+ !composerModel.replyingToTopic &&
+ (composerModel.topicFirstPost ||
+ composerModel.creatingPrivateMessage ||
+ (composerModel.editingPost &&
+ composerModel.post &&
+ composerModel.post.post_number === 1))
+ );
+ },
+ });
+}
+
+export default {
+ name: "add-post-event-builder",
+ initialize(container) {
+ const siteSettings = container.lookup("service:site-settings");
+ if (siteSettings.discourse_post_event_enabled) {
+ withPluginApi("0.8.7", initializeEventBuilder);
+ }
+ },
+};
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/initializers/add-upcoming-events-to-sidebar.js b/plugins/discourse-calendar/assets/javascripts/discourse/initializers/add-upcoming-events-to-sidebar.js
new file mode 100644
index 00000000000..fa353030b78
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/initializers/add-upcoming-events-to-sidebar.js
@@ -0,0 +1,26 @@
+import { withPluginApi } from "discourse/lib/plugin-api";
+import { i18n } from "discourse-i18n";
+
+export default {
+ name: "add-upcoming-events-to-sidebar",
+
+ initialize(container) {
+ const siteSettings = container.lookup("service:site-settings");
+ if (
+ siteSettings.discourse_post_event_enabled &&
+ siteSettings.sidebar_show_upcoming_events
+ ) {
+ withPluginApi("0.8.7", (api) => {
+ api.addCommunitySectionLink((baseSectionLink) => {
+ return class UpcomingEventsSectionLink extends baseSectionLink {
+ name = "upcoming-events";
+ route = "discourse-post-event-upcoming-events";
+ text = i18n("discourse_post_event.upcoming_events.title");
+ title = i18n("discourse_post_event.upcoming_events.title");
+ defaultPrefixValue = "calendar-day";
+ };
+ });
+ });
+ }
+ },
+};
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/initializers/disable-sort.js b/plugins/discourse-calendar/assets/javascripts/discourse/initializers/disable-sort.js
new file mode 100644
index 00000000000..e0af2dee373
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/initializers/disable-sort.js
@@ -0,0 +1,24 @@
+import { withPluginApi } from "discourse/lib/plugin-api";
+
+export default {
+ name: "disable-sort",
+
+ initialize(container) {
+ withPluginApi("0.8", (api) => {
+ api.registerValueTransformer(
+ "topic-list-header-sortable-column",
+ ({ value, context }) => {
+ if (!value) {
+ return value;
+ }
+
+ const siteSettings = container.lookup("service:site-settings");
+ return !(
+ siteSettings.disable_resorting_on_categories_enabled &&
+ context.category?.custom_fields?.disable_topic_resorting
+ );
+ }
+ );
+ });
+ },
+};
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/initializers/discourse-calendar.js b/plugins/discourse-calendar/assets/javascripts/discourse/initializers/discourse-calendar.js
new file mode 100644
index 00000000000..d86e29eefed
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/initializers/discourse-calendar.js
@@ -0,0 +1,1043 @@
+import { isPresent } from "@ember/utils";
+import $ from "jquery";
+import { escape } from "pretty-text/sanitizer";
+import { Promise } from "rsvp";
+import { ajax } from "discourse/lib/ajax";
+import getURL from "discourse/lib/get-url";
+import { iconHTML } from "discourse/lib/icon-library";
+import loadScript from "discourse/lib/load-script";
+import { withPluginApi } from "discourse/lib/plugin-api";
+import { cook } from "discourse/lib/text";
+import DiscourseURL from "discourse/lib/url";
+import { escapeExpression } from "discourse/lib/utilities";
+import Category from "discourse/models/category";
+import { i18n } from "discourse-i18n";
+import { formatEventName } from "../helpers/format-event-name";
+import addRecurrentEvents from "../lib/add-recurrent-events";
+import { colorToHex, contrastColor, stringToColor } from "../lib/colors";
+import fullCalendarDefaultOptions from "../lib/full-calendar-default-options";
+import { isNotFullDayEvent } from "../lib/guess-best-date-format";
+import { buildPopover, destroyPopover } from "../lib/popover";
+
+function loadFullCalendar() {
+ return loadScript(
+ "/plugins/discourse-calendar/javascripts/fullcalendar-with-moment-timezone.min.js"
+ );
+}
+
+function initializeDiscourseCalendar(api) {
+ const siteSettings = api.container.lookup("service:site-settings");
+
+ if (siteSettings.login_required && !api.getCurrentUser()) {
+ return;
+ }
+
+ let enableTimezoneOffset = siteSettings.default_timezone_offset_user_option;
+
+ let _topicController;
+ const outletName = siteSettings.calendar_categories_outlet;
+
+ const site = api.container.lookup("service:site");
+ const isMobileView = site && site.mobileView;
+
+ const router = api.container.lookup("service:router");
+
+ let selector = `.${outletName}-outlet`;
+ if (outletName === "before-topic-list-body") {
+ selector = `.topic-list:not(.shared-drafts) .${outletName}-outlet`;
+ }
+
+ api.onPageChange(async (url) => {
+ destroyPopover();
+
+ const categoryCalendarNode = document.querySelector(
+ `${selector}.category-calendar`
+ );
+ if (categoryCalendarNode) {
+ categoryCalendarNode.innerHTML = "";
+ }
+
+ const categoryEventNode = document.getElementById(
+ "category-events-calendar"
+ );
+ if (categoryEventNode) {
+ categoryEventNode.innerHTML = "";
+ }
+
+ const route = router.recognize(url);
+ if (!route?.params?.category_slug_path_with_id) {
+ return;
+ }
+
+ const browsedCategory = Category.findBySlugPathWithID(
+ route.params.category_slug_path_with_id
+ );
+ if (!browsedCategory) {
+ return;
+ }
+
+ const settings = siteSettings.calendar_categories
+ .split("|")
+ .filter(Boolean)
+ .map((stringSetting) => {
+ const data = {};
+ stringSetting
+ .split(";")
+ .filter(Boolean)
+ .forEach((s) => {
+ const parts = s.split("=");
+ data[parts[0]] = parts[1];
+ });
+ return data;
+ });
+ const categorySetting = settings.findBy(
+ "categoryId",
+ browsedCategory.id.toString()
+ );
+
+ if (categoryCalendarNode && categorySetting && categorySetting.postId) {
+ const postId = categorySetting.postId;
+ categoryCalendarNode.innerHTML =
+ '
';
+
+ await loadFullCalendar();
+
+ const options = [`postId=${postId}`];
+
+ const optionals = ["weekends", "tzPicker", "defaultView"];
+ optionals.forEach((optional) => {
+ if (isPresent(categorySetting[optional])) {
+ options.push(
+ `${optional}=${escapeExpression(categorySetting[optional])}`
+ );
+ }
+ });
+
+ const rawCalendar = `[calendar ${options.join(" ")}]\n[/calendar]`;
+ const cookRaw = cook(rawCalendar);
+ const loadPost = ajax(`/posts/${postId}.json`);
+
+ const [cooked, post] = await Promise.all([cookRaw, loadPost]);
+
+ categoryCalendarNode.innerHTML = cooked.toString();
+ render($(".calendar"), post);
+ } else {
+ if (!categoryEventNode) {
+ return;
+ }
+
+ const eventSettings = siteSettings.events_calendar_categories.split("|");
+ const foundCategory = eventSettings.find(
+ (k) => k === browsedCategory.id.toString()
+ );
+
+ if (foundCategory) {
+ await loadFullCalendar();
+ let fullCalendar = new window.FullCalendar.Calendar(categoryEventNode, {
+ ...fullCalendarDefaultOptions(),
+ firstDay: 1,
+ eventPositioned: (info) => {
+ if (siteSettings.events_max_rows === 0) {
+ return;
+ }
+
+ let fcContent = info.el.querySelector(".fc-content");
+ let computedStyle = window.getComputedStyle(fcContent);
+ let lineHeight = parseInt(computedStyle.lineHeight, 10);
+
+ if (lineHeight === 0) {
+ lineHeight = 20;
+ }
+ let maxHeight = lineHeight * siteSettings.events_max_rows;
+
+ if (fcContent) {
+ fcContent.style.maxHeight = `${maxHeight}px`;
+ }
+
+ let fcTitle = info.el.querySelector(".fc-title");
+ if (fcTitle) {
+ fcTitle.style.overflow = "hidden";
+ fcTitle.style.whiteSpace = "pre-wrap";
+ }
+ fullCalendar.updateSize();
+ },
+ });
+ const params = {
+ category_id: browsedCategory.id,
+ include_subcategories: true,
+ };
+ if (siteSettings.include_expired_events_on_calendar) {
+ params.include_expired = true;
+ }
+
+ const tagsColorsMap = JSON.parse(siteSettings.map_events_to_color);
+
+ const discoursePostEventApiService = api.container.lookup(
+ "service:discourse-post-event-api"
+ );
+
+ const events = await discoursePostEventApiService.events(params);
+ const recurrentEvents = addRecurrentEvents(events);
+ recurrentEvents.forEach((event) => {
+ const { startsAt, endsAt, post, categoryId } = event;
+
+ let backgroundColor;
+
+ if (post.topic.tags) {
+ const tagColorEntry = tagsColorsMap.find(
+ (entry) =>
+ entry.type === "tag" && post.topic.tags.includes(entry.slug)
+ );
+ backgroundColor = tagColorEntry ? tagColorEntry.color : null;
+ }
+
+ if (!backgroundColor) {
+ const categoryColorFromMap = tagsColorsMap.find(
+ (entry) =>
+ entry.type === "category" &&
+ entry.slug === post.topic.category_slug
+ )?.color;
+ backgroundColor =
+ categoryColorFromMap ||
+ `#${Category.findById(categoryId)?.color}`;
+ }
+
+ let classNames;
+ if (moment(endsAt || startsAt).isBefore(moment())) {
+ classNames = "fc-past-event";
+ }
+
+ fullCalendar.addEvent({
+ title: formatEventName(
+ event,
+ api.getCurrentUser()?.user_option?.timezone
+ ),
+ start: startsAt,
+ end: endsAt || startsAt,
+ allDay: !isNotFullDayEvent(moment(startsAt), moment(endsAt)),
+ url: getURL(`/t/-/${post.topic.id}/${post.post_number}`),
+ backgroundColor,
+ classNames,
+ });
+ });
+
+ fullCalendar.render();
+ }
+ }
+ });
+
+ api.decorateCooked(($elem, helper) => attachCalendar($elem, helper), {
+ onlyStream: true,
+ id: "discourse-calendar",
+ });
+
+ api.registerCustomPostMessageCallback(
+ "calendar_change",
+ (topicController) => {
+ const stream = topicController.get("model.postStream");
+ const post = stream.findLoadedPost(stream.get("firstPostId"));
+ const $op = $(".topic-post article#post_1");
+ const $calendar = $op.find(".calendar").first();
+
+ if (post && $calendar.length > 0) {
+ ajax(`/posts/${post.id}.json`).then(() =>
+ loadFullCalendar().then(() => render($calendar, post))
+ );
+ }
+ }
+ );
+
+ if (api.registerNotificationTypeRenderer) {
+ api.registerNotificationTypeRenderer(
+ "event_reminder",
+ (NotificationTypeBase) => {
+ return class extends NotificationTypeBase {
+ get linkTitle() {
+ if (this.notification.data.title) {
+ return i18n(this.notification.data.title);
+ } else {
+ return super.linkTitle;
+ }
+ }
+
+ get icon() {
+ return "calendar-day";
+ }
+
+ get label() {
+ return i18n(this.notification.data.message);
+ }
+
+ get description() {
+ return this.notification.data.topic_title;
+ }
+ };
+ }
+ );
+ api.registerNotificationTypeRenderer(
+ "event_invitation",
+ (NotificationTypeBase) => {
+ return class extends NotificationTypeBase {
+ get icon() {
+ return "calendar-day";
+ }
+
+ get label() {
+ if (
+ this.notification.data.message ===
+ "discourse_post_event.notifications.invite_user_predefined_attendance_notification"
+ ) {
+ return i18n(this.notification.data.message, {
+ username: this.username,
+ });
+ }
+ return super.label;
+ }
+
+ get description() {
+ return this.notification.data.topic_title;
+ }
+ };
+ }
+ );
+ }
+
+ function render($calendar, post) {
+ $calendar = $calendar.empty();
+
+ const timezone = _getTimeZone($calendar, api.getCurrentUser());
+ const calendar = _buildCalendar($calendar, timezone);
+ const isStatic = $calendar.attr("data-calendar-type") === "static";
+ const fullDay = $calendar.attr("data-calendar-full-day") === "true";
+
+ if (isStatic) {
+ calendar.render();
+ _setStaticCalendarEvents(calendar, $calendar, post);
+ } else {
+ _setDynamicCalendarEvents(calendar, post, fullDay, timezone);
+ calendar.render();
+ _setDynamicCalendarOptions(calendar, $calendar);
+ }
+
+ const resetDynamicEvents = () => {
+ const selectedTimezone = calendar.getOption("timeZone");
+ calendar.getEvents().forEach((event) => event.remove());
+ _setDynamicCalendarEvents(calendar, post, fullDay, selectedTimezone);
+ };
+
+ _setupTimezonePicker(calendar, timezone, resetDynamicEvents);
+
+ if (siteSettings.enable_timezone_offset_for_calendar_events) {
+ _setupTimezoneOffsetButton(resetDynamicEvents);
+ }
+ }
+
+ function attachCalendar($elem, helper) {
+ const $calendar = $(".calendar", $elem);
+
+ if ($calendar.length === 0) {
+ return;
+ }
+
+ loadFullCalendar().then(() => render($calendar, helper.getModel()));
+ }
+
+ function _buildCalendar($calendar, timeZone) {
+ let $calendarTitle = document.querySelector(
+ ".discourse-calendar-header > .discourse-calendar-title"
+ );
+
+ const defaultView = escapeExpression(
+ $calendar.attr("data-calendar-default-view") ||
+ (isMobileView ? "listNextYear" : "month")
+ );
+
+ const showAddToCalendar =
+ $calendar.attr("data-calendar-show-add-to-calendar") !== "false";
+
+ return new window.FullCalendar.Calendar($calendar[0], {
+ ...fullCalendarDefaultOptions(),
+ timeZone,
+ timeZoneImpl: "moment-timezone",
+ nextDayThreshold: "06:00:00",
+ displayEventEnd: true,
+ height: 650,
+ firstDay: 1,
+ defaultView,
+ views: {
+ listNextYear: {
+ type: "list",
+ duration: { days: 365 },
+ buttonText: "list",
+ listDayFormat: {
+ month: "long",
+ year: "numeric",
+ day: "numeric",
+ weekday: "long",
+ },
+ },
+ },
+ header: {
+ left: "prev,next today",
+ center: "title",
+ right: "month,basicWeek,listNextYear",
+ },
+ eventOrder: [
+ "start",
+ _orderByTz,
+ "-participantCount",
+ "-duration",
+ "allDay",
+ "title",
+ ],
+ datesRender: (info) => {
+ if (showAddToCalendar) {
+ _insertAddToCalendarLinks(info);
+ }
+
+ $calendarTitle.innerText = info.view.title;
+ },
+ eventPositioned: (info) => {
+ _setTimezoneOffset(info);
+ },
+ });
+ }
+
+ function _orderByTz(a, b) {
+ if (
+ !siteSettings.enable_timezone_offset_for_calendar_events &&
+ !enableTimezoneOffset
+ ) {
+ return 0;
+ }
+
+ const offsetA = a.extendedProps.timezoneOffset;
+ const offsetB = b.extendedProps.timezoneOffset;
+
+ return offsetA === offsetB ? 0 : offsetA < offsetB ? -1 : 1;
+ }
+
+ function _convertHtmlToDate(html) {
+ const date = html.attr("data-date");
+
+ if (!date) {
+ return null;
+ }
+
+ const time = html.attr("data-time");
+ const timezone = html.attr("data-timezone");
+ let dateTime = date;
+ if (time) {
+ dateTime = `${dateTime} ${time}`;
+ }
+
+ return {
+ weeklyRecurring: html.attr("data-recurring") === "1.weeks",
+ dateTime: moment.tz(dateTime, timezone || "Etc/UTC"),
+ };
+ }
+
+ function _buildEventObject(from, to) {
+ const hasTimeSpecified = (d) => {
+ if (!d) {
+ return false;
+ }
+ return d.hours() !== 0 || d.minutes() !== 0 || d.seconds() !== 0;
+ };
+
+ const hasTime =
+ hasTimeSpecified(to?.dateTime) || hasTimeSpecified(from?.dateTime);
+ const dateFormat = hasTime ? "YYYY-MM-DD HH:mm:ssZ" : "YYYY-MM-DD";
+
+ let event = {
+ start: from.dateTime.format(dateFormat),
+ allDay: false,
+ };
+
+ if (to) {
+ if (hasTime) {
+ event.end = to.dateTime.format(dateFormat);
+ } else {
+ event.end = to.dateTime.add(1, "days").format(dateFormat);
+ event.allDay = true;
+ }
+ } else {
+ event.allDay = true;
+ }
+
+ if (from.weeklyRecurring) {
+ event.startTime = {
+ hours: from.dateTime.hours(),
+ minutes: from.dateTime.minutes(),
+ seconds: from.dateTime.seconds(),
+ };
+ event.daysOfWeek = [from.dateTime.day()];
+ }
+
+ return event;
+ }
+
+ function _setStaticCalendarEvents(calendar, $calendar, post) {
+ $(`${post.cooked}
`)
+ .find('.calendar[data-calendar-type="static"] p')
+ .html()
+ .trim()
+ .split("
")
+ .forEach((line) => {
+ const html = $.parseHTML(line);
+ const htmlDates = html.filter((h) =>
+ $(h).hasClass("discourse-local-date")
+ );
+
+ const from = _convertHtmlToDate($(htmlDates[0]));
+ const to = _convertHtmlToDate($(htmlDates[1]));
+
+ let event = _buildEventObject(from, to);
+ event.title = html[0].textContent.trim();
+ calendar.addEvent(event);
+ });
+ }
+
+ function _setDynamicCalendarOptions(calendar, $calendar) {
+ const skipWeekends = $calendar.attr("data-weekends") === "false";
+ const hiddenDays = $calendar.attr("data-hidden-days");
+
+ if (skipWeekends) {
+ calendar.setOption("weekends", false);
+ }
+
+ if (hiddenDays) {
+ calendar.setOption(
+ "hiddenDays",
+ hiddenDays.split(",").map((d) => parseInt(d, 10))
+ );
+ }
+
+ calendar.setOption("eventClick", ({ event, jsEvent }) => {
+ destroyPopover();
+ const { htmlContent, postNumber, postUrl } = event.extendedProps;
+
+ if (postUrl) {
+ DiscourseURL.routeTo(postUrl);
+ } else if (postNumber) {
+ _topicController =
+ _topicController || api.container.lookup("controller:topic");
+ _topicController.send("jumpToPost", postNumber);
+ } else if (isMobileView && htmlContent) {
+ buildPopover(jsEvent, htmlContent);
+ }
+ });
+
+ calendar.setOption("eventMouseEnter", ({ event, jsEvent }) => {
+ destroyPopover();
+ const { htmlContent } = event.extendedProps;
+ buildPopover(jsEvent, htmlContent);
+ });
+
+ calendar.setOption("eventMouseLeave", () => {
+ destroyPopover();
+ });
+ }
+
+ function _buildEvent(detail) {
+ const event = _buildEventObject(
+ detail.from
+ ? {
+ dateTime: moment(detail.from),
+ weeklyRecurring: detail.recurring === "1.weeks",
+ }
+ : null,
+ detail.to
+ ? {
+ dateTime: moment(detail.to),
+ weeklyRecurring: detail.recurring === "1.weeks",
+ }
+ : null
+ );
+
+ event.extendedProps = {};
+ if (detail.post_url) {
+ event.extendedProps.postUrl = detail.post_url;
+ } else if (detail.post_number) {
+ event.extendedProps.postNumber = detail.post_number;
+ } else {
+ event.classNames = ["holiday"];
+ }
+
+ if (detail.timezoneOffset) {
+ event.extendedProps.timezoneOffset = detail.timezoneOffset;
+ }
+
+ return event;
+ }
+
+ function _addStandaloneEvent(calendar, post, detail) {
+ const event = _buildEvent(detail);
+
+ const holidayCalendarTopicId = parseInt(
+ siteSettings.holiday_calendar_topic_id,
+ 10
+ );
+
+ const text = detail.message.split("\n").filter((e) => e);
+ if (
+ text.length &&
+ post.topic_id &&
+ holidayCalendarTopicId !== post.topic_id
+ ) {
+ event.title = text[0];
+ event.extendedProps.description = text.slice(1).join(" ");
+ } else {
+ const color = stringToColor(detail.username);
+
+ event.title = detail.username;
+ event.backgroundColor = colorToHex(color);
+ event.textColor = contrastColor(color);
+ }
+
+ let popupText = detail.message.slice(0, 100);
+ if (detail.message.length > 100) {
+ popupText += "…";
+ }
+ event.extendedProps.htmlContent = escape(popupText);
+ event.title = event.title.replace(/
]*>/g, "");
+ event.participantCount = 1;
+ calendar.addEvent(event);
+ }
+
+ function _addGroupedEvent(calendar, post, detail, fullDay, calendarTz) {
+ const groupedEventData =
+ siteSettings.enable_timezone_offset_for_calendar_events &&
+ enableTimezoneOffset &&
+ fullDay
+ ? _splitGroupEventByTimezone(detail, calendarTz)
+ : [detail];
+
+ groupedEventData.forEach((eventData) => {
+ let htmlContent = "";
+ let users = [];
+ let localEventNames = [];
+
+ Object.keys(eventData.localEvents)
+ .sort()
+ .forEach((key) => {
+ const localEvent = eventData.localEvents[key];
+ htmlContent += `${key}: ${localEvent.users
+ .map((u) => u.username)
+ .sort()
+ .join(", ")}
`;
+ users = users.concat(localEvent.users);
+ localEventNames.push(key);
+ });
+
+ const event = _buildEvent(eventData);
+ event.classNames = ["grouped-event"];
+
+ if (users.length > 2) {
+ event.title = `(${users.length}) ${localEventNames[0]}`;
+ } else if (users.length === 1) {
+ event.title = users[0].username;
+ } else {
+ event.title = isMobileView
+ ? `(${users.length}) ${localEventNames[0]}`
+ : `(${users.length}) ` + users.map((u) => u.username).join(", ");
+ }
+
+ if (localEventNames.length > 1) {
+ event.extendedProps.htmlContent = htmlContent;
+ } else {
+ if (users.length > 1) {
+ event.extendedProps.htmlContent = htmlContent;
+ } else {
+ event.extendedProps.htmlContent = localEventNames[0];
+ }
+ }
+
+ event.participantCount = users.length;
+
+ calendar.addEvent(event);
+ });
+ }
+
+ function _splitGroupEventByTimezone(detail, calendarTz) {
+ const calendarUtcOffset = moment.tz(calendarTz).utcOffset();
+ let timezonesOffsets = [];
+ let splittedEvents = [];
+
+ Object.values(detail.localEvents).forEach((event) => {
+ event.users.forEach((user) => {
+ const userUtcOffset = moment.tz(user.timezone).utcOffset();
+ const timezoneOffset = (calendarUtcOffset - userUtcOffset) / 60;
+ user.timezoneOffset = timezoneOffset;
+ timezonesOffsets.push(timezoneOffset);
+ });
+ });
+
+ [...new Set(timezonesOffsets)].forEach((offset, i) => {
+ let filteredLocalEvents = {};
+ let eventTimezones = [];
+
+ Object.keys(detail.localEvents).forEach((key) => {
+ const threshold =
+ siteSettings.split_grouped_events_by_timezone_threshold;
+
+ const filtered = detail.localEvents[key].users.filter(
+ (u) =>
+ Math.abs(u.timezoneOffset - (offset + threshold * i)) <= threshold
+ );
+ if (filtered.length > 0) {
+ filteredLocalEvents[key] = {
+ users: filtered,
+ };
+ filtered.forEach((u) => {
+ detail.localEvents[key].users.splice(
+ detail.localEvents[key].users.findIndex(
+ (e) => e.username === u.username
+ ),
+ 1
+ );
+ if (
+ !eventTimezones.find((t) => t.timezoneOffset === u.timezoneOffset)
+ ) {
+ eventTimezones.push({
+ timezone: u.timezone,
+ timezoneOffset: u.timezoneOffset,
+ });
+ }
+ });
+ }
+ });
+
+ if (Object.keys(filteredLocalEvents).length > 0) {
+ const eventTimezone = _findAverageTimezone(eventTimezones);
+
+ let from = moment.tz(detail.from, eventTimezone.timezone);
+ let to = moment.tz(detail.to, eventTimezone.timezone);
+
+ _modifyDatesForTimezoneOffset(from, to, eventTimezone.timezoneOffset);
+
+ splittedEvents.push({
+ timezoneOffset: eventTimezone.timezoneOffset,
+ localEvents: filteredLocalEvents,
+ from: from.format("YYYY-MM-DD"),
+ to: to.format("YYYY-MM-DD"),
+ });
+ }
+ });
+
+ return splittedEvents;
+ }
+
+ function _findAverageTimezone(eventTimezones) {
+ const totalOffsets = eventTimezones.reduce(
+ (sum, timezone) => sum + timezone.timezoneOffset,
+ 0
+ );
+ const averageOffset = totalOffsets / eventTimezones.length;
+
+ return eventTimezones.reduce((closest, timezone) => {
+ const difference = Math.abs(timezone.timezoneOffset - averageOffset);
+ return difference < Math.abs(closest.timezoneOffset - averageOffset)
+ ? timezone
+ : closest;
+ });
+ }
+
+ function _setDynamicCalendarEvents(calendar, post, fullDay, calendarTz) {
+ const groupedEvents = [];
+ const calendarUtcOffset = moment.tz(calendarTz).utcOffset();
+
+ (post.calendar_details || []).forEach((detail) => {
+ switch (detail.type) {
+ case "grouped":
+ if (fullDay && detail.timezone) {
+ detail.from = moment
+ .tz(detail.from, detail.timezone)
+ .format("YYYY-MM-DD");
+ }
+ groupedEvents.push(detail);
+ break;
+ case "standalone":
+ if (fullDay && detail.timezone) {
+ const eventDetail = { ...detail };
+ let from = moment.tz(detail.from, detail.timezone);
+ let to = moment.tz(detail.to, detail.timezone);
+
+ if (
+ siteSettings.enable_timezone_offset_for_calendar_events &&
+ enableTimezoneOffset
+ ) {
+ const eventUtcOffset = moment.tz(detail.timezone).utcOffset();
+ const timezoneOffset = (calendarUtcOffset - eventUtcOffset) / 60;
+ eventDetail.timezoneOffset = timezoneOffset;
+
+ _modifyDatesForTimezoneOffset(from, to, timezoneOffset);
+ }
+ eventDetail.from = from.format("YYYY-MM-DD");
+ eventDetail.to = to.format("YYYY-MM-DD");
+
+ _addStandaloneEvent(calendar, post, eventDetail);
+ } else {
+ _addStandaloneEvent(calendar, post, detail);
+ }
+ break;
+ }
+ });
+
+ const formattedGroupedEvents = {};
+ groupedEvents.forEach((groupedEvent) => {
+ const minDate = fullDay
+ ? moment(groupedEvent.from).format("YYYY-MM-DD")
+ : moment(groupedEvent.from).utc().startOf("day").toISOString();
+ const maxDate = fullDay
+ ? moment(groupedEvent.to || groupedEvent.from).format("YYYY-MM-DD")
+ : moment(groupedEvent.to || groupedEvent.from)
+ .utc()
+ .endOf("day")
+ .toISOString();
+
+ const identifier = `${minDate}-${maxDate}`;
+ formattedGroupedEvents[identifier] = formattedGroupedEvents[
+ identifier
+ ] || {
+ from: minDate,
+ to: maxDate || minDate,
+ localEvents: {},
+ };
+
+ formattedGroupedEvents[identifier].localEvents[groupedEvent.name] =
+ formattedGroupedEvents[identifier].localEvents[groupedEvent.name] || {
+ users: [],
+ };
+
+ formattedGroupedEvents[identifier].localEvents[
+ groupedEvent.name
+ ].users.push.apply(
+ formattedGroupedEvents[identifier].localEvents[groupedEvent.name].users,
+ groupedEvent.users
+ );
+ });
+
+ Object.keys(formattedGroupedEvents).forEach((key) => {
+ const formattedGroupedEvent = formattedGroupedEvents[key];
+ _addGroupedEvent(
+ calendar,
+ post,
+ formattedGroupedEvent,
+ fullDay,
+ calendarTz
+ );
+ });
+ }
+
+ function _modifyDatesForTimezoneOffset(from, to, timezoneOffset) {
+ if (timezoneOffset > 0) {
+ if (to.isValid()) {
+ to.add(1, "day");
+ } else {
+ to = from.clone().add(1, "day");
+ }
+ } else if (timezoneOffset < 0) {
+ if (!to.isValid()) {
+ to = from.clone();
+ }
+ from.subtract(1, "day");
+ }
+ }
+
+ function _getTimeZone($calendar, currentUser) {
+ let defaultTimezone = $calendar.attr("data-calendar-default-timezone");
+ const isValidDefaultTimezone = !!moment.tz.zone(defaultTimezone);
+ if (!isValidDefaultTimezone) {
+ defaultTimezone = null;
+ }
+
+ return defaultTimezone || currentUser?.timezone || moment.tz.guess();
+ }
+
+ function _setupTimezonePicker(calendar, timezone, resetDynamicEvents) {
+ const tzPicker = document.querySelector(
+ ".discourse-calendar-timezone-picker"
+ );
+ if (tzPicker) {
+ tzPicker.addEventListener("change", function (event) {
+ calendar.setOption("timeZone", event.target.value);
+ if (enableTimezoneOffset) {
+ resetDynamicEvents();
+ _insertAddToCalendarLinks(calendar);
+ }
+ });
+
+ moment.tz
+ .names()
+ .filter((t) => !t.startsWith("Etc/GMT"))
+ .forEach((tz) => {
+ tzPicker.appendChild(new Option(tz, tz));
+ });
+
+ tzPicker.value = timezone;
+ } else {
+ document.querySelector(".discourse-calendar-timezone-wrap").innerText =
+ timezone;
+ }
+ }
+
+ function _setupTimezoneOffsetButton(resetDynamicEvents) {
+ const timezoneWrapper = document.querySelector(
+ ".discourse-calendar-timezone-wrap"
+ );
+ const timezoneButton = document.createElement("button");
+
+ timezoneButton.title = i18n(
+ "discourse_calendar.toggle_timezone_offset_title"
+ );
+ timezoneButton.classList.add(
+ "timezone-offset-button",
+ "btn",
+ "btn-default",
+ "btn-icon",
+ "no-text"
+ );
+ timezoneButton.innerHTML = iconHTML("globe");
+ timezoneWrapper.appendChild(timezoneButton);
+
+ timezoneButton.addEventListener("click", () => {
+ enableTimezoneOffset = !enableTimezoneOffset;
+ resetDynamicEvents();
+ timezoneButton.blur();
+ });
+ }
+
+ function _insertAddToCalendarLinks(info) {
+ if (info.view.type !== "listNextYear") {
+ return;
+ }
+
+ const eventSegments = info.view.eventRenderer.segs;
+ const eventSegmentDefMap = _eventSegmentDefMap(info);
+
+ for (const event of eventSegments) {
+ _insertAddToCalendarLinkForEvent(event, eventSegmentDefMap);
+ }
+ }
+
+ function _setTimezoneOffset(info) {
+ if (
+ !siteSettings.enable_timezone_offset_for_calendar_events ||
+ !enableTimezoneOffset ||
+ info.view.type === "listNextYear"
+ ) {
+ return;
+ }
+
+ // The timezone offset works by calculating the hour difference
+ // between a target event and the calendar event. This is used to
+ // determine whether to add an extra day before or after the event.
+ // Then, it applies inline styling to resize the event to its
+ // original size while adjusting it to the respective timezone.
+
+ const timezoneOffset = info.event.extendedProps.timezoneOffset;
+ const segmentDuration = info.el.parentNode?.colSpan;
+
+ const basePctOffset = 100 / segmentDuration;
+ // Base margin required to shrink down the event by one day
+ const basePxOffset = 5.5 - segmentDuration;
+ // Default space between two consecutive events
+ // 5.5px = ( ( ( 2px margin + 3px padding ) * 2 ) + 1px border ) / 2
+
+ // K factors are used to adjust each side of the event based on the hour difference
+ // A '2' is added to the pxOffset to account for the default margin
+
+ if (timezoneOffset > 0) {
+ // When the event extends into the next day
+ if (info.isStart) {
+ const leftK = Math.abs(timezoneOffset) / 24;
+ const pctOffset = `${basePctOffset * leftK}%`;
+ const pxOffset = `${basePxOffset * leftK + 2}px`;
+ info.el.style.marginLeft = `calc(${pctOffset} + ${pxOffset})`;
+ }
+ if (info.isEnd) {
+ const rightK = (24 - Math.abs(timezoneOffset)) / 24;
+ const pctOffset = `${basePctOffset * rightK}%`;
+ const pxOffset = `${basePxOffset * rightK + 2}px`;
+ info.el.style.marginRight = `calc(${pctOffset} + ${pxOffset})`;
+ }
+ } else if (timezoneOffset < 0) {
+ // When the event starts on the previous day
+ if (info.isStart) {
+ const leftK = (24 - Math.abs(timezoneOffset)) / 24;
+ const pctOffset = `${basePctOffset * leftK}%`;
+ const pxOffset = `${basePxOffset * leftK + 2}px`;
+ info.el.style.marginLeft = `calc(${pctOffset} + ${pxOffset})`;
+ }
+ if (info.isEnd) {
+ const rightK = Math.abs(timezoneOffset) / 24;
+ const pctOffset = `${basePctOffset * rightK}%`;
+ const pxOffset = `${basePxOffset * rightK + 2}px`;
+ info.el.style.marginRight = `calc(${pctOffset} + ${pxOffset})`;
+ }
+ }
+ }
+
+ function _insertAddToCalendarLinkForEvent(event, eventSegmentDefMap) {
+ const eventTitle = event.eventRange.def.title;
+ let map = eventSegmentDefMap[event.eventRange.def.defId];
+ let startDate = map.start;
+ let endDate = map.end;
+
+ endDate = endDate
+ ? _formatDateForGoogleApi(endDate, event.eventRange.def.allDay)
+ : _endDateForAllDayEvent(startDate, event.eventRange.def.allDay);
+ startDate = _formatDateForGoogleApi(startDate, event.eventRange.def.allDay);
+
+ const link = document.createElement("a");
+ const title = i18n("discourse_calendar.add_to_calendar");
+ link.title = title;
+ link.appendChild(document.createTextNode(title));
+ link.href = `
+ http://www.google.com/calendar/event?action=TEMPLATE&text=${encodeURIComponent(
+ eventTitle
+ )}&dates=${startDate}/${endDate}&details=${encodeURIComponent(
+ event.eventRange.def.extendedProps.description
+ )}`;
+ link.target = "_blank";
+ link.classList.add("fc-list-item-add-to-calendar");
+ event.el.querySelector(".fc-list-item-title").appendChild(link);
+ }
+
+ function _formatDateForGoogleApi(date, allDay = false) {
+ if (!allDay) {
+ return date.toISOString().replace(/-|:|\.\d\d\d/g, "");
+ }
+
+ return moment(date).utc().format("YYYYMMDD");
+ }
+
+ function _endDateForAllDayEvent(startDate, allDay) {
+ const unit = allDay ? "days" : "hours";
+ return _formatDateForGoogleApi(
+ moment(startDate).add(1, unit).toDate(),
+ allDay
+ );
+ }
+
+ function _eventSegmentDefMap(info) {
+ let map = {};
+
+ for (let event of info.view.calendar.getEvents()) {
+ map[event._instance.defId] = { start: event.start, end: event.end };
+ }
+ return map;
+ }
+}
+
+export default {
+ name: "discourse-calendar",
+
+ initialize(container) {
+ const siteSettings = container.lookup("service:site-settings");
+ if (siteSettings.calendar_enabled) {
+ withPluginApi("0.8.22", initializeDiscourseCalendar);
+ }
+ },
+};
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/initializers/discourse-post-event-decorator.gjs b/plugins/discourse-calendar/assets/javascripts/discourse/initializers/discourse-post-event-decorator.gjs
new file mode 100644
index 00000000000..f7ec4773643
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/initializers/discourse-post-event-decorator.gjs
@@ -0,0 +1,159 @@
+import { isTesting } from "discourse/lib/environment";
+import { withPluginApi } from "discourse/lib/plugin-api";
+import I18n, { i18n } from "discourse-i18n";
+import DiscoursePostEvent from "discourse/plugins/discourse-calendar/discourse/components/discourse-post-event";
+import DiscoursePostEventEvent from "discourse/plugins/discourse-calendar/discourse/models/discourse-post-event-event";
+import guessDateFormat from "../lib/guess-best-date-format";
+
+export function buildEventPreview(eventContainer) {
+ eventContainer.innerHTML = "";
+ eventContainer.classList.add("discourse-post-event-preview");
+
+ const statusLocaleKey = `discourse_post_event.models.event.status.${
+ eventContainer.dataset.status || "public"
+ }.title`;
+ if (I18n.lookup(statusLocaleKey, { locale: "en" })) {
+ const statusContainer = document.createElement("div");
+ statusContainer.classList.add("event-preview-status");
+ statusContainer.innerText = i18n(statusLocaleKey);
+ eventContainer.appendChild(statusContainer);
+ }
+
+ const datesContainer = document.createElement("div");
+ datesContainer.classList.add("event-preview-dates");
+
+ const startsAt = moment.tz(
+ eventContainer.dataset.start,
+ eventContainer.dataset.timezone || "UTC"
+ );
+
+ const endsAt =
+ eventContainer.dataset.end &&
+ moment.tz(
+ eventContainer.dataset.end,
+ eventContainer.dataset.timezone || "UTC"
+ );
+
+ const format = guessDateFormat(startsAt, endsAt);
+ const guessedTz = isTesting() ? "UTC" : moment.tz.guess();
+
+ let datesString = `${startsAt
+ .tz(guessedTz)
+ .format(format)}`;
+ if (endsAt) {
+ datesString += ` → ${endsAt
+ .tz(guessedTz)
+ .format(format)}`;
+ }
+ datesContainer.innerHTML = datesString;
+
+ eventContainer.appendChild(datesContainer);
+}
+
+function _invalidEventPreview(eventContainer) {
+ eventContainer.classList.add(
+ "discourse-post-event-preview",
+ "alert",
+ "alert-error"
+ );
+ eventContainer.classList.remove("discourse-post-event");
+ eventContainer.innerText = i18n(
+ "discourse_post_event.preview.more_than_one_event"
+ );
+}
+
+function _decorateEventPreview(api, cooked) {
+ const eventContainers = cooked.querySelectorAll(".discourse-post-event");
+
+ eventContainers.forEach((eventContainer, index) => {
+ if (index > 0) {
+ _invalidEventPreview(eventContainer);
+ } else {
+ buildEventPreview(eventContainer);
+ }
+ });
+}
+
+function initializeDiscoursePostEventDecorator(api) {
+ api.decorateCookedElement(
+ (cooked, helper) => {
+ if (cooked.classList.contains("d-editor-preview")) {
+ _decorateEventPreview(api, cooked);
+ return;
+ }
+
+ if (helper) {
+ const post = helper.getModel();
+
+ if (!post?.event) {
+ return;
+ }
+
+ const postEventNode = cooked.querySelector(".discourse-post-event");
+
+ if (!postEventNode) {
+ return;
+ }
+
+ const wrapper = document.createElement("div");
+ postEventNode.before(wrapper);
+
+ const event = DiscoursePostEventEvent.create(post.event);
+
+ helper.renderGlimmer(
+ wrapper,
+
+ );
+ }
+ },
+ {
+ id: "discourse-post-event-decorator",
+ }
+ );
+
+ api.replaceIcon(
+ "notification.discourse_post_event.notifications.invite_user_notification",
+ "calendar-day"
+ );
+
+ api.replaceIcon(
+ "notification.discourse_post_event.notifications.invite_user_auto_notification",
+ "calendar-day"
+ );
+
+ api.replaceIcon(
+ "notification.discourse_calendar.invite_user_notification",
+ "calendar-day"
+ );
+
+ api.replaceIcon(
+ "notification.discourse_post_event.notifications.invite_user_predefined_attendance_notification",
+ "calendar-day"
+ );
+
+ api.replaceIcon(
+ "notification.discourse_post_event.notifications.before_event_reminder",
+ "calendar-day"
+ );
+
+ api.replaceIcon(
+ "notification.discourse_post_event.notifications.after_event_reminder",
+ "calendar-day"
+ );
+
+ api.replaceIcon(
+ "notification.discourse_post_event.notifications.ongoing_event_reminder",
+ "calendar-day"
+ );
+}
+
+export default {
+ name: "discourse-post-event-decorator",
+
+ initialize(container) {
+ const siteSettings = container.lookup("service:site-settings");
+ if (siteSettings.discourse_post_event_enabled) {
+ withPluginApi("0.8.7", initializeDiscoursePostEventDecorator);
+ }
+ },
+};
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/initializers/event-relative-date.js b/plugins/discourse-calendar/assets/javascripts/discourse/initializers/event-relative-date.js
new file mode 100644
index 00000000000..8460897d79a
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/initializers/event-relative-date.js
@@ -0,0 +1,38 @@
+import { cancel } from "@ember/runloop";
+import { isTesting } from "discourse/lib/environment";
+import discourseLater from "discourse/lib/later";
+import eventRelativeDate from "../lib/event-relative-date";
+
+function computeRelativeEventDates() {
+ document
+ .querySelectorAll(".event-relative-date.topic-list")
+ .forEach((dateContainer) => eventRelativeDate(dateContainer));
+}
+
+export default {
+ name: "event-future-date",
+
+ initialize() {
+ computeRelativeEventDates();
+
+ if (!isTesting()) {
+ this._tick();
+ }
+ },
+
+ teardown() {
+ if (this._interval) {
+ cancel(this._interval);
+ this._interval = null;
+ }
+ },
+
+ _tick() {
+ this._interval && cancel(this._interval);
+
+ this._interval = discourseLater(() => {
+ computeRelativeEventDates();
+ this._tick();
+ }, 60 * 1000);
+ },
+};
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/lib/add-recurrent-events.js b/plugins/discourse-calendar/assets/javascripts/discourse/lib/add-recurrent-events.js
new file mode 100644
index 00000000000..ee512a52896
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/lib/add-recurrent-events.js
@@ -0,0 +1,28 @@
+/* eslint-disable no-console */
+import DiscoursePostEventEvent from "../models/discourse-post-event-event";
+
+export default function addRecurrentEvents(events) {
+ try {
+ return events.flatMap((event) => {
+ if (!event.upcomingDates?.length) {
+ return [event];
+ }
+
+ const upcomingEvents =
+ event.upcomingDates?.map((upcomingDate) =>
+ DiscoursePostEventEvent.create({
+ name: event.name,
+ post: event.post,
+ category_id: event.categoryId,
+ starts_at: upcomingDate.starts_at,
+ ends_at: upcomingDate.ends_at,
+ })
+ ) || [];
+
+ return upcomingEvents;
+ });
+ } catch (error) {
+ console.error("Failed to retrieve events:", error);
+ return [];
+ }
+}
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/lib/calendar-locale.js b/plugins/discourse-calendar/assets/javascripts/discourse/lib/calendar-locale.js
new file mode 100644
index 00000000000..f9beb94b693
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/lib/calendar-locale.js
@@ -0,0 +1,15 @@
+import I18n, { i18n } from "discourse-i18n";
+
+export function getCurrentBcp47Locale() {
+ return I18n.currentLocale().replace("_", "-").toLowerCase();
+}
+
+export function getCalendarButtonsText() {
+ return {
+ today: i18n("discourse_calendar.toolbar_button.today"),
+ month: i18n("discourse_calendar.toolbar_button.month"),
+ week: i18n("discourse_calendar.toolbar_button.week"),
+ day: i18n("discourse_calendar.toolbar_button.day"),
+ list: i18n("discourse_calendar.toolbar_button.list"),
+ };
+}
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/lib/colors.js b/plugins/discourse-calendar/assets/javascripts/discourse/lib/colors.js
new file mode 100644
index 00000000000..5fc0856d468
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/lib/colors.js
@@ -0,0 +1,28 @@
+// https://stackoverflow.com/a/16348977
+export function stringToColor(str) {
+ let hash = 0;
+ for (let i = 0; i < str.length; i++) {
+ // eslint-disable-next-line no-bitwise
+ hash = str.charCodeAt(i) + ((hash << 5) - hash);
+ }
+ let color = [];
+ for (let i = 0; i < 3; i++) {
+ // eslint-disable-next-line no-bitwise
+ let value = (hash >> (i * 8)) & 0xff;
+ color.push(value);
+ }
+ return color;
+}
+
+export function colorToHex(color) {
+ let hex = "#";
+ for (let i = 0; i < 3; i++) {
+ hex += ("00" + Math.round(color[i]).toString(16)).slice(-2);
+ }
+ return hex;
+}
+
+export function contrastColor(color) {
+ const luminance = 0.2126 * color[0] + 0.7152 * color[1] + 0.0722 * color[2];
+ return luminance / 255 >= 0.5 ? "#000d" : "#fffd";
+}
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/lib/discourse-markdown/discourse-calendar.js b/plugins/discourse-calendar/assets/javascripts/discourse/lib/discourse-markdown/discourse-calendar.js
new file mode 100644
index 00000000000..c8307285ed9
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/lib/discourse-markdown/discourse-calendar.js
@@ -0,0 +1,127 @@
+const calendarRule = {
+ tag: "calendar",
+
+ before: function (state, info) {
+ let wrapperDivToken = state.push("div_calendar_wrap", "div", 1);
+ wrapperDivToken.attrs = [["class", "discourse-calendar-wrap"]];
+
+ let headerDivToken = state.push("div_calendar_header", "div", 1);
+ headerDivToken.attrs = [["class", "discourse-calendar-header"]];
+
+ let titleH2Token = state.push("h2_open", "h2", 1);
+ titleH2Token.attrs = [["class", "discourse-calendar-title"]];
+ state.push("h2_close", "h2", -1);
+
+ let timezoneWrapToken = state.push("span_open", "span", 1);
+ timezoneWrapToken.attrs = [["class", "discourse-calendar-timezone-wrap"]];
+ if (info.attrs.tzPicker === "true") {
+ _renderTimezonePicker(state);
+ }
+ state.push("span_close", "span", -1);
+
+ state.push("div_calendar_header", "div", -1);
+
+ let mainCalendarDivToken = state.push("div_calendar", "div", 1);
+ mainCalendarDivToken.attrs = [
+ ["class", "calendar"],
+ ["data-calendar-type", info.attrs.type || "dynamic"],
+ ["data-calendar-default-timezone", info.attrs.defaultTimezone],
+ ];
+
+ if (info.attrs.defaultView) {
+ mainCalendarDivToken.attrs.push([
+ "data-calendar-default-view",
+ info.attrs.defaultView,
+ ]);
+ }
+
+ if (info.attrs.weekends) {
+ mainCalendarDivToken.attrs.push(["data-weekends", info.attrs.weekends]);
+ }
+
+ if (info.attrs.showAddToCalendar) {
+ mainCalendarDivToken.attrs.push([
+ "data-calendar-show-add-to-calendar",
+ info.attrs.showAddToCalendar === "true",
+ ]);
+ }
+
+ if (info.attrs.fullDay) {
+ mainCalendarDivToken.attrs.push([
+ "data-calendar-full-day",
+ info.attrs.fullDay === "true",
+ ]);
+ }
+
+ if (info.attrs.hiddenDays) {
+ mainCalendarDivToken.attrs.push([
+ "data-hidden-days",
+ info.attrs.hiddenDays,
+ ]);
+ }
+ },
+
+ after: function (state) {
+ state.push("div_calendar", "div", -1);
+ state.push("div_calendar_wrap", "div", -1);
+ },
+};
+
+const groupTimezoneRule = {
+ tag: "timezones",
+
+ before: function (state, info) {
+ const wrapperDivToken = state.push("div_group_timezones", "div", 1);
+ wrapperDivToken.attrs = [
+ ["class", "group-timezones"],
+ ["data-group", info.attrs.group],
+ ["data-size", info.attrs.size || "medium"],
+ ];
+ },
+
+ after: function (state) {
+ state.push("div_group_timezones", "div", -1);
+ },
+};
+
+function _renderTimezonePicker(state) {
+ const timezoneSelectToken = state.push("select_open", "select", 1);
+ timezoneSelectToken.attrs = [["class", "discourse-calendar-timezone-picker"]];
+
+ state.push("select_close", "select", -1);
+}
+
+export function setup(helper) {
+ helper.allowList([
+ "div.calendar",
+ "div.discourse-calendar-header",
+ "div.discourse-calendar-wrap",
+ "select.discourse-calendar-timezone-picker",
+ "span.discourse-calendar-timezone-wrap",
+ "h2.discourse-calendar-title",
+ "div[data-calendar-type]",
+ "div[data-calendar-default-view]",
+ "div[data-calendar-default-timezone]",
+ "div[data-weekends]",
+ "div[data-hidden-days]",
+ "div.group-timezones",
+ "div[data-group]",
+ "div[data-size]",
+ ]);
+
+ helper.registerOptions((opts, siteSettings) => {
+ opts.features["discourse-calendar-enabled"] =
+ !!siteSettings.calendar_enabled;
+ });
+
+ helper.registerPlugin((md) => {
+ const features = md.options.discourse.features;
+ if (features["discourse-calendar-enabled"]) {
+ md.block.bbcode.ruler.push("discourse-calendar", calendarRule);
+ md.block.bbcode.ruler.push(
+ "discourse-group-timezones",
+ groupTimezoneRule
+ );
+ }
+ });
+}
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/lib/discourse-markdown/discourse-post-event-block.js b/plugins/discourse-calendar/assets/javascripts/discourse/lib/discourse-markdown/discourse-post-event-block.js
new file mode 100644
index 00000000000..1f7bd7dbcad
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/lib/discourse-markdown/discourse-post-event-block.js
@@ -0,0 +1,41 @@
+const rule = {
+ tag: "event",
+
+ wrap(token, info) {
+ if (!info.attrs.start) {
+ return false;
+ }
+
+ token.attrs = [["class", "discourse-post-event"]];
+
+ Object.keys(info.attrs).forEach((key) => {
+ const value = info.attrs[key];
+
+ if (typeof value !== "undefined") {
+ token.attrs.push([`data-${dasherize(key)}`, value]);
+ }
+ });
+
+ return true;
+ },
+};
+
+function dasherize(input) {
+ return input.replace(/[A-Z]/g, function (char, index) {
+ return (index !== 0 ? "-" : "") + char.toLowerCase();
+ });
+}
+
+export function setup(helper) {
+ helper.allowList(["div.discourse-post-event"]);
+
+ helper.registerOptions((opts, siteSettings) => {
+ opts.features.discourse_post_event =
+ siteSettings.calendar_enabled &&
+ siteSettings.discourse_post_event_enabled;
+ });
+
+ helper.registerPlugin((md) =>
+ md.block.bbcode.ruler.push("discourse-post-event", rule)
+ );
+}
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/lib/event-relative-date.js b/plugins/discourse-calendar/assets/javascripts/discourse/lib/event-relative-date.js
new file mode 100644
index 00000000000..4f9b345921a
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/lib/event-relative-date.js
@@ -0,0 +1,58 @@
+import { i18n } from "discourse-i18n";
+import guessDateFormat from "./guess-best-date-format";
+
+function _computeCurrentEvent(container, endsAt) {
+ const indicator = document.createElement("div");
+ indicator.classList.add("indicator");
+ container.appendChild(indicator);
+
+ const text = document.createElement("span");
+ text.classList.add("text");
+ text.innerText = i18n("discourse_post_event.topic_title.ends_in_duration", {
+ duration: endsAt.from(moment()),
+ });
+ container.appendChild(text);
+}
+
+function _computePastEvent(container, endsAt) {
+ container.innerText = endsAt.from(moment());
+}
+
+function _computeFutureEvent(container, startsAt) {
+ container.innerText = startsAt.from(moment());
+}
+
+export default function eventRelativeDate(container) {
+ container.classList.remove("past", "current", "future");
+ container.innerHTML = "";
+
+ const startsAt = moment
+ .utc(container.dataset.starts_at)
+ .tz(moment.tz.guess());
+ const endsAt = moment.utc(container.dataset.ends_at).tz(moment.tz.guess());
+
+ const format = guessDateFormat(startsAt);
+ let title = startsAt.format(format);
+ if (endsAt) {
+ title += ` → ${endsAt.format(format)}`;
+ }
+ container.setAttribute("title", title);
+
+ if (startsAt.isAfter(moment()) && endsAt.isAfter(moment())) {
+ container.classList.add("future");
+ _computeFutureEvent(container, startsAt);
+ return;
+ }
+
+ if (startsAt.isBefore(moment()) && endsAt.isAfter(moment())) {
+ container.classList.add("current");
+ _computeCurrentEvent(container, endsAt);
+ return;
+ }
+
+ if (startsAt.isBefore(moment()) && endsAt.isBefore(moment())) {
+ container.classList.add("past");
+ _computePastEvent(container, endsAt);
+ return;
+ }
+}
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/lib/full-calendar-default-options.js b/plugins/discourse-calendar/assets/javascripts/discourse/lib/full-calendar-default-options.js
new file mode 100644
index 00000000000..515a377f319
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/lib/full-calendar-default-options.js
@@ -0,0 +1,25 @@
+import { escape } from "pretty-text/sanitizer";
+import {
+ getCalendarButtonsText,
+ getCurrentBcp47Locale,
+} from "./calendar-locale";
+import { buildPopover, destroyPopover } from "./popover";
+
+export default function fullCalendarDefaultOptions() {
+ return {
+ eventClick: function () {
+ destroyPopover();
+ },
+ locale: getCurrentBcp47Locale(),
+ buttonText: getCalendarButtonsText(),
+ eventMouseEnter: function ({ event, jsEvent }) {
+ destroyPopover();
+
+ const htmlContent = escape(event.title);
+ buildPopover(jsEvent, htmlContent);
+ },
+ eventMouseLeave: function () {
+ destroyPopover();
+ },
+ };
+}
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/lib/guess-best-date-format.js b/plugins/discourse-calendar/assets/javascripts/discourse/lib/guess-best-date-format.js
new file mode 100644
index 00000000000..e1c7e982701
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/lib/guess-best-date-format.js
@@ -0,0 +1,18 @@
+export function isNotFullDayEvent(startsAt, endsAt) {
+ return (
+ startsAt.hours() > 0 ||
+ startsAt.minutes() > 0 ||
+ (endsAt && (moment(endsAt).hours() > 0 || moment(endsAt).minutes() > 0))
+ );
+}
+
+export default function guessDateFormat(startsAt, endsAt) {
+ let format;
+ if (!isNotFullDayEvent(startsAt, endsAt)) {
+ format = "LL";
+ } else {
+ format = "LLL";
+ }
+
+ return format;
+}
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/lib/popover.js b/plugins/discourse-calendar/assets/javascripts/discourse/lib/popover.js
new file mode 100644
index 00000000000..65a262bd346
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/lib/popover.js
@@ -0,0 +1,39 @@
+import { createPopper } from "@popperjs/core";
+
+let eventPopper;
+const EVENT_POPOVER_ID = "event-popover";
+
+export function buildPopover(jsEvent, htmlContent) {
+ const node = document.createElement("div");
+ node.setAttribute("id", EVENT_POPOVER_ID);
+ node.innerHTML = htmlContent;
+
+ const arrow = document.createElement("span");
+ arrow.dataset.popperArrow = true;
+ node.appendChild(arrow);
+ document.body.appendChild(node);
+
+ eventPopper = createPopper(
+ jsEvent.target,
+ document.getElementById(EVENT_POPOVER_ID),
+ {
+ placement: "bottom",
+ modifiers: [
+ {
+ name: "arrow",
+ },
+ {
+ name: "offset",
+ options: {
+ offset: [20, 10],
+ },
+ },
+ ],
+ }
+ );
+}
+
+export function destroyPopover() {
+ eventPopper?.destroy();
+ document.getElementById(EVENT_POPOVER_ID)?.remove();
+}
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/lib/raw-event-helper.js b/plugins/discourse-calendar/assets/javascripts/discourse/lib/raw-event-helper.js
new file mode 100644
index 00000000000..6950e10fb85
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/lib/raw-event-helper.js
@@ -0,0 +1,136 @@
+export function buildParams(startsAt, endsAt, event, siteSettings) {
+ const params = {};
+
+ const eventTz = event.timezone || "UTC";
+
+ params.start = moment(startsAt).tz(eventTz).format("YYYY-MM-DD HH:mm");
+
+ if (event.isClosed) {
+ params.closed = "true";
+ }
+
+ if (event.status) {
+ params.status = event.status;
+ }
+
+ if (event.name) {
+ params.name = event.name;
+ }
+
+ if (event.location) {
+ params.location = event.location;
+ }
+
+ if (event.description) {
+ params.description = event.description;
+ }
+
+ if (event.url) {
+ params.url = event.url;
+ }
+
+ if (event.timezone) {
+ params.timezone = event.timezone;
+ }
+
+ if (event.recurrence) {
+ params.recurrence = event.recurrence;
+ }
+
+ if (event.recurrenceUntil) {
+ params.recurrenceUntil = moment(event.recurrenceUntil)
+ .tz(eventTz)
+ .format("YYYY-MM-DD HH:mm");
+ }
+
+ if (event.showLocalTime) {
+ params.showLocalTime = "true";
+ }
+
+ if (event.minimal) {
+ params.minimal = "true";
+ }
+
+ if (event.chatEnabled) {
+ params.chatEnabled = "true";
+ }
+
+ if (endsAt) {
+ params.end = moment(endsAt).tz(eventTz).format("YYYY-MM-DD HH:mm");
+ }
+
+ if (event.status === "private") {
+ params.allowedGroups = (event.rawInvitees || []).join(",");
+ }
+
+ if (event.status === "public") {
+ params.allowedGroups = "trust_level_0";
+ }
+
+ if (event.reminders && event.reminders.length) {
+ params.reminders = event.reminders
+ .map((r) => {
+ // we create a new intermediate object to avoid changes in the UI while
+ // we prepare the values for request
+ const reminder = Object.assign({}, r);
+
+ if (reminder.period === "after") {
+ reminder.value = `-${Math.abs(parseInt(reminder.value, 10))}`;
+ }
+ if (reminder.period === "before") {
+ reminder.value = Math.abs(parseInt(`${reminder.value}`, 10));
+ }
+
+ return `${reminder.type}.${reminder.value}.${reminder.unit}`;
+ })
+ .join(",");
+ }
+
+ siteSettings.discourse_post_event_allowed_custom_fields
+ .split("|")
+ .filter(Boolean)
+ .forEach((setting) => {
+ const param = camelCase(setting);
+ if (typeof event.customFields[setting] !== "undefined") {
+ params[param] = event.customFields[setting];
+ }
+ });
+
+ return params;
+}
+
+export function replaceRaw(params, raw) {
+ const eventRegex = /\[event (.*?)\](.*?)\[\/event\]/s;
+ const eventMatches = raw.match(eventRegex);
+
+ if (eventMatches && eventMatches[1]) {
+ const markdownParams = [];
+
+ let description = params.description;
+ description = description ? `${description}\n` : "";
+ delete params.description;
+
+ Object.keys(params).forEach((param) => {
+ const value = params[param];
+ if (value && value.length) {
+ markdownParams.push(`${param}="${value.replace(/"/g, "")}"`);
+ }
+ });
+
+ return raw.replace(
+ eventRegex,
+ `[event ${markdownParams.join(" ")}]\n${description}[/event]`
+ );
+ }
+
+ return false;
+}
+
+function camelCase(input) {
+ return input
+ .toLowerCase()
+ .replace(/-/g, "_")
+ .replace(/_(.)/g, function (match, group1) {
+ return group1.toUpperCase();
+ });
+}
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/lib/regions.js b/plugins/discourse-calendar/assets/javascripts/discourse/lib/regions.js
new file mode 100644
index 00000000000..48d3e98ff39
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/lib/regions.js
@@ -0,0 +1,481 @@
+// DO NOT EDIT THIS FILE!!!
+// Update it by running `rake javascript:update_constants`
+
+export const HOLIDAY_REGIONS = [
+ "ae",
+ "ar",
+ "at",
+ "au",
+ "au_nsw",
+ "au_vic",
+ "au_qld",
+ "au_nt",
+ "au_act",
+ "au_sa",
+ "au_wa",
+ "au_tas",
+ "au_tas_south",
+ "au_qld_cairns",
+ "au_qld_brisbane",
+ "au_tas_north",
+ "au_vic_melbourne",
+ "be_fr",
+ "be_nl",
+ "br",
+ "br_spcapital",
+ "br_sp",
+ "bg_en",
+ "bg_bg",
+ "ca",
+ "ca_qc",
+ "ca_ab",
+ "ca_sk",
+ "ca_on",
+ "ca_bc",
+ "ca_nb",
+ "ca_mb",
+ "ca_ns",
+ "ca_pe",
+ "ca_nl",
+ "ca_nt",
+ "ca_nu",
+ "ca_yt",
+ "us",
+ "ch_zh",
+ "ch_be",
+ "ch_lu",
+ "ch_ur",
+ "ch_sz",
+ "ch_ow",
+ "ch_nw",
+ "ch_gl",
+ "ch_zg",
+ "ch_fr",
+ "ch_so",
+ "ch_bs",
+ "ch_bl",
+ "ch_sh",
+ "ch_ar",
+ "ch_ai",
+ "ch_sg",
+ "ch_gr",
+ "ch_ag",
+ "ch_tg",
+ "ch_ti",
+ "ch_vd",
+ "ch_ne",
+ "ch_ge",
+ "ch_ju",
+ "ch_vs",
+ "ch",
+ "cl",
+ "co",
+ "cr",
+ "cz",
+ "dk",
+ "de",
+ "de_bw",
+ "de_by",
+ "de_he",
+ "de_nw",
+ "de_rp",
+ "de_sl",
+ "de_sn_sorbian",
+ "de_th_cath",
+ "de_sn",
+ "de_st",
+ "de_be",
+ "de_by_cath",
+ "de_by_augsburg",
+ "de_bb",
+ "de_mv",
+ "de_th",
+ "de_hb",
+ "de_hh",
+ "de_ni",
+ "de_sh",
+ "ee",
+ "el",
+ "es_pv",
+ "es_na",
+ "es_an",
+ "es_ib",
+ "es_cm",
+ "es_mu",
+ "es_m",
+ "es_ar",
+ "es_cl",
+ "es_cn",
+ "es_lo",
+ "es_ga",
+ "es_ce",
+ "es_o",
+ "es_ex",
+ "es",
+ "es_ct",
+ "es_v",
+ "es_vc",
+ "fi",
+ "fr_a",
+ "fr_m",
+ "fr",
+ "gb",
+ "gb_eng",
+ "gb_wls",
+ "gb_eaw",
+ "gb_nir",
+ "je",
+ "gb_jsy",
+ "gg",
+ "gb_gsy",
+ "gb_sct",
+ "gb_con",
+ "im",
+ "gb_iom",
+ "ge",
+ "gh",
+ "hr",
+ "hk",
+ "hu",
+ "id",
+ "ie",
+ "in",
+ "in_mh",
+ "in_gj",
+ "in_ka",
+ "in_tn",
+ "is",
+ "it",
+ "it_ve",
+ "it_tv",
+ "it_vr",
+ "it_pd",
+ "it_fi",
+ "it_ge",
+ "it_to",
+ "it_rm",
+ "it_vi",
+ "it_bl",
+ "it_ro",
+ "kr",
+ "kz",
+ "li",
+ "lt",
+ "lv",
+ "ma",
+ "mt_mt",
+ "mt_en",
+ "mx",
+ "mx_pue",
+ "nl",
+ "lu",
+ "no",
+ "nz",
+ "nz_sl",
+ "nz_we",
+ "nz_ak",
+ "nz_nl",
+ "nz_ne",
+ "nz_ot",
+ "nz_ta",
+ "nz_sc",
+ "nz_hb",
+ "nz_mb",
+ "nz_ca",
+ "nz_ch",
+ "nz_wl",
+ "pe",
+ "ph",
+ "pl",
+ "pt",
+ "pt_li",
+ "pt_po",
+ "ro",
+ "rs_cyrl",
+ "rs_la",
+ "ru",
+ "se",
+ "sa",
+ "tn",
+ "tr",
+ "ua",
+ "us_fl",
+ "us_la",
+ "us_ct",
+ "us_de",
+ "us_gu",
+ "us_hi",
+ "us_in",
+ "us_ky",
+ "us_nj",
+ "us_nc",
+ "us_nd",
+ "us_pr",
+ "us_tn",
+ "us_ms",
+ "us_id",
+ "us_ar",
+ "us_tx",
+ "us_dc",
+ "us_md",
+ "us_va",
+ "us_vt",
+ "us_ak",
+ "us_ca",
+ "us_me",
+ "us_ma",
+ "us_al",
+ "us_ga",
+ "us_ne",
+ "us_mo",
+ "us_sc",
+ "us_wv",
+ "us_vi",
+ "us_ut",
+ "us_ri",
+ "us_az",
+ "us_co",
+ "us_oh",
+ "us_or",
+ "us_sd",
+ "us_wy",
+ "us_nv",
+ "us_mt",
+ "us_ny",
+ "us_pa",
+ "us_nm",
+ "us_ia",
+ "us_il",
+ "us_ks",
+ "us_mi",
+ "us_mn",
+ "us_nh",
+ "us_ok",
+ "us_wa",
+ "us_wi",
+ "za",
+ "ve",
+ "sk",
+ "si",
+ "jp",
+ "vi",
+ "sg",
+ "my",
+ "th",
+ "ng",
+ "ke",
+ "zw",
+];
+
+export const TIME_ZONE_TO_REGION = {
+ "Africa/Accra": "gh",
+ "Africa/Casablanca": "ma",
+ "Africa/Ceuta": "es",
+ "Africa/Harare": "zw",
+ "Africa/Johannesburg": "za",
+ "Africa/Lagos": "ng",
+ "Africa/Nairobi": "ke",
+ "Africa/Tunis": "tn",
+ "America/Adak": "us",
+ "America/Anchorage": "us",
+ "America/Araguaina": "br",
+ "America/Argentina/Buenos_Aires": "ar",
+ "America/Argentina/Catamarca": "ar",
+ "America/Argentina/Cordoba": "ar",
+ "America/Argentina/Jujuy": "ar",
+ "America/Argentina/La_Rioja": "ar",
+ "America/Argentina/Mendoza": "ar",
+ "America/Argentina/Rio_Gallegos": "ar",
+ "America/Argentina/Salta": "ar",
+ "America/Argentina/San_Juan": "ar",
+ "America/Argentina/San_Luis": "ar",
+ "America/Argentina/Tucuman": "ar",
+ "America/Argentina/Ushuaia": "ar",
+ "America/Atikokan": "ca",
+ "America/Bahia": "br",
+ "America/Bahia_Banderas": "mx",
+ "America/Belem": "br",
+ "America/Blanc-Sablon": "ca",
+ "America/Boa_Vista": "br",
+ "America/Bogota": "co",
+ "America/Boise": "us",
+ "America/Cambridge_Bay": "ca",
+ "America/Campo_Grande": "br",
+ "America/Cancun": "mx",
+ "America/Caracas": "ve",
+ "America/Chicago": "us",
+ "America/Chihuahua": "mx",
+ "America/Ciudad_Juarez": "mx",
+ "America/Costa_Rica": "cr",
+ "America/Coyhaique": "cl",
+ "America/Creston": "ca",
+ "America/Cuiaba": "br",
+ "America/Dawson": "ca",
+ "America/Dawson_Creek": "ca",
+ "America/Denver": "us",
+ "America/Detroit": "us",
+ "America/Edmonton": "ca",
+ "America/Eirunepe": "br",
+ "America/Fort_Nelson": "ca",
+ "America/Fortaleza": "br",
+ "America/Glace_Bay": "ca",
+ "America/Goose_Bay": "ca",
+ "America/Halifax": "ca",
+ "America/Hermosillo": "mx",
+ "America/Indiana/Indianapolis": "us",
+ "America/Indiana/Knox": "us",
+ "America/Indiana/Marengo": "us",
+ "America/Indiana/Petersburg": "us",
+ "America/Indiana/Tell_City": "us",
+ "America/Indiana/Vevay": "us",
+ "America/Indiana/Vincennes": "us",
+ "America/Indiana/Winamac": "us",
+ "America/Inuvik": "ca",
+ "America/Iqaluit": "ca",
+ "America/Juneau": "us",
+ "America/Kentucky/Louisville": "us",
+ "America/Kentucky/Monticello": "us",
+ "America/Lima": "pe",
+ "America/Los_Angeles": "us",
+ "America/Maceio": "br",
+ "America/Manaus": "br",
+ "America/Matamoros": "mx",
+ "America/Mazatlan": "mx",
+ "America/Menominee": "us",
+ "America/Merida": "mx",
+ "America/Metlakatla": "us",
+ "America/Mexico_City": "mx",
+ "America/Moncton": "ca",
+ "America/Monterrey": "mx",
+ "America/New_York": "us",
+ "America/Nome": "us",
+ "America/Noronha": "br",
+ "America/North_Dakota/Beulah": "us",
+ "America/North_Dakota/Center": "us",
+ "America/North_Dakota/New_Salem": "us",
+ "America/Ojinaga": "mx",
+ "America/Phoenix": "us",
+ "America/Porto_Velho": "br",
+ "America/Punta_Arenas": "cl",
+ "America/Rankin_Inlet": "ca",
+ "America/Recife": "br",
+ "America/Regina": "ca",
+ "America/Resolute": "ca",
+ "America/Rio_Branco": "br",
+ "America/Santarem": "br",
+ "America/Santiago": "cl",
+ "America/Sao_Paulo": "br",
+ "America/Sitka": "us",
+ "America/St_Johns": "ca",
+ "America/St_Thomas": "vi",
+ "America/Swift_Current": "ca",
+ "America/Tijuana": "mx",
+ "America/Toronto": "ca",
+ "America/Vancouver": "ca",
+ "America/Whitehorse": "ca",
+ "America/Winnipeg": "ca",
+ "America/Yakutat": "us",
+ "Antarctica/Macquarie": "au",
+ "Asia/Almaty": "kz",
+ "Asia/Anadyr": "ru",
+ "Asia/Aqtau": "kz",
+ "Asia/Aqtobe": "kz",
+ "Asia/Atyrau": "kz",
+ "Asia/Bangkok": "th",
+ "Asia/Barnaul": "ru",
+ "Asia/Chita": "ru",
+ "Asia/Dubai": "ae",
+ "Asia/Hong_Kong": "hk",
+ "Asia/Irkutsk": "ru",
+ "Asia/Jakarta": "id",
+ "Asia/Jayapura": "id",
+ "Asia/Kamchatka": "ru",
+ "Asia/Khandyga": "ru",
+ "Asia/Kolkata": "in",
+ "Asia/Krasnoyarsk": "ru",
+ "Asia/Kuala_Lumpur": "my",
+ "Asia/Kuching": "my",
+ "Asia/Magadan": "ru",
+ "Asia/Makassar": "id",
+ "Asia/Manila": "ph",
+ "Asia/Novokuznetsk": "ru",
+ "Asia/Novosibirsk": "ru",
+ "Asia/Omsk": "ru",
+ "Asia/Oral": "kz",
+ "Asia/Pontianak": "id",
+ "Asia/Qostanay": "kz",
+ "Asia/Qyzylorda": "kz",
+ "Asia/Riyadh": "sa",
+ "Asia/Sakhalin": "ru",
+ "Asia/Seoul": "kr",
+ "Asia/Singapore": "sg",
+ "Asia/Srednekolymsk": "ru",
+ "Asia/Tbilisi": "ge",
+ "Asia/Tokyo": "jp",
+ "Asia/Tomsk": "ru",
+ "Asia/Ust-Nera": "ru",
+ "Asia/Vladivostok": "ru",
+ "Asia/Yakutsk": "ru",
+ "Asia/Yekaterinburg": "ru",
+ "Atlantic/Azores": "pt",
+ "Atlantic/Canary": "es",
+ "Atlantic/Madeira": "pt",
+ "Atlantic/Reykjavik": "is",
+ "Australia/Adelaide": "au",
+ "Australia/Brisbane": "au",
+ "Australia/Broken_Hill": "au",
+ "Australia/Darwin": "au",
+ "Australia/Eucla": "au",
+ "Australia/Hobart": "au",
+ "Australia/Lindeman": "au",
+ "Australia/Lord_Howe": "au",
+ "Australia/Melbourne": "au",
+ "Australia/Perth": "au",
+ "Australia/Sydney": "au",
+ "Europe/Amsterdam": "nl",
+ "Europe/Astrakhan": "ru",
+ "Europe/Athens": "el",
+ "Europe/Berlin": "de",
+ "Europe/Bratislava": "sk",
+ "Europe/Bucharest": "ro",
+ "Europe/Budapest": "hu",
+ "Europe/Busingen": "de",
+ "Europe/Copenhagen": "dk",
+ "Europe/Dublin": "ie",
+ "Europe/Guernsey": "gg",
+ "Europe/Helsinki": "fi",
+ "Europe/Isle_of_Man": "im",
+ "Europe/Istanbul": "tr",
+ "Europe/Jersey": "je",
+ "Europe/Kaliningrad": "ru",
+ "Europe/Kirov": "ru",
+ "Europe/Kyiv": "ua",
+ "Europe/Lisbon": "pt",
+ "Europe/Ljubljana": "si",
+ "Europe/London": "gb",
+ "Europe/Luxembourg": "lu",
+ "Europe/Madrid": "es",
+ "Europe/Moscow": "ru",
+ "Europe/Oslo": "no",
+ "Europe/Paris": "fr",
+ "Europe/Prague": "cz",
+ "Europe/Riga": "lv",
+ "Europe/Rome": "it",
+ "Europe/Samara": "ru",
+ "Europe/Saratov": "ru",
+ "Europe/Simferopol": "ru",
+ "Europe/Stockholm": "se",
+ "Europe/Tallinn": "ee",
+ "Europe/Ulyanovsk": "ru",
+ "Europe/Vaduz": "li",
+ "Europe/Vienna": "at",
+ "Europe/Vilnius": "lt",
+ "Europe/Volgograd": "ru",
+ "Europe/Warsaw": "pl",
+ "Europe/Zagreb": "hr",
+ "Europe/Zurich": "ch",
+ "Pacific/Auckland": "nz",
+ "Pacific/Chatham": "nz",
+ "Pacific/Easter": "cl",
+ "Pacific/Honolulu": "us",
+};
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/lib/round-time.js b/plugins/discourse-calendar/assets/javascripts/discourse/lib/round-time.js
new file mode 100644
index 00000000000..ecc35873775
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/lib/round-time.js
@@ -0,0 +1,84 @@
+// https://github.com/WebDevTmas/moment-round
+if (typeof moment.fn.round !== "function") {
+ moment.fn.round = function (precision, key, direction) {
+ direction = direction || "round";
+ let _this = this; //cache of this
+ let methods = {
+ hours: { name: "Hours", maxValue: 24 },
+ minutes: { name: "Minutes", maxValue: 60 },
+ seconds: { name: "Seconds", maxValue: 60 },
+ milliseconds: { name: "Milliseconds", maxValue: 1000 },
+ };
+ let keys = {
+ mm: methods.milliseconds.name,
+ milliseconds: methods.milliseconds.name,
+ Milliseconds: methods.milliseconds.name,
+ s: methods.seconds.name,
+ seconds: methods.seconds.name,
+ Seconds: methods.seconds.name,
+ m: methods.minutes.name,
+ minutes: methods.minutes.name,
+ Minutes: methods.minutes.name,
+ H: methods.hours.name,
+ h: methods.hours.name,
+ hours: methods.hours.name,
+ Hours: methods.hours.name,
+ };
+ let value = 0;
+ let rounded = false;
+ let subRatio = 1;
+ let maxValue;
+
+ // make sure key is plural
+ if (key.length > 1 && key !== "mm" && key.slice(-1) !== "s") {
+ key += "s";
+ }
+ key = keys[key].toLowerCase();
+
+ //control
+ if (!methods[key]) {
+ throw new Error(
+ 'The value to round is not valid. Possibles ["hours", "minutes", "seconds", "milliseconds"]'
+ );
+ }
+
+ let get = "get" + methods[key].name;
+ let set = "set" + methods[key].name;
+
+ for (let k in methods) {
+ if (k === key) {
+ value = _this._d[get]();
+ maxValue = methods[k].maxValue;
+ rounded = true;
+ } else if (rounded) {
+ subRatio *= methods[k].maxValue;
+ value += _this._d["get" + methods[k].name]() / subRatio;
+ _this._d["set" + methods[k].name](0);
+ }
+ }
+
+ value = Math[direction](value / precision) * precision;
+ value = Math.min(value, maxValue);
+ _this._d[set](value);
+
+ return _this;
+ };
+}
+
+if (typeof moment.fn.ceil !== "function") {
+ moment.fn.ceil = function (precision, key) {
+ return this.round(precision, key, "ceil");
+ };
+}
+
+if (typeof moment.fn.floor !== "function") {
+ moment.fn.floor = function (precision, key) {
+ return this.round(precision, key, "floor");
+ };
+}
+
+const STEP = 15;
+
+export default function roundTime(date) {
+ return date.round(STEP, "minutes");
+}
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/models/discourse-post-event-event-stats.js b/plugins/discourse-calendar/assets/javascripts/discourse/models/discourse-post-event-event-stats.js
new file mode 100644
index 00000000000..dc9078e2dea
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/models/discourse-post-event-event-stats.js
@@ -0,0 +1,19 @@
+import { tracked } from "@glimmer/tracking";
+
+export default class DiscoursePostEventEventStats {
+ static create(args = {}) {
+ return new DiscoursePostEventEventStats(args);
+ }
+
+ @tracked going = 0;
+ @tracked interested = 0;
+ @tracked invited = 0;
+ @tracked notGoing = 0;
+
+ constructor(args = {}) {
+ this.going = args.going;
+ this.invited = args.invited;
+ this.interested = args.interested;
+ this.notGoing = args.not_going;
+ }
+}
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/models/discourse-post-event-event.js b/plugins/discourse-calendar/assets/javascripts/discourse/models/discourse-post-event-event.js
new file mode 100644
index 00000000000..74caa6e369e
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/models/discourse-post-event-event.js
@@ -0,0 +1,204 @@
+import { tracked } from "@glimmer/tracking";
+import EmberObject from "@ember/object";
+import { TrackedArray } from "@ember-compat/tracked-built-ins";
+import { bind } from "discourse/lib/decorators";
+import { optionalRequire } from "discourse/lib/utilities";
+import User from "discourse/models/user";
+import DiscoursePostEventEventStats from "./discourse-post-event-event-stats";
+import DiscoursePostEventInvitee from "./discourse-post-event-invitee";
+
+const ChatChannel = optionalRequire(
+ "discourse/plugins/chat/discourse/models/chat-channel"
+);
+
+const DEFAULT_REMINDER = {
+ type: "notification",
+ value: 15,
+ unit: "minutes",
+ period: "before",
+};
+
+export default class DiscoursePostEventEvent {
+ static create(args = {}) {
+ return new DiscoursePostEventEvent(args);
+ }
+
+ @tracked title;
+ @tracked name;
+ @tracked categoryId;
+ @tracked startsAt;
+ @tracked endsAt;
+ @tracked rawInvitees;
+ @tracked location;
+ @tracked url;
+ @tracked description;
+ @tracked timezone;
+ @tracked showLocalTime;
+ @tracked status;
+ @tracked post;
+ @tracked minimal;
+ @tracked chatEnabled;
+ @tracked canUpdateAttendance;
+ @tracked canActOnDiscoursePostEvent;
+ @tracked shouldDisplayInvitees;
+ @tracked isClosed;
+ @tracked isExpired;
+ @tracked isStandalone;
+ @tracked recurrenceUntil;
+ @tracked recurrence;
+ @tracked recurrenceRule;
+ @tracked customFields;
+ @tracked channel;
+
+ @tracked _watchingInvitee;
+ @tracked _sampleInvitees;
+ @tracked _stats;
+ @tracked _creator;
+ @tracked _reminders;
+
+ constructor(args = {}) {
+ this.id = args.id;
+ this.name = args.name;
+ this.categoryId = args.category_id;
+ this.upcomingDates = args.upcoming_dates;
+ this.startsAt = args.starts_at;
+ this.endsAt = args.ends_at;
+ this.rawInvitees = args.raw_invitees;
+ this.sampleInvitees = args.sample_invitees || [];
+ this.location = args.location;
+ this.url = args.url;
+ this.description = args.description;
+ this.timezone = args.timezone;
+ this.showLocalTime = args.show_local_time;
+ this.status = args.status;
+ this.creator = args.creator;
+ this.post = args.post;
+ this.isClosed = args.is_closed;
+ this.isExpired = args.is_expired;
+ this.isStandalone = args.is_standalone;
+ this.minimal = args.minimal;
+ this.chatEnabled = args.chat_enabled;
+ this.recurrenceRule = args.recurrence_rule;
+ this.recurrence = args.recurrence;
+ this.recurrenceUntil = args.recurrence_until;
+ this.canUpdateAttendance = args.can_update_attendance;
+ this.canActOnDiscoursePostEvent = args.can_act_on_discourse_post_event;
+ this.shouldDisplayInvitees = args.should_display_invitees;
+ this.watchingInvitee = args.watching_invitee;
+ this.stats = args.stats;
+ this.reminders = args.reminders;
+ this.customFields = EmberObject.create(args.custom_fields || {});
+ if (args.channel && ChatChannel) {
+ this.channel = ChatChannel.create(args.channel);
+ }
+ }
+
+ get watchingInvitee() {
+ return this._watchingInvitee;
+ }
+
+ set watchingInvitee(invitee) {
+ this._watchingInvitee = invitee
+ ? DiscoursePostEventInvitee.create(invitee)
+ : null;
+ }
+
+ get sampleInvitees() {
+ return this._sampleInvitees;
+ }
+
+ set sampleInvitees(invitees = []) {
+ this._sampleInvitees = new TrackedArray(
+ invitees.map((i) => DiscoursePostEventInvitee.create(i))
+ );
+ }
+
+ get stats() {
+ return this._stats;
+ }
+
+ set stats(stats) {
+ this._stats = this.#initStatsModel(stats);
+ }
+
+ get reminders() {
+ return this._reminders;
+ }
+
+ set reminders(reminders = []) {
+ this._reminders = new TrackedArray(reminders);
+ }
+
+ get creator() {
+ return this._creator;
+ }
+
+ set creator(user) {
+ this._creator = this.#initUserModel(user);
+ }
+
+ get isPublic() {
+ return this.status === "public";
+ }
+
+ get isPrivate() {
+ return this.status === "private";
+ }
+
+ updateFromEvent(event) {
+ this.name = event.name;
+ this.startsAt = event.startsAt;
+ this.endsAt = event.endsAt;
+ this.location = event.location;
+ this.url = event.url;
+ this.timezone = event.timezone;
+ this.showLocalTime = event.showLocalTime;
+ this.description = event.description;
+ this.status = event.status;
+ this.creator = event.creator;
+ this.isClosed = event.isClosed;
+ this.isExpired = event.isExpired;
+ this.isStandalone = event.isStandalone;
+ this.minimal = event.minimal;
+ this.chatEnabled = event.chatEnabled;
+ this.recurrenceRule = event.recurrenceRule;
+ this.recurrence = event.recurrence;
+ this.recurrenceUntil = event.recurrenceUntil;
+ this.canUpdateAttendance = event.canUpdateAttendance;
+ this.canActOnDiscoursePostEvent = event.canActOnDiscoursePostEvent;
+ this.shouldDisplayInvitees = event.shouldDisplayInvitees;
+ this.stats = event.stats;
+ this.sampleInvitees = event.sampleInvitees || [];
+ this.reminders = event.reminders;
+ }
+
+ @bind
+ removeReminder(reminder) {
+ const index = this.reminders.findIndex((r) => r.id === reminder.id);
+ if (index > -1) {
+ this.reminders.splice(index, 1);
+ }
+ }
+
+ @bind
+ addReminder(reminder) {
+ reminder ??= { ...DEFAULT_REMINDER };
+ this.reminders.push(reminder);
+ }
+
+ #initUserModel(user) {
+ if (!user || user instanceof User) {
+ return user;
+ }
+
+ return User.create(user);
+ }
+
+ #initStatsModel(stats) {
+ if (!stats || stats instanceof DiscoursePostEventEventStats) {
+ return stats;
+ }
+
+ return DiscoursePostEventEventStats.create(stats);
+ }
+}
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/models/discourse-post-event-invitee.js b/plugins/discourse-calendar/assets/javascripts/discourse/models/discourse-post-event-invitee.js
new file mode 100644
index 00000000000..7c6db707918
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/models/discourse-post-event-invitee.js
@@ -0,0 +1,25 @@
+import { tracked } from "@glimmer/tracking";
+import User from "discourse/models/user";
+
+export default class DiscoursePostEventInvitee {
+ static create(args = {}) {
+ return new DiscoursePostEventInvitee(args);
+ }
+
+ @tracked status;
+
+ constructor(args = {}) {
+ this.id = args.id;
+ this.post_id = args.post_id;
+ this.status = args.status;
+ this.user = this.#initUserModel(args.user);
+ }
+
+ #initUserModel(user) {
+ if (!user || user instanceof User) {
+ return user;
+ }
+
+ return User.create(user);
+ }
+}
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/models/discourse-post-event-invitees.js b/plugins/discourse-calendar/assets/javascripts/discourse/models/discourse-post-event-invitees.js
new file mode 100644
index 00000000000..64391f2769f
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/models/discourse-post-event-invitees.js
@@ -0,0 +1,50 @@
+import { tracked } from "@glimmer/tracking";
+import { TrackedArray } from "@ember-compat/tracked-built-ins";
+import User from "discourse/models/user";
+import DiscoursePostEventInvitee from "./discourse-post-event-invitee";
+
+export default class DiscoursePostEventInvitees {
+ static create(args = {}) {
+ return new DiscoursePostEventInvitees(args);
+ }
+
+ @tracked _invitees;
+ @tracked _suggestedUsers;
+
+ constructor(args = {}) {
+ this.invitees = args.invitees || [];
+ this.suggestedUsers = args.meta?.suggested_users || [];
+ }
+
+ get invitees() {
+ return this._invitees;
+ }
+
+ set invitees(invitees = []) {
+ this._invitees = new TrackedArray(
+ invitees.map((i) => DiscoursePostEventInvitee.create(i))
+ );
+ }
+
+ get suggestedUsers() {
+ return this._suggestedUsers;
+ }
+
+ set suggestedUsers(suggestedUsers = []) {
+ this._suggestedUsers = new TrackedArray(
+ suggestedUsers.map((su) => User.create(su))
+ );
+ }
+
+ add(invitee) {
+ this.invitees.push(invitee);
+
+ this.suggestedUsers = this.suggestedUsers.filter(
+ (su) => su.id !== invitee.user.id
+ );
+ }
+
+ remove(invitee) {
+ this.invitees = this.invitees.filter((i) => i.user.id !== invitee.user.id);
+ }
+}
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/models/discourse-post-event-reminder.js b/plugins/discourse-calendar/assets/javascripts/discourse/models/discourse-post-event-reminder.js
new file mode 100644
index 00000000000..4decadac9e0
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/models/discourse-post-event-reminder.js
@@ -0,0 +1,9 @@
+import RestModel from "discourse/models/rest";
+
+export default class DiscoursePostEventReminder extends RestModel {
+ init() {
+ super.init(...arguments);
+
+ this.__type = "discourse-post-event-reminder";
+ }
+}
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/pre-initializers/rich-editor-extension.js b/plugins/discourse-calendar/assets/javascripts/discourse/pre-initializers/rich-editor-extension.js
new file mode 100644
index 00000000000..7131e49ff06
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/pre-initializers/rich-editor-extension.js
@@ -0,0 +1,105 @@
+import { camelize } from "@ember/string";
+import { withPluginApi } from "discourse/lib/plugin-api";
+import { buildEventPreview } from "../initializers/discourse-post-event-decorator";
+
+const EVENT_ATTRIBUTES = {
+ name: { default: null },
+ start: { default: null },
+ end: { default: null },
+ reminders: { default: null },
+ minimal: { default: null },
+ closed: { default: null },
+ status: { default: "public" },
+ timezone: { default: "UTC" },
+ showLocalTime: { default: null },
+ allowedGroups: { default: null },
+ recurrence: { default: null },
+ recurrenceUntil: { default: null },
+ chatEnabled: { default: null },
+ chatChannelId: { default: null },
+};
+
+/** @type {RichEditorExtension} */
+const extension = {
+ nodeSpec: {
+ event: {
+ attrs: EVENT_ATTRIBUTES,
+ group: "block",
+ defining: true,
+ isolating: true,
+ draggable: true,
+ parseDOM: [
+ {
+ tag: "div.discourse-post-event",
+ getAttrs(dom) {
+ return { ...dom.dataset };
+ },
+ },
+ ],
+ toDOM(node) {
+ const element = document.createElement("div");
+ element.classList.add("discourse-post-event");
+ for (const [key, value] of Object.entries(node.attrs)) {
+ if (value !== null) {
+ element.dataset[key] = value;
+ }
+ }
+
+ buildEventPreview(element);
+
+ return element;
+ },
+ },
+ },
+
+ parse: {
+ wrap_bbcode(state, token) {
+ if (token.tag === "div") {
+ if (token.nesting === -1 && state.top().type.name === "event") {
+ state.closeNode();
+ return true;
+ }
+
+ if (
+ token.nesting === 1 &&
+ token.attrGet("class") === "discourse-post-event"
+ ) {
+ const attrs = Object.fromEntries(
+ token.attrs
+ .filter(([key]) => key.startsWith("data-"))
+ .map(([key, value]) => [camelize(key.slice(5)), value])
+ );
+
+ state.openNode(state.schema.nodes.event, attrs);
+ return true;
+ }
+ }
+
+ return false;
+ },
+ },
+
+ serializeNode: {
+ event(state, node) {
+ let bbcode = "[event";
+
+ Object.entries(node.attrs).forEach(([key, value]) => {
+ if (value !== null) {
+ bbcode += ` ${key}="${value}"`;
+ }
+ });
+
+ bbcode += "]\n[/event]\n";
+
+ state.write(bbcode);
+ },
+ },
+};
+
+export default {
+ initialize() {
+ withPluginApi("2.1.1", (api) => {
+ api.registerRichEditorExtension(extension);
+ });
+ },
+};
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/pre-initializers/transformers.js b/plugins/discourse-calendar/assets/javascripts/discourse/pre-initializers/transformers.js
new file mode 100644
index 00000000000..76c0dc18145
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/pre-initializers/transformers.js
@@ -0,0 +1,12 @@
+import { withPluginApi } from "discourse/lib/plugin-api";
+
+export default {
+ before: "freeze-valid-transformers",
+ initialize() {
+ withPluginApi("1.33.0", (api) => {
+ api.addValueTransformerName(
+ "discourse-calendar-event-more-menu-should-show-participants"
+ );
+ });
+ },
+};
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/routes/discourse-post-event-upcoming-events-index.js b/plugins/discourse-calendar/assets/javascripts/discourse/routes/discourse-post-event-upcoming-events-index.js
new file mode 100644
index 00000000000..135d2ff6eb0
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/routes/discourse-post-event-upcoming-events-index.js
@@ -0,0 +1,19 @@
+import { action } from "@ember/object";
+import { service } from "@ember/service";
+import DiscourseURL from "discourse/lib/url";
+import DiscourseRoute from "discourse/routes/discourse";
+
+export default class PostEventUpcomingEventsIndexRoute extends DiscourseRoute {
+ @service discoursePostEventService;
+
+ @action
+ activate() {
+ if (!this.siteSettings.discourse_post_event_enabled) {
+ DiscourseURL.redirectTo("/404");
+ }
+ }
+
+ async model(params) {
+ return await this.discoursePostEventService.fetchEvents(params);
+ }
+}
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/routes/discourse-post-event-upcoming-events-mine.gjs b/plugins/discourse-calendar/assets/javascripts/discourse/routes/discourse-post-event-upcoming-events-mine.gjs
new file mode 100644
index 00000000000..367697a1753
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/routes/discourse-post-event-upcoming-events-mine.gjs
@@ -0,0 +1,22 @@
+import { action } from "@ember/object";
+import { service } from "@ember/service";
+import DiscourseURL from "discourse/lib/url";
+import DiscourseRoute from "discourse/routes/discourse";
+
+export default class PostEventUpcomingEventsIndexRoute extends DiscourseRoute {
+ @service discoursePostEventApi;
+ @service discoursePostEventService;
+ @service currentUser;
+
+ @action
+ activate() {
+ if (!this.siteSettings.discourse_post_event_enabled) {
+ DiscourseURL.redirectTo("/404");
+ }
+ }
+
+ async model(params) {
+ params.attending_user = this.currentUser?.username;
+ return await this.discoursePostEventService.fetchEvents(params);
+ }
+}
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/services/discourse-post-event-api.js b/plugins/discourse-calendar/assets/javascripts/discourse/services/discourse-post-event-api.js
new file mode 100644
index 00000000000..41a83c4c5d7
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/services/discourse-post-event-api.js
@@ -0,0 +1,124 @@
+import Service from "@ember/service";
+import { ajax } from "discourse/lib/ajax";
+import DiscoursePostEventEvent from "discourse/plugins/discourse-calendar/discourse/models/discourse-post-event-event";
+import DiscoursePostEventInvitee from "discourse/plugins/discourse-calendar/discourse/models/discourse-post-event-invitee";
+import DiscoursePostEventInvitees from "discourse/plugins/discourse-calendar/discourse/models/discourse-post-event-invitees";
+
+/**
+ * Discoure post event API service. Provides methods to interact with the discourse post event API.
+ *
+ * @module DiscoursePostEventApi
+ * @implements {@ember/service}
+ */
+export default class DiscoursePostEventApi extends Service {
+ async event(id) {
+ const result = await this.#getRequest(`/events/${id}`);
+ return DiscoursePostEventEvent.create(result.event);
+ }
+
+ async events(data = {}) {
+ const result = await this.#getRequest("/events", data);
+ return result.events.map((e) => DiscoursePostEventEvent.create(e));
+ }
+
+ async listEventInvitees(event, data = {}) {
+ const result = await this.#getRequest(`/events/${event.id}/invitees`, data);
+ return DiscoursePostEventInvitees.create(result);
+ }
+
+ async updateEvent(event, data = {}) {
+ const updatedEvent = await this.#putRequest(`/events/${event.id}`, {
+ event: data,
+ });
+ event.updateFromEvent(updatedEvent);
+ return event;
+ }
+
+ async updateEventAttendance(event, data = {}) {
+ if (!event.watchingInvitee) {
+ return;
+ }
+
+ const result = await this.#putRequest(
+ `/events/${event.id}/invitees/${event.watchingInvitee.id}`,
+ { invitee: data }
+ );
+
+ event.watchingInvitee = DiscoursePostEventInvitee.create(result.invitee);
+
+ event.sampleInvitees.forEach((invitee) => {
+ if (invitee.id === event.watchingInvitee.id) {
+ invitee.status = event.watchingInvitee.status;
+ }
+ });
+
+ event.stats = result.invitee.meta.event_stats;
+ event.shouldDisplayInvitees =
+ result.invitee.meta.event_should_display_invitees;
+
+ return event;
+ }
+
+ async leaveEvent(event, invitee) {
+ await this.#deleteRequest(`/events/${event.id}/invitees/${invitee.id}`);
+
+ event.sampleInvitees = event.sampleInvitees.filter(
+ (i) => i.id !== invitee.id
+ );
+
+ if (event.watchingInvitee?.id === invitee.id) {
+ event.watchingInvitee = null;
+ }
+ }
+
+ async joinEvent(event, data = {}) {
+ const result = await this.#postRequest(`/events/${event.id}/invitees`, {
+ invitee: data,
+ });
+
+ const invitee = DiscoursePostEventInvitee.create(result.invitee);
+
+ if (!data.user_id) {
+ event.watchingInvitee = invitee;
+ event.sampleInvitees.push(event.watchingInvitee);
+ }
+
+ event.stats = result.invitee.meta.event_stats;
+ event.shouldDisplayInvitees =
+ result.invitee.meta.event_should_display_invitees;
+
+ return invitee;
+ }
+
+ get #basePath() {
+ return "/discourse-post-event";
+ }
+
+ #getRequest(endpoint, data = {}) {
+ return ajax(`${this.#basePath}${endpoint}`, {
+ type: "GET",
+ data,
+ });
+ }
+
+ #putRequest(endpoint, data = {}) {
+ return ajax(`${this.#basePath}${endpoint}`, {
+ type: "PUT",
+ data,
+ });
+ }
+
+ #postRequest(endpoint, data = {}) {
+ return ajax(`${this.#basePath}${endpoint}`, {
+ type: "POST",
+ data,
+ });
+ }
+
+ #deleteRequest(endpoint, data = {}) {
+ return ajax(`${this.#basePath}${endpoint}`, {
+ type: "DELETE",
+ data,
+ });
+ }
+}
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/services/discourse-post-event-service.js b/plugins/discourse-calendar/assets/javascripts/discourse/services/discourse-post-event-service.js
new file mode 100644
index 00000000000..1a78dee6e46
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/services/discourse-post-event-service.js
@@ -0,0 +1,14 @@
+import Service, { service } from "@ember/service";
+
+export default class DiscoursePostEventService extends Service {
+ @service siteSettings;
+ @service discoursePostEventApi;
+
+ async fetchEvents(params = {}) {
+ if (this.siteSettings.include_expired_events_on_calendar) {
+ params.include_expired = true;
+ }
+ const events = await this.discoursePostEventApi.events(params);
+ return await events;
+ }
+}
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/templates/admin-plugins-calendar.gjs b/plugins/discourse-calendar/assets/javascripts/discourse/templates/admin-plugins-calendar.gjs
new file mode 100644
index 00000000000..8561a457214
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/templates/admin-plugins-calendar.gjs
@@ -0,0 +1,33 @@
+import RouteTemplate from "ember-route-template";
+import ConditionalLoadingSpinner from "discourse/components/conditional-loading-spinner";
+import { i18n } from "discourse-i18n";
+import AdminHolidaysList from "../components/admin-holidays-list";
+import RegionInput from "../components/region-input";
+
+export default RouteTemplate(
+
+
+ {{i18n "discourse_calendar.holidays.header_title"}}
+
+
+
+
+
+ {{i18n "discourse_calendar.holidays.pick_region_description"}}
+
+ {{i18n "discourse_calendar.holidays.disabled_holidays_description"}}
+
+
+
+
+ {{#if @controller.model.holidays}}
+
+ {{/if}}
+
+);
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/templates/discourse-post-event-upcoming-events-index.gjs b/plugins/discourse-calendar/assets/javascripts/discourse/templates/discourse-post-event-upcoming-events-index.gjs
new file mode 100644
index 00000000000..bed50a0c42e
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/templates/discourse-post-event-upcoming-events-index.gjs
@@ -0,0 +1,10 @@
+import RouteTemplate from "ember-route-template";
+import UpcomingEventsCalendar from "../components/upcoming-events-calendar";
+
+export default RouteTemplate(
+
+
+
+
+
+);
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/templates/discourse-post-event-upcoming-events-mine.gjs b/plugins/discourse-calendar/assets/javascripts/discourse/templates/discourse-post-event-upcoming-events-mine.gjs
new file mode 100644
index 00000000000..bed50a0c42e
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/templates/discourse-post-event-upcoming-events-mine.gjs
@@ -0,0 +1,10 @@
+import RouteTemplate from "ember-route-template";
+import UpcomingEventsCalendar from "../components/upcoming-events-calendar";
+
+export default RouteTemplate(
+
+
+
+
+
+);
diff --git a/plugins/discourse-calendar/assets/javascripts/discourse/templates/discourse-post-event-upcoming-events.gjs b/plugins/discourse-calendar/assets/javascripts/discourse/templates/discourse-post-event-upcoming-events.gjs
new file mode 100644
index 00000000000..76ab5bd9893
--- /dev/null
+++ b/plugins/discourse-calendar/assets/javascripts/discourse/templates/discourse-post-event-upcoming-events.gjs
@@ -0,0 +1,3 @@
+import RouteTemplate from "ember-route-template";
+
+export default RouteTemplate({{outlet}});
diff --git a/plugins/discourse-calendar/assets/stylesheets/colors.scss b/plugins/discourse-calendar/assets/stylesheets/colors.scss
new file mode 100644
index 00000000000..8246c9ae866
--- /dev/null
+++ b/plugins/discourse-calendar/assets/stylesheets/colors.scss
@@ -0,0 +1,15 @@
+/* stylelint-disable scss/no-global-function-names */
+:root {
+ --calendar-normal: #{dark-light-choose(
+ lighten($tertiary, 55%),
+ darken($tertiary, 25%)
+ )};
+ --calendar-close-to-working-hours: #{dark-light-choose(
+ desaturate(lighten($tertiary, 45%), 15%),
+ darken($tertiary, 15%)
+ )};
+ --calendar-in-working-hours: #{dark-light-choose(
+ desaturate(lighten($tertiary, 40%), 20%),
+ darken($tertiary, 10%)
+ )};
+}
diff --git a/plugins/discourse-calendar/assets/stylesheets/common/discourse-calendar-holidays.scss b/plugins/discourse-calendar/assets/stylesheets/common/discourse-calendar-holidays.scss
new file mode 100644
index 00000000000..02b332388e8
--- /dev/null
+++ b/plugins/discourse-calendar/assets/stylesheets/common/discourse-calendar-holidays.scss
@@ -0,0 +1,9 @@
+.region-input {
+ width: 50%;
+}
+
+.disabled td {
+ background-color: var(--primary-very-low);
+ color: var(--primary-medium);
+ font-style: italic;
+}
diff --git a/plugins/discourse-calendar/assets/stylesheets/common/discourse-calendar.scss b/plugins/discourse-calendar/assets/stylesheets/common/discourse-calendar.scss
new file mode 100644
index 00000000000..22489e2deaf
--- /dev/null
+++ b/plugins/discourse-calendar/assets/stylesheets/common/discourse-calendar.scss
@@ -0,0 +1,433 @@
+.discourse-calendar-wrap {
+ margin: 0.5em 0;
+ border: 5px solid var(--primary-low);
+}
+
+.category-calendar .calendar {
+ overflow-y: scroll;
+}
+
+.before-topic-list-body-outlet.category-calendar {
+ display: table-caption;
+}
+
+.calendar.fc {
+ height: 645px; // Must be fixed to prevent height change on load
+ border: 0;
+
+ &.fc-unthemed {
+ tbody,
+ thead,
+ tr {
+ border: none;
+
+ td.fc-widget-content,
+ td.fc-widget-header {
+ border-left: 0;
+
+ &:last-child {
+ border-right: 0;
+ }
+ }
+ }
+ overflow: hidden;
+
+ .fc-scroller {
+ height: 560px !important;
+ padding-bottom: 5px;
+ }
+
+ .fc-basic-view .fc-day-top .fc-day-number {
+ float: left;
+ }
+
+ .fc-bg td.fc-today {
+ background-color: var(--highlight-medium);
+ border-style: solid;
+ }
+
+ .fc-month-view .fc-widget-content,
+ .fc-basicWeek-view .fc-widget-content,
+ .fc-head-container {
+ padding: 0;
+ }
+
+ .fc-bg tbody {
+ border-width: 0;
+ }
+
+ .fc-header-toolbar {
+ padding: 0.5em 0.5em 0 0.5em;
+ }
+
+ .fc-title {
+ @include ellipsis;
+ display: block;
+ }
+
+ .fc-event-container {
+ padding: 3px;
+ }
+
+ .fc-widget-header span {
+ padding: 3px 3px 3px 0.5em;
+ }
+
+ .fc-center {
+ display: none;
+ }
+
+ .fc-button {
+ border-radius: 0;
+ box-shadow: none;
+ background: var(--primary-low);
+ text-transform: capitalize;
+ color: var(--primary);
+ text-shadow: none;
+ border: none;
+ padding: 6px 12px;
+
+ &:hover {
+ background: var(--primary-medium);
+ color: var(--secondary);
+ }
+
+ &.fc-state-active {
+ background: var(--tertiary);
+ color: var(--secondary);
+ }
+ }
+
+ .fc-button-group {
+ // margin-right: 0;
+ .fc-button {
+ margin: 0;
+ }
+ }
+
+ .fc-divider,
+ .fc-list-empty,
+ .fc-list-heading td,
+ .fc-popover .fc-header {
+ background: var(--primary-low);
+ }
+
+ .fc-content,
+ .fc-divider,
+ .fc-list-heading td,
+ .fc-list-view,
+ .fc-popover,
+ .fc-row,
+ tbody,
+ td,
+ th,
+ thead {
+ border-color: var(--primary-low);
+ }
+ }
+
+ .fc-event,
+ .fc-event-dot {
+ background-color: var(--tertiary);
+ border: 1px solid transparent;
+
+ .fc-time {
+ display: none;
+ }
+
+ &.grouped-event {
+ background-color: var(--primary-low);
+ border: 1px solid var(--primary-low-mid);
+ color: var(--primary);
+
+ .emoji {
+ margin-right: 0.25em;
+ }
+ }
+ }
+
+ .fc-left {
+ .fc-button-group:first-child {
+ margin-left: 0;
+ }
+ }
+
+ .fc-list-item-add-to-calendar {
+ color: var(--tertiary);
+ font-size: var(--font-down-1);
+ }
+}
+
+a.holiday {
+ cursor: default;
+}
+
+.combo-box.user-timezone {
+ min-width: 15em;
+}
+
+.discourse-calendar-header {
+ display: flex;
+ width: 100%;
+ align-items: center;
+ justify-content: space-between;
+ box-sizing: border-box;
+ padding: 0.5em;
+ border-bottom: 1px solid var(--primary-low);
+ background: var(--primary-very-low);
+ min-height: 60px;
+
+ .discourse-calendar-timezone-picker {
+ font-size: 16px;
+ margin-bottom: 0;
+ max-width: 50vw;
+ }
+
+ h2.discourse-calendar-title {
+ margin: 0 !important;
+ }
+
+ .title {
+ font-weight: 700;
+ text-transform: capitalize;
+ }
+}
+
+.group-timezones {
+ display: grid;
+ width: 100%;
+ box-sizing: border-box;
+
+ &[data-size="auto"],
+ &.auto {
+ height: auto;
+ }
+
+ &[data-size="small"],
+ &.small {
+ height: 175px;
+ }
+
+ &[data-size="medium"],
+ &.medium {
+ height: 300px;
+ }
+
+ &[data-size="large"],
+ &.large {
+ height: 600px;
+ }
+}
+
+.group-timezones-header {
+ display: flex;
+ width: 100%;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 0.5em;
+ box-sizing: border-box;
+
+ .title {
+ font-weight: bold;
+ text-transform: capitalize;
+ }
+}
+
+.group-timezones-time-traveler {
+ display: flex;
+ align-items: center;
+
+ .time {
+ font-weight: 700;
+ margin-right: 0.5em;
+ min-width: 45px;
+ }
+}
+
+.mobile-view .group-timezones-time-traveler,
+.mobile-view .group-timezones-filter {
+ display: none;
+}
+
+.discourse-group-timezones-slider-wrapper {
+ // we need the wrapper because Firefox doesn't allow pseudo selectors on inputs
+ position: relative;
+ margin: 0.25em 0;
+ margin-right: 0.5em;
+
+ &::before {
+ display: block;
+ content: "";
+ position: absolute;
+ margin-top: -1px;
+ background: var(--tertiary);
+ height: 2px;
+ top: 50%;
+ width: 100%;
+ }
+}
+
+.group-timezones-slider {
+ position: relative;
+ z-index: 1; // need a positive z-index to appear above the ::before
+ display: flex;
+ width: 120px;
+ padding: 0.25em;
+ appearance: none;
+ cursor: pointer;
+ font: inherit;
+ outline: none;
+ box-sizing: border-box;
+ background-color: transparent;
+}
+
+.group-timezones-body {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
+ grid-template-rows: auto;
+ grid-gap: 0.25em;
+ box-sizing: border-box;
+ overflow-y: auto;
+
+ .group-timezones-header {
+ .title {
+ font-weight: 700;
+ }
+ }
+
+ .group-timezone,
+ .group-timezone-new-day {
+ box-sizing: border-box;
+ display: flex;
+ flex-direction: column;
+ padding: 0.25em;
+ }
+
+ .group-timezone-new-day {
+ align-items: center;
+ justify-content: space-between;
+ color: inherit;
+ font-size: var(--font-down-1);
+
+ .before {
+ margin-right: auto;
+ text-transform: capitalize;
+ }
+
+ .after {
+ margin-left: auto;
+ text-transform: capitalize;
+ }
+ }
+
+ .group-timezone {
+ color: var(--primary);
+ background-color: var(--calendar-normal);
+ transition: opacity 0.4s;
+ opacity: 0.5;
+
+ &:first-child {
+ margin-left: 0;
+ }
+
+ &:last-child {
+ margin-right: 0;
+ }
+
+ &.close-to-working-hours {
+ background-color: var(--calendar-close-to-working-hours);
+ opacity: 0.7;
+ }
+
+ &.in-working-hours {
+ background-color: var(--calendar-in-working-hours);
+ opacity: 1;
+ }
+
+ &:hover {
+ opacity: 1;
+ }
+
+ .info {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+
+ .time {
+ font-weight: 700;
+ }
+
+ .offset {
+ font-size: var(--font-down-2);
+ margin-left: 0.5em;
+ }
+ }
+
+ .group-timezones-members {
+ padding: 0;
+ margin: 0.1em 0;
+
+ .group-timezones-member {
+ margin: 0.1em;
+ list-style: none;
+ display: inline-block;
+
+ &.on-holiday {
+ opacity: 0.7;
+ position: relative;
+ }
+
+ &.on-holiday::after {
+ content: "📅";
+ position: absolute;
+ bottom: -0.15em;
+ left: 1em;
+ font-size: var(--font-down-2);
+ }
+ }
+ }
+ }
+}
+
+.group-timezones-reset {
+ display: flex;
+ margin-left: 0.5em;
+}
+
+.group-timezones-filter[type="text"] {
+ margin: 0;
+ width: 120px;
+}
+
+.emoji.on-holiday {
+ width: 15px;
+ height: 15px;
+}
+
+#event-popover {
+ background-color: var(--tertiary-very-low);
+ z-index: z("modal", "tooltip");
+ box-shadow: var(--shadow-dropdown);
+ border-radius: 4px;
+ padding: 0.5em;
+ max-width: min(75vw, 400px);
+
+ [data-popper-arrow],
+ [data-popper-arrow]::before {
+ position: absolute;
+ width: 10px;
+ height: 10px;
+ background: inherit;
+ top: -2px;
+ }
+
+ [data-popper-arrow] {
+ visibility: hidden;
+ }
+
+ [data-popper-arrow]::before {
+ visibility: visible;
+ content: "";
+ transform: rotate(45deg);
+ }
+}
diff --git a/plugins/discourse-calendar/assets/stylesheets/common/discourse-post-event-bulk-invite-modal.scss b/plugins/discourse-calendar/assets/stylesheets/common/discourse-post-event-bulk-invite-modal.scss
new file mode 100644
index 00000000000..5f4f9f83ce2
--- /dev/null
+++ b/plugins/discourse-calendar/assets/stylesheets/common/discourse-post-event-bulk-invite-modal.scss
@@ -0,0 +1,52 @@
+.post-event-bulk-invite {
+ .bulk-event-help {
+ margin: 0 0 1em 0;
+ }
+
+ .bulk-invite-rows {
+ margin-bottom: 1em;
+
+ .group-selector {
+ margin: 0;
+ }
+ }
+
+ .bulk-invite-row {
+ display: flex;
+ padding: 0.25em 0;
+
+ .bulk-invite-attendance {
+ margin: 0 0.5em;
+
+ .select-kit-header {
+ height: 100%;
+ }
+ }
+
+ .remove-bulk-invite {
+ margin-left: auto;
+ }
+ }
+
+ .bulk-invites {
+ margin-bottom: 2em;
+
+ .bulk-invite-actions {
+ display: flex;
+
+ .add-bulk-invite {
+ margin-left: auto;
+ }
+ }
+ }
+
+ .csv-bulk-invites {
+ .bulk-invite-actions {
+ display: flex;
+
+ > :last-child {
+ margin-left: 0.5em;
+ }
+ }
+ }
+}
diff --git a/plugins/discourse-calendar/assets/stylesheets/common/discourse-post-event-core-ext.scss b/plugins/discourse-calendar/assets/stylesheets/common/discourse-post-event-core-ext.scss
new file mode 100644
index 00000000000..2dd77b8fb31
--- /dev/null
+++ b/plugins/discourse-calendar/assets/stylesheets/common/discourse-post-event-core-ext.scss
@@ -0,0 +1,45 @@
+.header-title {
+ @include ellipsis;
+
+ .topic-link {
+ display: inline;
+ }
+
+ .event-date {
+ font-size: var(--font-down-4);
+ color: var(--primary-medium);
+ font-weight: normal;
+ padding: 0.25em;
+ }
+}
+
+.main-link {
+ .event-date-container-wrapper {
+ // prevents new dot from breaking separately onto next line
+ white-space: nowrap;
+ }
+}
+
+.link-top-line,
+.header-title {
+ .event-date {
+ display: inline-flex;
+ align-items: center;
+ font-size: var(--font-down-2);
+ border: 1px solid var(--primary-medium);
+ background: none;
+ padding: 0 0.25em;
+ border-radius: 3px;
+ pointer-events: auto; // needed to show title attribute on hover
+ vertical-align: text-bottom;
+
+ .indicator {
+ display: flex;
+ width: 6px;
+ height: 6px;
+ border-radius: 3px;
+ background: var(--success);
+ margin-right: 0.25em;
+ }
+ }
+}
diff --git a/plugins/discourse-calendar/assets/stylesheets/common/discourse-post-event-invitees.scss b/plugins/discourse-calendar/assets/stylesheets/common/discourse-post-event-invitees.scss
new file mode 100644
index 00000000000..5c40f1372b3
--- /dev/null
+++ b/plugins/discourse-calendar/assets/stylesheets/common/discourse-post-event-invitees.scss
@@ -0,0 +1,90 @@
+.post-event-invitees-modal {
+ .modal-body {
+ padding: 0;
+ }
+
+ .modal-inner-container {
+ min-width: 350px;
+ }
+
+ .loading-container {
+ height: 40vh;
+ overflow-y: scroll;
+
+ .no-users {
+ text-align: center;
+ font-size: var(--font-up-1);
+ }
+ }
+
+ .invitees-type-filter {
+ margin-bottom: 9px;
+ display: flex;
+
+ .btn {
+ width: calc(100% / 3);
+ margin: 0;
+ border-radius: 0;
+ padding: 0.75em 0;
+ }
+ }
+
+ .filter {
+ width: calc(100% - 2em);
+ margin-bottom: 1em;
+ }
+
+ .invitees,
+ .possible-invitees {
+ display: flex;
+ margin: 0;
+ flex-direction: column;
+
+ .invitee {
+ list-style: none;
+ display: flex;
+ flex: 1;
+ padding: 0.5em;
+ justify-content: space-between;
+ align-items: center;
+ border-bottom: 1px solid var(--primary-low);
+
+ &:last-child {
+ border: none;
+ }
+
+ .user {
+ max-width: 175px;
+ display: flex;
+ align-items: center;
+ white-space: nowrap;
+
+ .username {
+ margin-left: 0.5em;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ color: var(--primary-high-or-secondary-low);
+ font-weight: bold;
+ }
+ }
+
+ .status {
+ margin-left: auto;
+ margin-right: 0.5em;
+
+ &.going {
+ color: var(--success);
+ }
+
+ &.not_going {
+ color: var(--danger);
+ }
+ }
+ }
+ }
+
+ .possible-invitees {
+ margin-top: 1em;
+ background-color: var(--primary-very-low);
+ }
+}
diff --git a/plugins/discourse-calendar/assets/stylesheets/common/discourse-post-event-preview.scss b/plugins/discourse-calendar/assets/stylesheets/common/discourse-post-event-preview.scss
new file mode 100644
index 00000000000..88c923d7c87
--- /dev/null
+++ b/plugins/discourse-calendar/assets/stylesheets/common/discourse-post-event-preview.scss
@@ -0,0 +1,25 @@
+.discourse-post-event-preview {
+ background: var(--secondary);
+ align-items: center;
+ flex-direction: column;
+ padding: 0.5em;
+ border: 1px solid var(--primary-low);
+ display: flex;
+ flex: 1 0 auto;
+
+ .event-preview-status {
+ margin: 0 0 0.5em 0;
+ }
+
+ .event-preview-dates {
+ font-weight: 700;
+ }
+
+ &.alert-error {
+ border-color: var(--danger-low-mid);
+ }
+}
+
+.discourse-post-event-preview + .discourse-post-event-preview {
+ margin-top: 1em;
+}
diff --git a/plugins/discourse-calendar/assets/stylesheets/common/discourse-post-event-upcoming-events.scss b/plugins/discourse-calendar/assets/stylesheets/common/discourse-post-event-upcoming-events.scss
new file mode 100644
index 00000000000..2d4836cabcf
--- /dev/null
+++ b/plugins/discourse-calendar/assets/stylesheets/common/discourse-post-event-upcoming-events.scss
@@ -0,0 +1,19 @@
+.discourse-post-event-upcoming-events {
+ height: 100%;
+
+ .upcoming-events-table {
+ width: 100%;
+
+ thead {
+ tr th {
+ text-align: left;
+ }
+ }
+
+ tbody {
+ tr td {
+ padding: 0.5em;
+ }
+ }
+ }
+}
diff --git a/plugins/discourse-calendar/assets/stylesheets/common/discourse-post-event.scss b/plugins/discourse-calendar/assets/stylesheets/common/discourse-post-event.scss
new file mode 100644
index 00000000000..710c8c493f6
--- /dev/null
+++ b/plugins/discourse-calendar/assets/stylesheets/common/discourse-post-event.scss
@@ -0,0 +1,448 @@
+$interested: #fb985d;
+$show-interested: inherit;
+
+.cooked > .discourse-post-event {
+ display: none;
+}
+
+.discourse-post-event {
+ display: flex;
+ justify-content: center;
+
+ .event__section {
+ padding: 0.5em 0.75rem;
+
+ &:first-child {
+ border-top: 1px solid var(--primary-low);
+ }
+
+ p {
+ margin: 0;
+ }
+
+ > .d-icon {
+ padding-right: 0.25em;
+ }
+
+ .discourse-local-date .d-icon {
+ padding-right: 0.25em;
+ }
+ }
+
+ .discourse-post-event-widget {
+ box-shadow: 0 0 0 3px var(--primary-100);
+ border: 1px solid var(--primary-300);
+ display: flex;
+ background: var(--secondary);
+ margin: 5px 0;
+ flex-direction: column;
+ flex: 1 0 auto;
+ max-width: calc(100% - 2px - 6px);
+ box-sizing: border-box;
+ border-radius: var(--d-border-radius);
+
+ section:last-child {
+ padding-bottom: 0.75em;
+ }
+
+ .widget-dropdown {
+ margin: 0;
+
+ .widget-dropdown-header.disabled {
+ pointer-events: none;
+ }
+
+ .widget-dropdown-item {
+ &:not(.separator) {
+ padding: 0.5em;
+ }
+
+ .d-icon + span {
+ margin-left: 0.5em;
+ }
+ }
+ }
+ }
+
+ &.is-loading {
+ align-items: center;
+ justify-content: center;
+ }
+
+ &.has-event {
+ display: flex;
+ flex-direction: column;
+ }
+
+ .event-header {
+ column-gap: 1em;
+ display: flex;
+ align-items: flex-start;
+ padding: 0.75em;
+
+ .more-dropdown {
+ margin-left: auto;
+ align-self: flex-start;
+
+ &.has-no-actions {
+ display: none;
+ }
+
+ .widget-dropdown {
+ .widget-dropdown-header {
+ .d-icon {
+ margin: 0;
+ }
+
+ .label {
+ display: none;
+ }
+ }
+
+ .item-closeEvent {
+ .d-icon,
+ span {
+ color: var(--danger);
+ }
+ }
+ }
+ }
+ }
+
+ .event-date {
+ display: flex;
+ flex-shrink: 0;
+ align-items: center;
+ justify-content: center;
+ width: 3em;
+ height: 3em;
+ margin: 0;
+ padding: 0;
+ border: 1px solid var(--primary-low);
+ border-radius: var(--d-border-radius);
+ flex-direction: column;
+
+ .month {
+ text-align: center;
+ color: red;
+ font-size: var(--font-down-2);
+ font-weight: 400;
+ text-transform: uppercase;
+ }
+
+ .day {
+ text-align: center;
+ font-size: var(--font-up-1);
+ font-weight: 400;
+ }
+ }
+
+ .event-info {
+ display: flex;
+ flex-direction: column;
+ min-width: 0;
+ margin-right: 0.5rem;
+
+ .name {
+ @include ellipsis;
+ max-width: 45vw;
+ font-size: var(--font-up-2);
+ font-weight: 400;
+ }
+
+ .status-and-creators {
+ display: flex;
+ align-items: center;
+ color: var(--primary-medium);
+ font-size: var(--font-down-1);
+ font-weight: 400;
+ margin: 0;
+
+ .separator {
+ margin: 0 0.25em;
+ }
+
+ .created-by {
+ margin-right: 0.25em;
+
+ @media screen and (width <= 450px) {
+ display: none;
+ }
+ }
+
+ .username {
+ margin-left: 0.25em;
+ color: var(--primary);
+ }
+
+ .creators {
+ display: flex;
+ align-items: center;
+
+ .event-creator {
+ .topic-invitee-avatar {
+ display: flex;
+ align-items: center;
+ }
+ }
+ }
+
+ .status {
+ &.expired,
+ &.closed {
+ color: var(--danger-medium);
+ }
+
+ .d-icon {
+ margin-right: 0.5em;
+ }
+ }
+ }
+ }
+
+ .event-actions {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 0.75em;
+ flex-wrap: wrap;
+ border-top: 1px solid var(--primary-low);
+
+ &.event-status {
+ margin: 0;
+ gap: 0.75em;
+
+ .btn {
+ flex: 1;
+ }
+
+ .interested-button {
+ display: $show-interested;
+ }
+
+ &.status-going .going-button .d-icon {
+ color: var(--success);
+ }
+
+ &.status-interested .interested-button .d-icon {
+ color: $interested;
+ }
+
+ &.status-not_going .not-going-button .d-icon {
+ color: var(--danger);
+ }
+
+ .not-going-button span {
+ white-space: nowrap;
+ }
+ }
+ }
+
+ .event-creator {
+ .username {
+ margin-left: 0.25em;
+ }
+ }
+
+ .event-invitees {
+ min-height: 1.75em;
+ display: flex;
+ align-items: flex-start;
+ justify-content: center;
+ overflow-y: auto;
+ flex-direction: column;
+
+ .header {
+ display: none;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 1em;
+ width: 100%;
+
+ .show-all {
+ margin-left: 0.5em;
+ }
+
+ .event-invitees-status {
+ font-weight: 700;
+ display: flex;
+
+ span:not(:last-child)::after {
+ content: "-";
+ font-weight: 400;
+ margin: 0 0.3em;
+ }
+
+ .event-status-invited {
+ font-weight: 600;
+ color: var(--primary-medium);
+ }
+ }
+ }
+
+ .event-invitees-avatars {
+ padding: 0;
+ margin: 0;
+ gap: 0.25em;
+ display: flex;
+ flex-wrap: wrap;
+ width: 100%;
+
+ .event-invitee {
+ list-style: none;
+ opacity: 0.25;
+
+ &.status-going,
+ &.status-not_going,
+ &.status-interested {
+ opacity: 1;
+ }
+
+ &.status-interested {
+ display: $show-interested;
+ }
+
+ &.status-going .avatar-flair .d-icon {
+ color: var(--success);
+ }
+
+ &.status-not_going .avatar-flair .d-icon {
+ color: var(--danger);
+ }
+
+ &.status-interested .avatar-flair .d-icon {
+ color: $interested;
+ }
+ }
+
+ .topic-invitee-avatar {
+ position: relative;
+ display: inline-block;
+ padding-right: 0.5em;
+
+ .avatar {
+ width: 1.5rem;
+ }
+
+ .avatar-flair {
+ position: absolute;
+ right: 0;
+ bottom: 0;
+ background: var(--secondary);
+ border-radius: 50%;
+ height: 1rem;
+ width: 1rem;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: var(--primary-medium);
+ box-shadow: 0 0 0 1px var(--primary-100);
+
+ .d-icon {
+ font-size: var(--font-down-2);
+ }
+ }
+ }
+ }
+ }
+
+ hr {
+ margin: 0;
+ }
+
+ .event-description {
+ display: flex;
+ }
+
+ .event-location,
+ .event-url,
+ .event-dates,
+ .event-chat-channel,
+ .event-invitees-avatars-container {
+ display: grid;
+ grid-template-columns: 3em 1fr;
+ grid-column-gap: 1em;
+ align-items: center;
+ height: min-content;
+
+ > .d-icon {
+ color: var(--primary-high);
+ margin: 0 auto;
+ padding: 0;
+ }
+ }
+
+ .event-url {
+ .url {
+ max-width: 80%;
+
+ @include ellipsis;
+ }
+ }
+
+ .cooked-date,
+ .participants,
+ .event-url .url {
+ font-size: var(--base-font-size);
+ font-weight: 400;
+ border-bottom: none;
+ margin: 0;
+ }
+
+ .event__section.no-rsvp {
+ display: grid;
+ grid-template-columns: 3em 1fr;
+ grid-column-gap: 1em;
+ align-items: center;
+ }
+
+ p.no-rsvp-description {
+ color: var(--primary-medium);
+ font-size: var(--font-down-1);
+ font-weight: 400;
+ grid-column-start: 2;
+ }
+
+ .event-invitees-icon {
+ position: relative;
+ display: flex;
+ margin: 0 auto;
+ color: var(--primary-high);
+ }
+
+ .event-invitees-icon .going {
+ font-size: var(--font-down-3);
+ position: absolute;
+ right: -2px;
+ bottom: -8px;
+ background: var(--secondary);
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: var(--primary-medium);
+ }
+}
+
+.event-dates {
+ // hardcoded as cooking date is async and will change height after initial rendering otherwise
+ // not ideal but a decent low tech solution
+ height: 24px;
+
+ .participants {
+ margin-left: 0.5em;
+ color: var(--primary-medium);
+ }
+
+ .discourse-local-date {
+ .d-icon {
+ display: none;
+ }
+ }
+
+ .separator {
+ color: var(--primary-high);
+ margin: 0 0.5em;
+ text-align: center;
+ }
+}
diff --git a/plugins/discourse-calendar/assets/stylesheets/common/post-event-builder.scss b/plugins/discourse-calendar/assets/stylesheets/common/post-event-builder.scss
new file mode 100644
index 00000000000..26ed860ae3f
--- /dev/null
+++ b/plugins/discourse-calendar/assets/stylesheets/common/post-event-builder.scss
@@ -0,0 +1,246 @@
+.mobile-view {
+ .post-event-builder-modal {
+ .modal-inner-container {
+ .modal-body {
+ .d-date-time-input-range {
+ flex-direction: column;
+ width: 100%;
+ border: 0;
+
+ .d-date-time-input {
+ .d-date-input {
+ width: 100%;
+ }
+
+ .name {
+ font-size: var(--font-down-1);
+ }
+
+ &.from {
+ margin-right: 2.65em;
+ }
+
+ &.to {
+ margin-top: 0.5em;
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+.post-event-builder-modal {
+ .modal-inner-container {
+ width: 550px;
+ }
+
+ .conditional-loading-section {
+ background: transparent;
+ }
+
+ .modal-body {
+ min-height: 200px;
+
+ .d-date-time-input-range {
+ margin-bottom: 2em;
+ flex-direction: column;
+
+ .d-date-time-input {
+ display: flex;
+ justify-content: flex-start;
+ }
+
+ .select-kit-header {
+ height: 100%;
+ }
+
+ .d-date-input {
+ box-sizing: border-box;
+ flex: 0;
+ min-width: unset;
+
+ .date-picker {
+ width: 155px;
+ }
+ }
+
+ .d-time-input {
+ .combo-box {
+ width: 130px;
+ }
+
+ .selected-name {
+ border: 0;
+
+ .name {
+ font-size: var(--font-down-1);
+ }
+ }
+ }
+
+ .to.d-date-time-input {
+ .d-time-input {
+ order: 1;
+ }
+
+ .d-date-input {
+ order: 0;
+ }
+
+ .clear-date-time {
+ order: 2;
+ margin-left: auto;
+ }
+
+ .pika-single.is-bound {
+ left: -2px !important;
+ }
+ }
+ }
+ }
+
+ .modal-footer {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ }
+
+ .event-field {
+ display: flex;
+ margin-bottom: 2em;
+ flex-direction: column;
+
+ &.description {
+ textarea {
+ border-radius: var(--d-input-border-radius);
+ margin: 0;
+ }
+ }
+
+ &.name,
+ &.url {
+ input {
+ width: 100%;
+ }
+ }
+
+ .event-field-description {
+ margin: 0 0 0.5em 0;
+ }
+
+ .event-field-label {
+ display: flex;
+ min-height: 1px;
+ padding-top: 0;
+ top: 0;
+ vertical-align: middle;
+ align-items: center;
+
+ .label {
+ font-weight: 700;
+ margin-bottom: 0.5em;
+ }
+ }
+
+ .event-field-control {
+ display: flex;
+ flex: 1;
+ flex-direction: column;
+
+ .select-kit.available-recurrences {
+ width: 100%;
+ }
+
+ .custom-field-label {
+ font-weight: 500;
+ margin: 0.5em 0 0.25em 0;
+ }
+
+ .custom-field-input {
+ width: 100%;
+ }
+
+ .radio-label,
+ .checkbox-label {
+ display: flex;
+ align-items: center;
+ margin-bottom: 1em;
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+
+ input[type="radio"] {
+ width: auto;
+ }
+
+ .message {
+ margin: 0 0 0 1em;
+ padding: 0;
+ display: flex;
+ flex-direction: column;
+
+ .description {
+ font-weight: normal;
+ }
+ }
+ }
+
+ .ac-wrap {
+ max-width: 100%;
+ }
+
+ input {
+ margin: 0;
+ }
+ }
+ }
+
+ .event-field.reminders {
+ display: flex;
+
+ .reminders-list {
+ display: flex;
+ flex-direction: column;
+ margin-bottom: 1em;
+
+ .reminder-item {
+ display: flex;
+ flex: 1 0 auto;
+ padding: 0.25em 0;
+
+ .select-kit-header {
+ height: 100%;
+ }
+
+ .reminder-type {
+ width: 320px;
+ margin-right: 0.5em;
+ }
+
+ .reminder-value {
+ width: 60px;
+ margin-right: 0.5em;
+ }
+
+ .reminder-unit {
+ width: 140px;
+ margin-right: 0.5em;
+ }
+
+ .reminder-period {
+ margin-right: 0.5em;
+ }
+
+ .remove-reminder {
+ margin-left: auto;
+ }
+ }
+ }
+
+ .add-reminder {
+ align-self: flex-start;
+ }
+ }
+}
diff --git a/plugins/discourse-calendar/assets/stylesheets/common/upcoming-events-calendar.scss b/plugins/discourse-calendar/assets/stylesheets/common/upcoming-events-calendar.scss
new file mode 100644
index 00000000000..2cb2b9dda54
--- /dev/null
+++ b/plugins/discourse-calendar/assets/stylesheets/common/upcoming-events-calendar.scss
@@ -0,0 +1,133 @@
+#upcoming-events-calendar,
+#category-events-calendar {
+ &.fc-unthemed {
+ tbody,
+ thead,
+ tr {
+ border: none;
+ }
+
+ .fc-basic-view .fc-day-top .fc-day-number {
+ float: left;
+ }
+
+ .fc-bg td.fc-today {
+ background-color: var(--highlight-medium);
+ border-style: solid;
+ }
+
+ .fc-month-view .fc-widget-content,
+ .fc-basicWeek-view .fc-widget-content,
+ .fc-head-container {
+ padding: 0;
+ }
+
+ .fc-bg tbody {
+ border-width: 0;
+ }
+
+ .fc-header-toolbar {
+ margin: 1em 0 0.5em 0;
+ }
+
+ .fc-title {
+ @include ellipsis;
+ display: block;
+ }
+
+ .fc-widget-header span {
+ padding: 3px 3px 3px 0.5em;
+ }
+
+ .fc-center {
+ display: none;
+ }
+
+ .fc-button {
+ border-radius: 0;
+ box-shadow: none;
+ background: var(--primary-low);
+ text-transform: capitalize;
+ color: var(--primary);
+ text-shadow: none;
+ border: none;
+ padding: 6px 12px;
+
+ &:hover {
+ background: var(--primary-medium);
+ color: var(--secondary);
+ }
+
+ &.fc-state-active {
+ background: var(--tertiary);
+ color: var(--secondary);
+ }
+ margin: 0.3em 0 0.3em 0.5em;
+ }
+
+ .fc-button-group {
+ margin: 0.3em 0 0.3em 0.5em;
+
+ // margin-right: 0;
+ .fc-button {
+ margin: 0;
+ }
+ }
+
+ .fc-divider,
+ .fc-list-empty,
+ .fc-list-heading td,
+ .fc-popover .fc-header {
+ background: var(--primary-low);
+ }
+
+ .fc-content,
+ .fc-divider,
+ .fc-list-heading td,
+ .fc-list-view,
+ .fc-popover,
+ .fc-row,
+ tbody,
+ td,
+ th,
+ thead {
+ border-color: var(--primary-low);
+ }
+ }
+
+ .fc-event,
+ .fc-event-dot {
+ color: var(--secondary);
+ background-color: var(--tertiary);
+ border: 1px solid transparent;
+
+ .fc-time {
+ display: none;
+ }
+
+ &.grouped-event {
+ background: var(--secondary);
+ border: 1px solid var(--primary-low-mid);
+ color: var(--primary);
+
+ .emoji {
+ margin-right: 0.25em;
+ }
+ }
+ }
+
+ .fc-past-event {
+ opacity: 0.3;
+ }
+
+ .fc-left {
+ .fc-button-group:first-child {
+ margin-left: 0;
+ }
+ }
+
+ .fc-list-item-add-to-calendar {
+ color: var(--tertiary);
+ font-size: var(--font-down-1);
+ }
+}
diff --git a/plugins/discourse-calendar/assets/stylesheets/common/upcoming-events-list.scss b/plugins/discourse-calendar/assets/stylesheets/common/upcoming-events-list.scss
new file mode 100644
index 00000000000..ec8e62fa43b
--- /dev/null
+++ b/plugins/discourse-calendar/assets/stylesheets/common/upcoming-events-list.scss
@@ -0,0 +1,79 @@
+.upcoming-events-list {
+ &__event {
+ column-gap: 0.5em;
+ display: flex;
+ padding: 0.5em;
+ border-radius: var(--d-border-radius);
+ }
+
+ &__event:last-of-type {
+ margin-bottom: 0.75em;
+ }
+
+ &__event:hover {
+ background-color: var(--primary-50);
+ }
+
+ &__event-date {
+ display: flex;
+ flex-shrink: 0;
+ align-items: center;
+ justify-content: center;
+ width: 2.5em;
+ height: 2.5em;
+ margin: 0;
+ padding: 0;
+ border: 1px solid var(--primary-low);
+ border-radius: var(--d-button-border-radius);
+ flex-direction: column;
+ background-color: var(--secondary);
+ }
+
+ &__event-date .month {
+ text-align: center;
+ color: var(--primary);
+ font-size: var(--font-down-3);
+ text-transform: uppercase;
+ }
+
+ &__event-date .day {
+ text-align: center;
+ color: var(--primary);
+ font-weight: bold;
+ font-size: var(--font-down-1);
+ }
+
+ &__event-content {
+ display: flex;
+ flex-direction: column;
+ }
+
+ &__event-name {
+ display: -webkit-box;
+ -webkit-line-clamp: 1;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+ color: var(--primary);
+ }
+
+ &__event-time {
+ font-size: var(--font-down-2);
+ font-weight: 400;
+ color: var(--primary-700);
+ }
+
+ &__footer {
+ margin-top: 1em;
+ font-size: var(--font-down-2);
+ line-height: var(--line-height-medium);
+ padding-left: 0.5em;
+ }
+
+ &__footer a {
+ color: var(--primary-high);
+ }
+
+ &__footer a:hover {
+ color: var(--primary);
+ }
+}
diff --git a/plugins/discourse-calendar/assets/stylesheets/common/user-preferences.scss b/plugins/discourse-calendar/assets/stylesheets/common/user-preferences.scss
new file mode 100644
index 00000000000..5d70c4e9147
--- /dev/null
+++ b/plugins/discourse-calendar/assets/stylesheets/common/user-preferences.scss
@@ -0,0 +1,5 @@
+.user-preferences {
+ .region details {
+ min-width: 175px;
+ }
+}
diff --git a/plugins/discourse-calendar/assets/stylesheets/desktop/discourse-calendar.scss b/plugins/discourse-calendar/assets/stylesheets/desktop/discourse-calendar.scss
new file mode 100644
index 00000000000..61ef32b2a10
--- /dev/null
+++ b/plugins/discourse-calendar/assets/stylesheets/desktop/discourse-calendar.scss
@@ -0,0 +1,14 @@
+.calendar.fc {
+ table {
+ width: 100%;
+ }
+
+ .fc-list-item-add-to-calendar {
+ float: right;
+ margin-right: 5px;
+ }
+
+ .fc-list-item:hover td {
+ background: var(--highlight-medium);
+ }
+}
diff --git a/plugins/discourse-calendar/assets/stylesheets/desktop/discourse-post-event-invitees.scss b/plugins/discourse-calendar/assets/stylesheets/desktop/discourse-post-event-invitees.scss
new file mode 100644
index 00000000000..1769b13ceb8
--- /dev/null
+++ b/plugins/discourse-calendar/assets/stylesheets/desktop/discourse-post-event-invitees.scss
@@ -0,0 +1,5 @@
+.post-event-invitees-modal {
+ .modal-inner-container {
+ min-width: 350px;
+ }
+}
diff --git a/plugins/discourse-calendar/assets/stylesheets/mobile/discourse-calendar.scss b/plugins/discourse-calendar/assets/stylesheets/mobile/discourse-calendar.scss
new file mode 100644
index 00000000000..8fafaad8847
--- /dev/null
+++ b/plugins/discourse-calendar/assets/stylesheets/mobile/discourse-calendar.scss
@@ -0,0 +1,46 @@
+.discourse-calendar-wrap {
+ border: 0;
+
+ .discourse-calendar-header {
+ padding: 0;
+ background: none;
+
+ h2.discourse-calendar-title {
+ font-size: var(--font-0);
+ flex-wrap: nowrap;
+ max-width: 75%;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ }
+
+ .discourse-calendar-timezone-picker {
+ max-width: 40vw;
+ }
+ }
+
+ .fc-view-container {
+ .fc-day-header.fc-widget-header {
+ text-align: center;
+ padding: 0.25em;
+
+ span {
+ font-size: var(--font-down-1);
+ text-align: center;
+ padding: 0;
+ }
+ }
+ }
+
+ .calendar {
+ padding: 0;
+
+ .fc-list-item-add-to-calendar {
+ display: block;
+ }
+
+ &.fc-unthemed .fc-header-toolbar {
+ padding: 0.5em 0;
+ }
+ }
+}
diff --git a/plugins/discourse-calendar/assets/stylesheets/mobile/discourse-post-event-core-ext.scss b/plugins/discourse-calendar/assets/stylesheets/mobile/discourse-post-event-core-ext.scss
new file mode 100644
index 00000000000..ac3ebfabd22
--- /dev/null
+++ b/plugins/discourse-calendar/assets/stylesheets/mobile/discourse-post-event-core-ext.scss
@@ -0,0 +1,16 @@
+.link-top-line {
+ .event-date-container {
+ display: block;
+ margin-top: 0.25em;
+
+ .event-date {
+ margin: 0;
+ }
+ }
+}
+
+.header-title {
+ .event-date {
+ display: none;
+ }
+}
diff --git a/plugins/discourse-calendar/assets/stylesheets/mobile/discourse-post-event-invitees.scss b/plugins/discourse-calendar/assets/stylesheets/mobile/discourse-post-event-invitees.scss
new file mode 100644
index 00000000000..9995538446e
--- /dev/null
+++ b/plugins/discourse-calendar/assets/stylesheets/mobile/discourse-post-event-invitees.scss
@@ -0,0 +1,5 @@
+.post-event-invitees-modal {
+ .modal-inner-container {
+ min-width: 90vw;
+ }
+}
diff --git a/plugins/discourse-calendar/assets/stylesheets/mobile/discourse-post-event.scss b/plugins/discourse-calendar/assets/stylesheets/mobile/discourse-post-event.scss
new file mode 100644
index 00000000000..c5fdd2b985d
--- /dev/null
+++ b/plugins/discourse-calendar/assets/stylesheets/mobile/discourse-post-event.scss
@@ -0,0 +1,34 @@
+.discourse-post-event {
+ .discourse-post-event-widget {
+ border-width: 1px;
+ }
+
+ .event-dates {
+ .date {
+ max-width: 75vw;
+ }
+ }
+
+ .event-actions {
+ .event-status {
+ width: 100%;
+ display: flex;
+ justify-content: space-between;
+
+ button {
+ padding: 0 0.6em;
+ height: 2.5em;
+ }
+ }
+ }
+
+ .event-invitees-status {
+ font-size: var(--font-down-1);
+ }
+
+ .creators {
+ .created-by {
+ display: none;
+ }
+ }
+}
diff --git a/plugins/discourse-calendar/assets/stylesheets/vendor/fullcalendar.min.css b/plugins/discourse-calendar/assets/stylesheets/vendor/fullcalendar.min.css
new file mode 100644
index 00000000000..bb5b20655d3
--- /dev/null
+++ b/plugins/discourse-calendar/assets/stylesheets/vendor/fullcalendar.min.css
@@ -0,0 +1,5 @@
+/*!
+ * FullCalendar v4.0.0-alpha.3
+ * Docs & License: https://fullcalendar.io/
+ * (c) 2018 Adam Shaw
+ */.fc button,.fc table,body .fc{font-size:1em}.fc-bg,.fc-row .fc-bgevent-skeleton,.fc-row .fc-highlight-skeleton{bottom:0}.fc-icon,.fc-unselectable{-webkit-touch-callout:none;-khtml-user-select:none}.fc{direction:ltr;text-align:left}.fc-rtl{text-align:right}.fc th,.fc-basic-view td.fc-week-number,.fc-icon,.fc-toolbar{text-align:center}.fc-highlight{background:#bce8f1;opacity:.3}.fc-bgevent{background:#8fdf82;opacity:.3}.fc-nonbusiness{background:#d7d7d7}.fc button{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;height:2.1em;padding:0 .6em;white-space:nowrap;cursor:pointer}.fc button::-moz-focus-inner{margin:0;padding:0}.fc-state-default{border:1px solid;background-color:#f5f5f5;background-image:-moz-linear-gradient(top,#fff,#e6e6e6);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#e6e6e6));background-image:-webkit-linear-gradient(top,#fff,#e6e6e6);background-image:-o-linear-gradient(top,#fff,#e6e6e6);background-image:linear-gradient(to bottom,#fff,#e6e6e6);background-repeat:repeat-x;border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-color:rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);color:#333;text-shadow:0 1px 1px rgba(255,255,255,.75);box-shadow:inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05)}.fc-state-default.fc-corner-left{border-top-left-radius:4px;border-bottom-left-radius:4px}.fc-state-default.fc-corner-right{border-top-right-radius:4px;border-bottom-right-radius:4px}.fc button .fc-icon{position:relative;top:-.05em;margin:0 .2em;vertical-align:middle}.fc-state-active,.fc-state-disabled,.fc-state-down,.fc-state-hover{color:#333;background-color:#e6e6e6}.fc-state-hover{color:#333;text-decoration:none;background-position:0 -15px;-webkit-transition:background-position .1s linear;-moz-transition:background-position .1s linear;-o-transition:background-position .1s linear;transition:background-position .1s linear}.fc-state-active,.fc-state-down{background-color:#ccc;background-image:none;box-shadow:inset 0 2px 4px rgba(0,0,0,.15),0 1px 2px rgba(0,0,0,.05)}.fc-state-disabled{cursor:default;background-image:none;opacity:.65;box-shadow:none}.fc-event.fc-draggable,.fc-event[href],.fc-popover .fc-header .fc-close,a[data-goto]{cursor:pointer}.fc-button-group{display:inline-block}.fc .fc-button-group>*{float:left;margin:0 0 0 -1px}.fc .fc-button-group>:first-child{margin-left:0}.fc-popover{position:absolute;box-shadow:0 2px 6px rgba(0,0,0,.15)}.fc-popover .fc-header{padding:2px 4px}.fc-popover .fc-header .fc-title{margin:0 2px}.fc-ltr .fc-popover .fc-header .fc-title,.fc-rtl .fc-popover .fc-header .fc-close{float:left}.fc-ltr .fc-popover .fc-header .fc-close,.fc-rtl .fc-popover .fc-header .fc-title{float:right}.fc-divider{border-style:solid;border-width:1px}hr.fc-divider{height:0;margin:0;padding:0 0 2px;border-width:1px 0}.fc-bg table,.fc-row .fc-bgevent-skeleton table,.fc-row .fc-highlight-skeleton table{height:100%}.fc-clear{clear:both}.fc-bg,.fc-bgevent-skeleton,.fc-highlight-skeleton,.fc-mirror-skeleton{position:absolute;top:0;left:0;right:0}.fc table{width:100%;box-sizing:border-box;table-layout:fixed;border-collapse:collapse;border-spacing:0}.fc td,.fc th{border-style:solid;border-width:1px;padding:0;vertical-align:top}.fc td.fc-today{border-style:double}a[data-goto]:hover{text-decoration:underline}.fc .fc-row{border-style:solid;border-width:0}.fc-row table{border-left:0 hidden transparent;border-right:0 hidden transparent;border-bottom:0 hidden transparent}.fc-row:first-child table{border-top:0 hidden transparent}.fc-row{position:relative}.fc-row .fc-bg{z-index:1}.fc-row .fc-bgevent-skeleton td,.fc-row .fc-highlight-skeleton td{border-color:transparent}.fc-row .fc-bgevent-skeleton{z-index:2}.fc-row .fc-highlight-skeleton{z-index:3}.fc-row .fc-content-skeleton{position:relative;z-index:4;padding-bottom:2px}.fc-row .fc-mirror-skeleton{z-index:5}.fc .fc-row .fc-content-skeleton table,.fc .fc-row .fc-content-skeleton td,.fc .fc-row .fc-mirror-skeleton td{background:0 0;border-color:transparent}.fc-row .fc-content-skeleton td,.fc-row .fc-mirror-skeleton td{border-bottom:0}.fc-row .fc-content-skeleton tbody td,.fc-row .fc-mirror-skeleton tbody td{border-top:0}.fc-scroller{-webkit-overflow-scrolling:touch}.fc-icon,.fc-row.fc-rigid,.fc-time-grid-event{overflow:hidden}.fc-scroller>.fc-day-grid,.fc-scroller>.fc-time-grid{position:relative;width:100%}.fc-event{position:relative;display:block;font-size:.85em;line-height:1.3;border-radius:3px;border:1px solid #3a87ad}.fc-event,.fc-event-dot{background-color:#3a87ad}.fc-event,.fc-event:hover{color:#fff;text-decoration:none}.fc-not-allowed,.fc-not-allowed .fc-event{cursor:not-allowed}.fc-event .fc-bg{z-index:1;background:#fff;opacity:.25}.fc-event .fc-content{position:relative;z-index:2}.fc-event .fc-resizer{position:absolute;z-index:4;display:none}.fc-event.fc-allow-mouse-resize .fc-resizer,.fc-event.fc-selected .fc-resizer{display:block}.fc-event.fc-selected .fc-resizer:before{content:"";position:absolute;z-index:9999;top:50%;left:50%;width:40px;height:40px;margin-left:-20px;margin-top:-20px}.fc-event.fc-selected{z-index:9999!important;box-shadow:0 2px 5px rgba(0,0,0,.2)}.fc-event.fc-dragging.fc-selected{box-shadow:0 2px 7px rgba(0,0,0,.3)}.fc-event.fc-dragging:not(.fc-selected){opacity:.75}.fc-h-event.fc-selected:before{content:"";position:absolute;z-index:3;top:-10px;bottom:-10px;left:0;right:0}.fc-ltr .fc-h-event.fc-not-start,.fc-rtl .fc-h-event.fc-not-end{margin-left:0;border-left-width:0;padding-left:1px;border-top-left-radius:0;border-bottom-left-radius:0}.fc-ltr .fc-h-event.fc-not-end,.fc-rtl .fc-h-event.fc-not-start{margin-right:0;border-right-width:0;padding-right:1px;border-top-right-radius:0;border-bottom-right-radius:0}.fc-ltr .fc-h-event .fc-start-resizer,.fc-rtl .fc-h-event .fc-end-resizer{cursor:w-resize;left:-1px}.fc-ltr .fc-h-event .fc-end-resizer,.fc-rtl .fc-h-event .fc-start-resizer{cursor:e-resize;right:-1px}.fc-h-event.fc-allow-mouse-resize .fc-resizer{width:7px;top:-1px;bottom:-1px}.fc-h-event.fc-selected .fc-resizer{border-radius:4px;border-width:1px;width:6px;height:6px;border-style:solid;border-color:inherit;background:#fff;top:50%;margin-top:-4px}.fc-ltr .fc-h-event.fc-selected .fc-start-resizer,.fc-rtl .fc-h-event.fc-selected .fc-end-resizer{margin-left:-4px}.fc-ltr .fc-h-event.fc-selected .fc-end-resizer,.fc-rtl .fc-h-event.fc-selected .fc-start-resizer{margin-right:-4px}.fc-day-grid-event{margin:1px 2px 0;padding:0 1px}tr:first-child>td>.fc-day-grid-event{margin-top:2px}.fc-mirror-skeleton tr:first-child>td>.fc-day-grid-event{margin-top:0}.fc-day-grid-event.fc-selected:after{content:"";position:absolute;z-index:1;top:-1px;right:-1px;bottom:-1px;left:-1px;background:#000;opacity:.25}.fc-day-grid-event .fc-content{white-space:nowrap;overflow:hidden}.fc-day-grid-event .fc-time{font-weight:700}.fc-ltr .fc-day-grid-event.fc-allow-mouse-resize .fc-start-resizer,.fc-rtl .fc-day-grid-event.fc-allow-mouse-resize .fc-end-resizer{margin-left:-2px}.fc-ltr .fc-day-grid-event.fc-allow-mouse-resize .fc-end-resizer,.fc-rtl .fc-day-grid-event.fc-allow-mouse-resize .fc-start-resizer{margin-right:-2px}a.fc-more{margin:1px 3px;font-size:.85em;cursor:pointer;text-decoration:none}a.fc-more:hover{text-decoration:underline}.fc.fc-bootstrap3 a,.ui-widget .fc-event{text-decoration:none}.fc-limited{display:none}.fc-icon,.fc-toolbar .fc-center{display:inline-block}.fc-day-grid .fc-row{z-index:1}.fc-more-popover{z-index:2;width:220px}.fc-more-popover .fc-event-container{padding:10px}.fc-bootstrap3 .fc-popover .panel-body,.fc-bootstrap4 .fc-popover .card-body{padding:0}.fc-now-indicator{position:absolute;border:0 solid red}.fc-bootstrap3 .fc-today.alert,.fc-bootstrap4 .fc-today.alert{border-radius:0}.fc-unselectable{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent}.fc-unthemed .fc-content,.fc-unthemed .fc-divider,.fc-unthemed .fc-list-heading td,.fc-unthemed .fc-list-view,.fc-unthemed .fc-popover,.fc-unthemed .fc-row,.fc-unthemed tbody,.fc-unthemed td,.fc-unthemed th,.fc-unthemed thead{border-color:#ddd}.fc-unthemed .fc-popover{background-color:#fff;border-width:1px;border-style:solid}.fc-unthemed .fc-divider,.fc-unthemed .fc-list-heading td,.fc-unthemed .fc-popover .fc-header{background:#eee}.fc-unthemed td.fc-today{background:#fcf8e3}.fc-unthemed .fc-disabled-day{background:#d7d7d7;opacity:.3}.fc-icon{height:1em;line-height:1em;font-size:1em;font-family:"Courier New",Courier,monospace;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.fc-icon:after{position:relative}.fc-icon-left-single-arrow:after{content:"\2039";font-weight:700;font-size:200%;top:-7%}.fc-icon-right-single-arrow:after{content:"\203A";font-weight:700;font-size:200%;top:-7%}.fc-icon-left-double-arrow:after{content:"\AB";font-size:160%;top:-7%}.fc-icon-right-double-arrow:after{content:"\BB";font-size:160%;top:-7%}.fc-icon-left-triangle:after{content:"\25C4";font-size:125%;top:3%}.fc-icon-right-triangle:after{content:"\25BA";font-size:125%;top:3%}.fc-icon-down-triangle:after{content:"\25BC";font-size:125%;top:2%}.fc-icon-x:after{content:"\D7";font-size:200%;top:6%}.fc-unthemed .fc-popover .fc-header .fc-close{color:#666;font-size:.9em;margin-top:2px}.fc-unthemed .fc-list-item:hover td{background-color:#f5f5f5}.ui-widget .fc-disabled-day{background-image:none}.fc-bootstrap3 .fc-time-grid .fc-slats table,.fc-bootstrap4 .fc-time-grid .fc-slats table,.fc-time-grid .fc-slats .ui-widget-content{background:0 0}.fc-popover>.ui-widget-header+.ui-widget-content{border-top:0}.fc-bootstrap3 hr.fc-divider,.fc-bootstrap4 hr.fc-divider{border-color:inherit}.ui-widget .fc-event{color:#fff;font-weight:400}.ui-widget td.fc-axis{font-weight:400}.fc.fc-bootstrap3 a[data-goto]:hover{text-decoration:underline}.fc.fc-bootstrap4 a{text-decoration:none}.fc.fc-bootstrap4 a[data-goto]:hover{text-decoration:underline}.fc-bootstrap4 a.fc-event:not([href]):not([tabindex]){color:#fff}.fc-bootstrap4 .fc-popover.card{position:absolute}.fc-toolbar button,.fc-view-container{position:relative}.fc-toolbar.fc-header-toolbar{margin-bottom:1em}.fc-toolbar.fc-footer-toolbar{margin-top:1em}.fc-toolbar .fc-left{float:left}.fc-toolbar .fc-right{float:right}.fc .fc-toolbar>*>*{float:left;margin-left:.75em}.fc .fc-toolbar>*>:first-child{margin-left:0}.fc-toolbar h2{margin:0}.fc-toolbar .fc-state-hover,.fc-toolbar .ui-state-hover{z-index:2}.fc-toolbar .fc-state-down{z-index:3}.fc-toolbar .fc-state-active,.fc-toolbar .ui-state-active{z-index:4}.fc-toolbar button:focus{z-index:5}.fc-view-container *,.fc-view-container :after,.fc-view-container :before{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}.fc-view,.fc-view>table{position:relative;z-index:1}.fc-basicDay-view .fc-content-skeleton,.fc-basicWeek-view .fc-content-skeleton{padding-bottom:1em}.fc-basic-view .fc-body .fc-row{min-height:4em}.fc-row.fc-rigid .fc-content-skeleton{position:absolute;top:0;left:0;right:0}.fc-day-top.fc-other-month{opacity:.3}.fc-basic-view .fc-day-number,.fc-basic-view .fc-week-number{padding:2px}.fc-basic-view th.fc-day-number,.fc-basic-view th.fc-week-number{padding:0 2px}.fc-ltr .fc-basic-view .fc-day-top .fc-day-number{float:right}.fc-rtl .fc-basic-view .fc-day-top .fc-day-number{float:left}.fc-ltr .fc-basic-view .fc-day-top .fc-week-number{float:left;border-radius:0 0 3px}.fc-rtl .fc-basic-view .fc-day-top .fc-week-number{float:right;border-radius:0 0 0 3px}.fc-basic-view .fc-day-top .fc-week-number{min-width:1.5em;text-align:center;background-color:#f2f2f2;color:grey}.fc-basic-view td.fc-week-number>*{display:inline-block;min-width:1.25em}.fc-agenda-view .fc-day-grid{position:relative;z-index:2}.fc-agenda-view .fc-day-grid .fc-row{min-height:3em}.fc-agenda-view .fc-day-grid .fc-row .fc-content-skeleton{padding-bottom:1em}.fc .fc-axis{vertical-align:middle;padding:0 4px;white-space:nowrap}.fc-ltr .fc-axis{text-align:right}.fc-rtl .fc-axis{text-align:left}.fc-time-grid,.fc-time-grid-container{position:relative;z-index:1}.fc-time-grid{min-height:100%}.fc-time-grid table{border:0 hidden transparent}.fc-time-grid>.fc-bg{z-index:1}.fc-time-grid .fc-slats,.fc-time-grid>hr{position:relative;z-index:2}.fc-time-grid .fc-content-col{position:relative}.fc-time-grid .fc-content-skeleton{position:absolute;z-index:3;top:0;left:0;right:0}.fc-time-grid .fc-business-container{position:relative;z-index:1}.fc-time-grid .fc-bgevent-container{position:relative;z-index:2}.fc-time-grid .fc-highlight-container{z-index:3;position:relative}.fc-time-grid .fc-event-container{position:relative;z-index:4}.fc-time-grid .fc-now-indicator-line{z-index:5}.fc-time-grid .fc-mirror-container{position:relative;z-index:6}.fc-time-grid .fc-slats td{height:1.5em;border-bottom:0}.fc-time-grid .fc-slats .fc-minor td{border-top-style:dotted}.fc-time-grid .fc-highlight{position:absolute;left:0;right:0}.fc-ltr .fc-time-grid .fc-event-container{margin:0 2.5% 0 2px}.fc-rtl .fc-time-grid .fc-event-container{margin:0 2px 0 2.5%}.fc-time-grid .fc-bgevent,.fc-time-grid .fc-event{position:absolute;z-index:1}.fc-time-grid .fc-bgevent{left:0;right:0}.fc-v-event.fc-not-start{border-top-width:0;padding-top:1px;border-top-left-radius:0;border-top-right-radius:0}.fc-v-event.fc-not-end{border-bottom-width:0;padding-bottom:1px;border-bottom-left-radius:0;border-bottom-right-radius:0}.fc-time-grid-event.fc-selected{overflow:visible}.fc-time-grid-event.fc-selected .fc-bg{display:none}.fc-time-grid-event .fc-content{overflow:hidden}.fc-time-grid-event .fc-time,.fc-time-grid-event .fc-title{padding:0 1px}.fc-time-grid-event .fc-time{font-size:.85em;white-space:nowrap}.fc-time-grid-event.fc-short .fc-content{white-space:nowrap}.fc-time-grid-event.fc-short .fc-time,.fc-time-grid-event.fc-short .fc-title{display:inline-block;vertical-align:top}.fc-time-grid-event.fc-short .fc-time span{display:none}.fc-time-grid-event.fc-short .fc-time:before{content:attr(data-start)}.fc-time-grid-event.fc-short .fc-time:after{content:"\A0-\A0"}.fc-time-grid-event.fc-short .fc-title{font-size:.85em;padding:0}.fc-time-grid-event.fc-allow-mouse-resize .fc-resizer{left:0;right:0;bottom:0;height:8px;overflow:hidden;line-height:8px;font-size:11px;font-family:monospace;text-align:center;cursor:s-resize}.fc-time-grid-event.fc-allow-mouse-resize .fc-resizer:after{content:"="}.fc-time-grid-event.fc-selected .fc-resizer{border-radius:5px;border-width:1px;width:8px;height:8px;border-style:solid;border-color:inherit;background:#fff;left:50%;margin-left:-5px;bottom:-5px}.fc-time-grid .fc-now-indicator-line{border-top-width:1px;left:0;right:0}.fc-time-grid .fc-now-indicator-arrow{margin-top:-5px}.fc-ltr .fc-time-grid .fc-now-indicator-arrow{left:0;border-width:5px 0 5px 6px;border-top-color:transparent;border-bottom-color:transparent}.fc-rtl .fc-time-grid .fc-now-indicator-arrow{right:0;border-width:5px 6px 5px 0;border-top-color:transparent;border-bottom-color:transparent}.fc-event-dot{display:inline-block;width:10px;height:10px;border-radius:5px}.fc-rtl .fc-list-view{direction:rtl}.fc-list-view{border-width:1px;border-style:solid}.fc .fc-list-table{table-layout:auto}.fc-list-table td{border-width:1px 0 0;padding:8px 14px}.fc-list-table tr:first-child td{border-top-width:0}.fc-list-heading{border-bottom-width:1px}.fc-list-heading td{font-weight:700}.fc-ltr .fc-list-heading-main{float:left}.fc-ltr .fc-list-heading-alt,.fc-rtl .fc-list-heading-main{float:right}.fc-rtl .fc-list-heading-alt{float:left}.fc-list-item.fc-has-url{cursor:pointer}.fc-list-item-marker,.fc-list-item-time{white-space:nowrap;width:1px}.fc-ltr .fc-list-item-marker{padding-right:0}.fc-rtl .fc-list-item-marker{padding-left:0}.fc-list-item-title a{text-decoration:none;color:inherit}.fc-list-item-title a[href]:hover{text-decoration:underline}.fc-list-empty-wrap2{position:absolute;top:0;left:0;right:0;bottom:0}.fc-list-empty-wrap1{width:100%;height:100%;display:table}.fc-list-empty{display:table-cell;vertical-align:middle;text-align:center}.fc-unthemed .fc-list-empty{background-color:#eee}@media print{.fc-bg,.fc-bgevent-container,.fc-bgevent-skeleton,.fc-business-container,.fc-event .fc-resizer,.fc-highlight-container,.fc-highlight-skeleton,.fc-mirror-container,.fc-mirror-skeleton{display:none}.fc tbody .fc-row,.fc-time-grid{min-height:0!important}.fc-time-grid .fc-event.fc-not-end:after,.fc-time-grid .fc-event.fc-not-start:before{content:"..."}.fc{max-width:100%!important}.fc-event{background:#fff!important;color:#000!important;page-break-inside:avoid}.fc hr,.fc tbody,.fc td,.fc th,.fc thead,.fc-row{border-color:#ccc!important;background:#fff!important}.fc tbody .fc-row{height:auto!important}.fc tbody .fc-row .fc-content-skeleton{position:static;padding-bottom:0!important}.fc tbody .fc-row .fc-content-skeleton tbody tr:last-child td{padding-bottom:1em}.fc tbody .fc-row .fc-content-skeleton table{height:1em}.fc-more,.fc-more-cell{display:none!important}.fc tr.fc-limited{display:table-row!important}.fc td.fc-limited{display:table-cell!important}.fc-agenda-view .fc-axis,.fc-popover{display:none}.fc-slats,.fc-time-grid hr{display:none!important}.fc button,.fc-button-group,.fc-time-grid .fc-event .fc-time span{display:none}.fc-time-grid .fc-content-skeleton{position:static}.fc-time-grid .fc-content-skeleton table{height:4em}.fc-time-grid .fc-event-container{margin:0!important}.fc-time-grid .fc-event{position:static!important;margin:3px 2px!important}.fc-time-grid .fc-event.fc-not-end{border-bottom-width:1px!important}.fc-time-grid .fc-event.fc-not-start{border-top-width:1px!important}.fc-time-grid .fc-event .fc-time{white-space:normal!important}.fc-time-grid .fc-event .fc-time:after{content:attr(data-full)}.fc-day-grid-container,.fc-scroller,.fc-time-grid-container{overflow:visible!important;height:auto!important}.fc-row{border:0!important;margin:0!important}}
diff --git a/plugins/discourse-calendar/config/locales/client.ar.yml b/plugins/discourse-calendar/config/locales/client.ar.yml
new file mode 100644
index 00000000000..18f1a4efea0
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/client.ar.yml
@@ -0,0 +1,502 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+ar:
+ admin_js:
+ admin:
+ calendar: "التقويم"
+ site_settings:
+ categories:
+ discourse_post_event: "حدث Discourse"
+ discourse_calendar: "تقويم Discourse"
+ js:
+ notifications:
+ titles:
+ event_reminder: "تذكير بالحدث"
+ event_invitation: "دعوة إلى حدث"
+ popup:
+ event_reminder: تذكير بالحدث
+ discourse_automation:
+ triggerables:
+ event_started:
+ title: بدأ الحدث
+ fields:
+ topic_id:
+ label: معرِّف الموضوع
+ discourse_calendar:
+ invite_user_notification: "دعاك %{username} للانضمام إلى: %{description}"
+ on_holiday: "في عطلة"
+ disable_holiday: "إيقاف"
+ enable_holiday: "تفعيل"
+ holiday: "عطلة"
+ holidays:
+ header_title: "العطلات"
+ pick_region_description: "اختر منطقة لعرض العطلات لتلك المنطقة."
+ disabled_holidays_description: "سيتم استبعاد العطلات المتوقفة من تقويم عطلات فريق العمل."
+ date: "التاريخ"
+ add_to_calendar: "الإضافة إلى تقويم Google"
+ toggle_timezone_offset_title: "تبديل إزاحة المنطقة الزمنية"
+ region:
+ title: "المنطقة"
+ none: "لا يوجد"
+ use_current_region: "استخدام المنطقة الحالية"
+ names:
+ ae: "الإمارات العربية المتحدة"
+ ar: "الأرجنتين"
+ at: "النمسا"
+ au_act: "أستراليا (au_act)"
+ au_nsw: "أستراليا (au_nsw)"
+ au_nt: "أستراليا (au_nsw)"
+ au_qld_brisbane: "أستراليا (au_qld_brisbane)"
+ au_qld_cairns: "أستراليا (au_qld_cairns)"
+ au_qld: "أستراليا (au_qld)"
+ au_sa: "أستراليا (au_sa)"
+ au_tas_north: "أستراليا (au_tas_north)"
+ au_tas_south: "أستراليا (au_tas_south)"
+ au_tas: "أستراليا (au_tas)"
+ au_vic_melbourne: "أستراليا (au_vic_melbourne)"
+ au_vic: "أستراليا (au_vic)"
+ au_wa: "أستراليا (au_wa)"
+ au: "أستراليا"
+ be_fr: "بلجيكا (be_fr)"
+ be_nl: "بلجيكا (be_nl)"
+ bg_bg: "بلغاريا (bg_bg)"
+ bg_en: "بلغاريا (bg_en)"
+ br: "البرازيل"
+ br_sp: "البرازيل (br_sp)"
+ br_spcapital: "البرازيل (br_spcapital)"
+ ca_ab: "كندا (ca_ab)"
+ ca_bc: "كندا (ca_bc)"
+ ca_mb: "كندا (ca_mb)"
+ ca_nb: "كندا (ca_nb)"
+ ca_nl: "كندا (ca_nl)"
+ ca_ns: "كندا (ca_ns)"
+ ca_nt: "كندا (ca_nt)"
+ ca_nu: "كندا (ca_nu)"
+ ca_on: "كندا (ca_on)"
+ ca_pe: "كندا (ca_pe)"
+ ca_qc: "كندا (ca_qc)"
+ ca_sk: "كندا (ca_sk)"
+ ca_yt: "كندا (ca_yt)"
+ ca: "كندا"
+ ch_ag: "سويسرا (ch_ag)"
+ ch_ai: "سويسرا (ch_ai)"
+ ch_ar: "سويسرا (ch_ar)"
+ ch_be: "سويسرا (ch_be)"
+ ch_bl: "سويسرا (ch_bl)"
+ ch_bs: "سويسرا (ch_bs)"
+ ch_fr: "سويسرا (ch_fr)"
+ ch_ge: "سويسرا (ch_ge)"
+ ch_gl: "سويسرا (ch_gl)"
+ ch_gr: "سويسرا (ch_gr)"
+ ch_ju: "سويسرا (ch_ju)"
+ ch_lu: "سويسرا (ch_lu)"
+ ch_ne: "سويسرا (ch_ne)"
+ ch_nw: "سويسرا (ch_nw)"
+ ch_ow: "سويسرا (ch_ow)"
+ ch_sg: "سويسرا (ch_sg)"
+ ch_sh: "سويسرا (ch_sh)"
+ ch_so: "سويسرا (ch_so)"
+ ch_sz: "سويسرا (ch_sz)"
+ ch_tg: "سويسرا (ch_tg)"
+ ch_ti: "سويسرا (ch_ti)"
+ ch_ur: "سويسرا (ch_ur)"
+ ch_vd: "سويسرا (ch_vd)"
+ ch_vs: "سويسرا (ch_vs)"
+ ch_zg: "سويسرا (ch_zg)"
+ ch_zh: "سويسرا (ch_zh)"
+ ch: "سويسرا"
+ cl: "شيلي"
+ co: "كولومبيا"
+ cr: "كوستاريكا"
+ cz: "جمهورية التشيك"
+ de_bb: "ألمانيا (de_bb)"
+ de_be: "ألمانيا (de_be)"
+ de_bw: "ألمانيا (de_bw)"
+ de_by_augsburg: "ألمانيا (de_by_augsburg)"
+ de_by_cath: "ألمانيا (de_by_cath)"
+ de_by: "ألمانيا (de_by)"
+ de_hb: "ألمانيا (de_hb)"
+ de_he: "ألمانيا (de_he)"
+ de_hh: "ألمانيا (de_hh)"
+ de_mv: "ألمانيا (de_mv)"
+ de_ni: "ألمانيا (de_ni)"
+ de_nw: "ألمانيا (de_nw)"
+ de_rp: "ألمانيا (de_rp)"
+ de_sh: "ألمانيا (de_sh)"
+ de_sl: "ألمانيا (de_sl)"
+ de_sn_sorbian: "ألمانيا (de_sn_sorbian)"
+ de_sn: "ألمانيا (de_sn)"
+ de_st: "ألمانيا (de_st)"
+ de_th_cath: "ألمانيا (de_th_cath)"
+ de_th: "ألمانيا (de_th)"
+ de: "ألمانيا"
+ dk: "الدنمارك"
+ ee: "إستونيا"
+ el: "اليونان"
+ es_an: "إسبانيا (es_an)"
+ es_ar: "إسبانيا (es_ar)"
+ es_ce: "إسبانيا (es_ce)"
+ es_cl: "إسبانيا (es_cl)"
+ es_cm: "إسبانيا (es_cm)"
+ es_cn: "إسبانيا (es_cn)"
+ es_ct: "إسبانيا (es_ct)"
+ es_ex: "إسبانيا (es_ex)"
+ es_ga: "إسبانيا (es_ga)"
+ es_ib: "إسبانيا (es_ib)"
+ es_lo: "إسبانيا (es_lo)"
+ es_m: "إسبانيا (es_m)"
+ es_mu: "إسبانيا (es_mu)"
+ es_na: "إسبانيا (es_na)"
+ es_o: "إسبانيا (es_o)"
+ es_pv: "إسبانيا (es_pv)"
+ es_v: "إسبانيا (es_v)"
+ es_vc: "إسبانيا (es_vc)"
+ es: "إسبانيا"
+ fi: "فنلندا"
+ fr_a: "فرنسا (fr_a)"
+ fr_m: "فرنسا (fr_m)"
+ fr: "فرنسا"
+ gb_con: "المملكة المتحدة (gb_con)"
+ gb_eaw: "المملكة المتحدة (gb_eaw)"
+ gb_eng: "المملكة المتحدة (gb_eng)"
+ gb_gsy: "المملكة المتحدة (gb_gsy)"
+ gb_iom: "المملكة المتحدة (gb_iom)"
+ gb_jsy: "المملكة المتحدة (gb_jsy)"
+ gb_nir: "المملكة المتحدة (gb_nir)"
+ gb_sct: "المملكة المتحدة (gb_sct)"
+ gb_wls: "المملكة المتحدة (gb_wls)"
+ gb: "المملكة المتحدة"
+ ge: "جورجيا"
+ gg: "غيرنزي"
+ gh: "غانا"
+ hk: "هونغ كونغ"
+ hr: "كرواتيا"
+ hu: "المجر"
+ id: "إندونيسيا"
+ ie: "أيرلندا"
+ im: "جزيرة مان"
+ in: "الهند"
+ in_gj: "الهند (in_gj)"
+ in_mh: "الهند (in_mh)"
+ in_rj: "الهند (in_rj)"
+ in_tn: "الهند (in_tn)"
+ in_ka: "الهند (in_ka)"
+ is: "أيسلندا"
+ it_bl: "إيطاليا (it_bl)"
+ it_fi: "إيطاليا (it_fi)"
+ it_ge: "إيطاليا (it_ge)"
+ it_pd: "إيطاليا (it_pd)"
+ it_rm: "إيطاليا (it_rm)"
+ it_ro: "إيطاليا (it_ro)"
+ it_to: "إيطاليا (it_to)"
+ it_tv: "إيطاليا (it_tv)"
+ it_ve: "إيطاليا (it_ve)"
+ it_vi: "إيطاليا (it_vi)"
+ it_vr: "إيطاليا (it_vr)"
+ it: "إيطاليا"
+ je: "جيرسي"
+ jp: "اليابان"
+ ke: "كينيا"
+ kr: "جمهورية كوريا"
+ kz: "جمهورية كازاخستان"
+ li: "ليختنشتاين"
+ lt: "ليتوانيا"
+ lu: "لوكسمبورغ"
+ lv: "لاتفيا"
+ ma: "المغرب"
+ mt_en: "مالطا (mt_en)"
+ mt_mt: "مالطا (mt_mt)"
+ mx_pue: "المكسيك (mx_pue)"
+ mx: "المكسيك"
+ my: "ماليزيا"
+ ng: "نيجيريا"
+ nl: "هولندا"
+ "no": "النرويج"
+ nz_ak: "نيوزيلندا (nz_ak)"
+ nz_ca: "نيوزيلندا (nz_ca)"
+ nz_ch: "نيوزيلندا (nz_ch)"
+ nz_hb: "نيوزيلندا (nz_hb)"
+ nz_mb: "نيوزيلندا (nz_mb)"
+ nz_ne: "نيوزيلندا (nz_ne)"
+ nz_nl: "نيوزيلندا (nz_nl)"
+ nz_ot: "نيوزيلندا (nz_ot)"
+ nz_sc: "نيوزيلندا (nz_sc)"
+ nz_sl: "نيوزيلندا (nz_sl)"
+ nz_ta: "نيوزيلندا (nz_ta)"
+ nz_we: "نيوزيلندا (nz_we)"
+ nz_wl: "نيوزيلندا (nz_wl)"
+ nz: "نيوزيلندا"
+ pe: "بيرو"
+ ph: "الفلبين"
+ pl: "بولندا"
+ pt_li: "البرتغال (pt_li)"
+ pt_po: "البرتغال (pt_po)"
+ pt: "البرتغال"
+ ro: "رومانيا"
+ rs_cyrl: "صربيا (rs_cyrl)"
+ rs_la: "صربيا (rs_la)"
+ ru: "روسيا الاتحادية"
+ se: "السويد"
+ sa: "المملكة العربية السعودية"
+ sg: "سنغافورة"
+ si: "سلوفينيا"
+ sk: "سلوفاكيا"
+ th: "تايلاند"
+ tn: "تونس"
+ tr: "تركيا"
+ ua: "أوكرانيا"
+ us_ak: "الولايات المتحدة (us_ak)"
+ us_al: "الولايات المتحدة (us_al)"
+ us_ar: "الولايات المتحدة (us_ar)"
+ us_az: "الولايات المتحدة (us_az)"
+ us_ca: "الولايات المتحدة (us_ca)"
+ us_co: "الولايات المتحدة (us_co)"
+ us_ct: "الولايات المتحدة (us_ct)"
+ us_dc: "الولايات المتحدة (us_dc)"
+ us_de: "الولايات المتحدة (us_de)"
+ us_fl: "الولايات المتحدة (us_fl)"
+ us_ga: "الولايات المتحدة (us_ga)"
+ us_gu: "الولايات المتحدة (us_gu)"
+ us_hi: "الولايات المتحدة (us_hi)"
+ us_ia: "الولايات المتحدة (us_ia)"
+ us_id: "الولايات المتحدة (us_id)"
+ us_il: "الولايات المتحدة (us_il)"
+ us_in: "الولايات المتحدة (us_in)"
+ us_ks: "الولايات المتحدة (us_ks)"
+ us_ky: "الولايات المتحدة (us_ky)"
+ us_la: "الولايات المتحدة (us_la)"
+ us_ma: "الولايات المتحدة (us_ma)"
+ us_md: "الولايات المتحدة (us_md)"
+ us_me: "الولايات المتحدة (us_me)"
+ us_mi: "الولايات المتحدة (us_mi)"
+ us_mn: "الولايات المتحدة (us_mn)"
+ us_mo: "الولايات المتحدة (us_mo)"
+ us_ms: "الولايات المتحدة (us_ms)"
+ us_mt: "الولايات المتحدة (us_mt)"
+ us_nc: "الولايات المتحدة (us_nc)"
+ us_nd: "الولايات المتحدة (us_nd)"
+ us_ne: "الولايات المتحدة (us_ne)"
+ us_nh: "الولايات المتحدة (us_nh)"
+ us_nj: "الولايات المتحدة (us_nj)"
+ us_nm: "الولايات المتحدة (us_nm)"
+ us_nv: "الولايات المتحدة (us_nv)"
+ us_ny: "الولايات المتحدة (us_ny)"
+ us_oh: "الولايات المتحدة (us_oh)"
+ us_ok: "الولايات المتحدة (us_ok)"
+ us_or: "الولايات المتحدة (us_or)"
+ us_pa: "الولايات المتحدة (us_pa)"
+ us_pr: "الولايات المتحدة (us_pr)"
+ us_ri: "الولايات المتحدة (us_ri)"
+ us_sc: "الولايات المتحدة (us_sc)"
+ us_sd: "الولايات المتحدة (us_sd)"
+ us_tn: "الولايات المتحدة (us_tn)"
+ us_tx: "الولايات المتحدة (us_tx)"
+ us_ut: "الولايات المتحدة (us_ut)"
+ us_va: "الولايات المتحدة (us_va)"
+ us_vi: "الولايات المتحدة (us_vi)"
+ us_vt: "الولايات المتحدة (us_vt)"
+ us_wa: "الولايات المتحدة (us_wa)"
+ us_wi: "الولايات المتحدة (us_wi)"
+ us_wv: "الولايات المتحدة (us_wv)"
+ us_wy: "الولايات المتحدة (us_wy)"
+ us: "الولايات المتحدة"
+ ve: "فنزويلا"
+ vi: "جزر العذراء الأمريكية"
+ za: "جنوب إفريقيا"
+ toolbar_button:
+ today: "اليوم"
+ month: "الشهر"
+ week: "الأسبوع"
+ day: "اليوم"
+ list: "إدراج"
+ group_timezones:
+ search: "بحث..."
+ group_availability: "توافر %{group}"
+ discourse_post_event:
+ notifications:
+ before_event_reminder: "هناك حدث على وشك البدء"
+ after_event_reminder: "هناك حدث انتهى"
+ ongoing_event_reminder: "هناك حدث جارٍ"
+ invite_user_notification: "%{username} %{description}"
+ invite_user_predefined_attendance_notification_html: "حدَّد %{username} حضورك تلقائيًا ودعاك إلى %{description}"
+ before_event_reminder_html: "هناك حدث على وشك البدء %{description}"
+ after_event_reminder_html: "انتهى حدث %{description}"
+ ongoing_event_reminder_html: "هناك حدث جارٍ %{description}"
+ edit_reason: "تم تحديث الحدث"
+ edit_reason_closed: "تم إغلاق الحدث"
+ edit_reason_opened: "تم فتح الحدث"
+ topic_title:
+ starts_at: "سيبدأ الحدث: %{date}"
+ ended_at: "انتهى الحدث: %{date}"
+ ends_in_duration: "ينتهي بعد %{duration}"
+ show_all: "عرض الكل"
+ show_participants: "إظهار المشاركين"
+ participants:
+ zero: "شارك %{count} مستخدم."
+ one: "شارك مستخدم واحد (%{count})."
+ two: "شارك مستخدمَين (%{count})."
+ few: "شارك %{count} مستخدمين."
+ many: "شارك %{count} مستخدمًا."
+ other: "شارك %{count} مستخدم."
+ invite: "إرسال إشعار إلى المستخدم"
+ add_to_calendar: "إضافة إلى التقويم"
+ send_pm_to_creator: "إرسال رسالة خاصة إلى %{username}"
+ leave: "مغادرة الحدث"
+ edit_event: "تعديل الحدث"
+ export_event: "تصدير الحدث"
+ created_by: "تم إنشاؤه بواسطة"
+ bulk_invite: "دعوة جماعية"
+ close_event: "إغلاق الحدث"
+ open_event: "فتح الحدث"
+ invitees_modal:
+ title_invited: "المشاركة في الحدث"
+ title_participated: "قائمة المستخدمين الذين شاركوا"
+ filter_placeholder: "تصفية المستخدمين"
+ remove_invitee: "إزالة المدعو من القائمة"
+ add_invitee: "إضافة مدعو إلى القائمة"
+ bulk_invite_modal:
+ confirm: "تأكيد"
+ text: "تحميل ملف CSV"
+ title: "دعوة جماعية"
+ success: "تم تحميل الملف بنجاح، وسيتم إرسال إشعار إليك عبر رسالة عند اكتمال العملية."
+ error: "عذرًا، يجب أن يكون الملف بتنسيق CSV."
+ confirmation_message: "أنت على وشك إرسال إشعار إلى جميع الأشخاص في الملف الذي تم رفعه."
+ description_public: "لا تقبل الأحداث العامة أسماء المستخدمين إلا للدعوات الجماعية."
+ description_private: "لا تقبل الأحداث العامة إلا أسماء المجموعات للدعوات الجماعية."
+ download_sample_csv: "تنزيل نموذج ملف CSV"
+ send_bulk_invites: "إرسال دعوات"
+ group_selector_placeholder: "اختر مجموعة..."
+ user_selector_placeholder: "اختر المستخدم..."
+ inline_title: "دعوة جماعية مضمَّنة"
+ csv_title: "دعوة جماعية باستخدام ملف CSV"
+ upcoming_events:
+ title: "الأحداث القادمة"
+ creator: "المنشئ"
+ status: "الحالة"
+ starts_at: "يبدأ في"
+ upcoming_events_list:
+ title: "الأحداث القادمة"
+ empty: "لا توجد أحداث قادمة"
+ all_day: "طوال اليوم"
+ error: "فشل استرداد الأحداث"
+ try_again: "إعادة المحاولة"
+ view_all: "عرض الكل"
+ category:
+ sort_topics_by_event_start_date: "ترتيب الموضوعات حسب تاريخ بدء الحدث."
+ disable_topic_resorting: "إعادة فرز الموضوع."
+ settings_sections:
+ event_sorting: "ترتيب الأحداث"
+ preview:
+ more_than_one_event: "لا يمكن أن يكون لديك أكثر من حدثٍ واحد."
+ models:
+ invitee:
+ no_users: "لم يتم العثور على المستخدمين"
+ status:
+ unknown: "غير مهتم"
+ going: "ذاهب"
+ not_going: "لست ذاهبًا"
+ interested: "مهتم"
+ going_count:
+ zero: "%{count} مدعو ذاهب"
+ one: "مدعو واحد (%{count}) ذاهب"
+ two: "مدعوان (%{count}) ذاهبان"
+ few: "%{count} مدعوين ذاهبين"
+ many: "%{count} مدعوًا ذاهبًا"
+ other: "%{count} مدعو ذاهب"
+ not_going_count:
+ zero: "%{count} مدعو غير ذاهب"
+ one: "مدعو واحد (%{count}) غير ذاهب"
+ two: "مدعوان (%{count}) غير ذاهبَين"
+ few: "%{count} مدعوين غير ذاهبين"
+ many: "%{count} مدعوًا غير ذاهب"
+ other: "%{count} مدعو غير ذاهب"
+ interested_count:
+ zero: "%{count} مدعو مهتم"
+ one: "مدعو واحد (%{count}) مهتم"
+ two: "مدعوان (%{count}) مهتمان"
+ few: "%{count} مدعوين مهتمين"
+ many: "%{count} مدعوًا مهتمًا"
+ other: "%{count} مدعو مهتم"
+ invited_count:
+ zero: "%{count} مستخدم تمت دعوته"
+ one: "مستخدم واحد (%{count}) تمت دعوته"
+ two: "مستخدمان (%{count}) تمت دعوتهما"
+ few: "%{count} مستخدمين تمت دعوتهم"
+ many: "%{count} مستخدمًا تمت دعوته"
+ other: "%{count} مستخدم تمت دعوته"
+ event:
+ expired: "انتهى"
+ closed: "مغلق"
+ status:
+ standalone:
+ title: "قائم بذاته"
+ description: "لا يمكن الانضمام إلى حدثٍ مستقل."
+ public:
+ title: "عام"
+ description: "يمكن لأي شخص الانضمام إلى حدثٍ عام."
+ private:
+ title: "خاص"
+ description: "لا يمكن إلا للمستخدمين المدعوين الانضمام إلى حدثٍ خاص."
+ builder_modal:
+ custom_fields:
+ label: "الحقول المخصَّصة"
+ placeholder: "اختياري"
+ description: "يتم تحديد الحقول المخصَّصة المسموح بها في إعدادات الموقع. ويتم استخدام الحقول المخصَّصة لنقل البيانات إلى المكوِّنات الإضافية الأخرى."
+ create_event_title: "إنشاء حدث"
+ update_event_title: "تعديل الحدث"
+ confirm_delete: "هل تريد بالتأكيد حذف هذا الحدث؟"
+ confirm_close: "هل تريد بالتأكيد إغلاق هذا الحدث؟"
+ confirm_open: "هل تريد بالتأكيد فتح هذا الحدث؟"
+ create: "إنشاء"
+ update: "حفظ"
+ attach: "إنشاء حدث"
+ add_reminder: "إضافة تذكير"
+ timezone:
+ label: المنطقة الزمنية
+ remove_timezone: لا توجد منطقة زمنية (UTC)
+ reminders:
+ label: "التذكيرات"
+ types:
+ bump_topic: "رفع الموضوع تلقائيًا"
+ notification: "إرسال إشعار إلى المشاركين"
+ units:
+ minutes: "دقائق"
+ hours: "ساعات"
+ days: "أيام"
+ weeks: "أسابيع"
+ periods:
+ before: "قبل"
+ after: "بعد"
+ recurrence:
+ label: "التكرار"
+ none: "بلا تكرار"
+ every_day: "كل يوم"
+ every_month: "كل شهر في هذا اليوم من الأسبوع"
+ every_weekday: "كل أيام الأسبوع"
+ every_week: "كل أسبوع في هذا اليوم"
+ every_two_weeks: "كل أسبوعين في هذا اليوم"
+ every_four_weeks: "كل أربعة أسابيع في هذا اليوم"
+ minimal:
+ label: "حدث صغير"
+ checkbox_label: "إخفاء زريِّ ذاهب/غير ذاهب وحالة المدعوين"
+ url:
+ label: "عنوان URL"
+ placeholder: "اختياري"
+ location:
+ label: "الموقع الجغرافي"
+ description:
+ label: "الوصف"
+ name:
+ label: "اسم الحدث"
+ placeholder: "اختياري، الإعدادات الافتراضية لعنوان الموضوع"
+ invitees:
+ label: "المجموعات المدعوة"
+ status:
+ label: "الحالة"
+ invite_user_or_group:
+ title: "إرسال إشعار إلى المستخدمين أو المجموعات"
+ invite: "إرسال"
diff --git a/plugins/discourse-calendar/config/locales/client.be.yml b/plugins/discourse-calendar/config/locales/client.be.yml
new file mode 100644
index 00000000000..101e0157ca9
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/client.be.yml
@@ -0,0 +1,52 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+be:
+ js:
+ discourse_automation:
+ triggerables:
+ event_started:
+ fields:
+ topic_id:
+ label: Номер тэмы
+ discourse_calendar:
+ disable_holiday: "Адключыць"
+ enable_holiday: "Уключыць"
+ toolbar_button:
+ today: "сёння"
+ month: "месяц"
+ week: "тыдзень"
+ day: "дзень"
+ discourse_post_event:
+ upcoming_events:
+ status: "Статус"
+ models:
+ event:
+ closed: "Закрыта"
+ status:
+ public:
+ title: "грамадскага"
+ private:
+ title: "прыватны"
+ builder_modal:
+ create: "стварыць"
+ update: "захаваць"
+ reminders:
+ units:
+ days: "дзён"
+ periods:
+ before: "перад"
+ after: "пасля"
+ url:
+ label: "URL спасылка"
+ location:
+ label: "размяшчэнне"
+ description:
+ label: "Апісанне"
+ status:
+ label: "Статус"
+ invite_user_or_group:
+ invite: "адправіць"
diff --git a/plugins/discourse-calendar/config/locales/client.bg.yml b/plugins/discourse-calendar/config/locales/client.bg.yml
new file mode 100644
index 00000000000..7fad7867ba7
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/client.bg.yml
@@ -0,0 +1,70 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+bg:
+ js:
+ discourse_automation:
+ triggerables:
+ event_started:
+ fields:
+ topic_id:
+ label: Тема ID
+ discourse_calendar:
+ disable_holiday: "Деактивиране"
+ enable_holiday: "Позволи"
+ date: "Дата"
+ region:
+ none: "Без"
+ toolbar_button:
+ today: "Днес"
+ month: "Месец"
+ week: "Седмица"
+ day: "Ден"
+ group_timezones:
+ search: "Търсене ... "
+ discourse_post_event:
+ notifications:
+ invite_user_notification: "%{username} %{description}"
+ bulk_invite: "Групова покана"
+ bulk_invite_modal:
+ title: "Групова покана"
+ success: "Файлът е качен успешно. Ще бъдете информирани чрез съобщение, когато процесът завърши."
+ error: "За съжаление файлът трябва да е във формат CSV."
+ upcoming_events:
+ status: "Статус"
+ models:
+ event:
+ expired: "Изтекъл срок"
+ closed: "Затворена"
+ status:
+ public:
+ title: "Публични"
+ private:
+ title: "Затворени"
+ builder_modal:
+ custom_fields:
+ placeholder: "По избор"
+ create: "Създай"
+ update: "Запази "
+ timezone:
+ label: Часова зона
+ reminders:
+ units:
+ minutes: "минути"
+ hours: "часа"
+ days: "дни "
+ periods:
+ before: "преди"
+ after: "след"
+ url:
+ label: "URL"
+ placeholder: "По избор"
+ location:
+ label: "Локация"
+ description:
+ label: "Описание"
+ status:
+ label: "Статус"
diff --git a/plugins/discourse-calendar/config/locales/client.bs_BA.yml b/plugins/discourse-calendar/config/locales/client.bs_BA.yml
new file mode 100644
index 00000000000..da603183f6d
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/client.bs_BA.yml
@@ -0,0 +1,73 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+bs_BA:
+ js:
+ discourse_automation:
+ triggerables:
+ event_started:
+ fields:
+ topic_id:
+ label: ID teme
+ discourse_calendar:
+ disable_holiday: "Onemogući"
+ enable_holiday: "Omogući"
+ date: "Datum"
+ region:
+ none: "Ništa"
+ toolbar_button:
+ today: "Today"
+ month: "Mjesec"
+ week: "Sedmica"
+ day: "Day"
+ group_timezones:
+ search: "Pretraga..."
+ discourse_post_event:
+ notifications:
+ invite_user_notification: "%{username} %{description}"
+ bulk_invite: "Skupna pozivnica"
+ bulk_invite_modal:
+ title: "Skupna pozivnica"
+ success: "Fajl je uspješno učitan, dobit će te ukratko obavijest o progresu."
+ error: "Oprostite, vaš fajl bi trebao biti u CSV formatu."
+ upcoming_events:
+ status: "Status"
+ models:
+ event:
+ closed: "Zatvoreno"
+ status:
+ public:
+ title: "Javno"
+ private:
+ title: "Privatno"
+ builder_modal:
+ custom_fields:
+ placeholder: "Opciono"
+ create: "napravi"
+ update: "Save"
+ timezone:
+ label: Vremenska zona
+ reminders:
+ units:
+ days: "days"
+ periods:
+ before: "prije"
+ after: "poslije"
+ recurrence:
+ label: "Vraćanje"
+ none: "Bez ponavljanja"
+ every_day: "Svaki dan"
+ url:
+ label: "URL"
+ placeholder: "Opciono"
+ location:
+ label: "Lokacija"
+ description:
+ label: "Opis"
+ status:
+ label: "Status"
+ invite_user_or_group:
+ invite: "Pošalji"
diff --git a/plugins/discourse-calendar/config/locales/client.ca.yml b/plugins/discourse-calendar/config/locales/client.ca.yml
new file mode 100644
index 00000000000..eeca359041d
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/client.ca.yml
@@ -0,0 +1,74 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+ca:
+ js:
+ discourse_automation:
+ triggerables:
+ event_started:
+ fields:
+ topic_id:
+ label: ID de tema
+ discourse_calendar:
+ disable_holiday: "Desactiva"
+ enable_holiday: "Activa"
+ date: "Data"
+ region:
+ none: "Cap"
+ toolbar_button:
+ today: "Avui"
+ month: "Mes"
+ week: "Setmana"
+ day: "Dia"
+ group_timezones:
+ search: "Cerca..."
+ discourse_post_event:
+ notifications:
+ invite_user_notification: "%{username} %{description}"
+ bulk_invite_modal:
+ confirm: "confirma"
+ success: "Fitxer carregat amb èxit. Us notificarem amb un missatge quan s'hagi completat el procés."
+ error: "El fitxer hauria de tenir format CSV."
+ upcoming_events:
+ status: "Estat"
+ models:
+ event:
+ closed: "Tancat"
+ status:
+ public:
+ title: "Públic"
+ private:
+ title: "Privat"
+ builder_modal:
+ custom_fields:
+ placeholder: "Opcional"
+ create: "Crea"
+ update: "Desa"
+ timezone:
+ label: Zona horària
+ reminders:
+ units:
+ minutes: "minuts"
+ hours: "hores"
+ days: "dies"
+ periods:
+ before: "abans de"
+ after: "després de"
+ recurrence:
+ label: "Recurrència"
+ none: "Sense recurrència"
+ every_day: "Cada dia"
+ url:
+ label: "URL"
+ placeholder: "Opcional"
+ location:
+ label: "Ubicació"
+ description:
+ label: "Descripció"
+ status:
+ label: "Estat"
+ invite_user_or_group:
+ invite: "Envia"
diff --git a/plugins/discourse-calendar/config/locales/client.cs.yml b/plugins/discourse-calendar/config/locales/client.cs.yml
new file mode 100644
index 00000000000..22050c46373
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/client.cs.yml
@@ -0,0 +1,501 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+cs:
+ admin_js:
+ admin:
+ calendar: "Kalendář"
+ site_settings:
+ categories:
+ discourse_post_event: "Událost Discourse"
+ discourse_calendar: "Kalendář Discourse"
+ js:
+ notifications:
+ titles:
+ event_reminder: "připomenutí události"
+ event_invitation: "pozvánka na akci"
+ popup:
+ event_reminder: Připomenutí události
+ discourse_automation:
+ triggerables:
+ event_started:
+ title: Událost zahájena
+ fields:
+ topic_id:
+ label: ID tématu
+ discourse_calendar:
+ invite_user_notification: "%{username} vás zve do kalendáře: %{description}"
+ on_holiday: "Na dovolené"
+ disable_holiday: "Vypnout"
+ enable_holiday: "Zapnout"
+ holiday: "Dovolená"
+ holidays:
+ header_title: "Svátky"
+ pick_region_description: "Vyberte region a podívejte se na svátky pro tento region."
+ disabled_holidays_description: "Zakázané svátky budou vyloučeny z kalendáře dovolených."
+ date: "Datum"
+ add_to_calendar: "Přidat do Kalendáře Google"
+ toggle_timezone_offset_title: "Přepnout posun časového pásma"
+ region:
+ title: "Region"
+ none: "Žádná"
+ use_current_region: "Použít současný region"
+ names:
+ ae: "Spojené arabské emiráty"
+ ar: "Argentina"
+ at: "Rakousko"
+ au_act: "Austrálie (au_act)"
+ au_nsw: "Austrálie (au_nsw)"
+ au_nt: "Austrálie (au_nt)"
+ au_qld_brisbane: "Austrálie (au_qld_brisbane)"
+ au_qld_cairns: "Austrálie (au_qld_cairns)"
+ au_qld: "Austrálie (au_qld)"
+ au_sa: "Austrálie (au_sa)"
+ au_tas_north: "Austrálie (au_tas_north)"
+ au_tas_south: "Austrálie (au_tas_south)"
+ au_tas: "Austrálie (au_tas)"
+ au_vic_melbourne: "Austrálie (au_vic_melbourne)"
+ au_vic: "Austrálie (au_vic)"
+ au_wa: "Austrálie (au_wa)"
+ au: "Austrálie"
+ be_fr: "Belgie (be_fr)"
+ be_nl: "Belgie (be_nl)"
+ bg_bg: "Bulharsko (bg_bg)"
+ bg_en: "Bulharsko (bg_en)"
+ br: "Brazílie"
+ br_sp: "Brazílie (br_sp)"
+ br_spcapital: "Brazílie (br_spcapital)"
+ ca_ab: "Kanada (ca_ab)"
+ ca_bc: "Kanada (ca_bc)"
+ ca_mb: "Kanada (ca_mb)"
+ ca_nb: "Kanada (ca_nb)"
+ ca_nl: "Kanada (ca_nl)"
+ ca_ns: "Kanada (ca_ns)"
+ ca_nt: "Kanada (ca_nt)"
+ ca_nu: "Kanada (ca_nu)"
+ ca_on: "Kanada (ca_on)"
+ ca_pe: "Kanada (ca_pe)"
+ ca_qc: "Kanada (ca_qc)"
+ ca_sk: "Kanada (ca_sk)"
+ ca_yt: "Kanada (ca_yt)"
+ ca: "Kanada"
+ ch_ag: "Švýcarsko (ch_ag)"
+ ch_ai: "Švýcarsko (ch_ai)"
+ ch_ar: "Švýcarsko (ch_ar)"
+ ch_be: "Švýcarsko (ch_be)"
+ ch_bl: "Švýcarsko (ch_bl)"
+ ch_bs: "Švýcarsko (ch_bs)"
+ ch_fr: "Švýcarsko (ch_fr)"
+ ch_ge: "Švýcarsko (ch_ge)"
+ ch_gl: "Švýcarsko (ch_gl)"
+ ch_gr: "Švýcarsko (ch_gr)"
+ ch_ju: "Švýcarsko (ch_ju)"
+ ch_lu: "Švýcarsko (ch_lu)"
+ ch_ne: "Švýcarsko (ch_ne)"
+ ch_nw: "Švýcarsko (ch_nw)"
+ ch_ow: "Švýcarsko (ch_ow)"
+ ch_sg: "Švýcarsko (ch_sg)"
+ ch_sh: "Švýcarsko (ch_sh)"
+ ch_so: "Švýcarsko (ch_so)"
+ ch_sz: "Švýcarsko (ch_sz)"
+ ch_tg: "Švýcarsko (ch_tg)"
+ ch_ti: "Švýcarsko (ch_ti)"
+ ch_ur: "Švýcarsko (ch_ur)"
+ ch_vd: "Švýcarsko (ch_vd)"
+ ch_vs: "Švýcarsko (ch_vs)"
+ ch_zg: "Švýcarsko (ch_zg)"
+ ch_zh: "Švýcarsko (ch_zh)"
+ ch: "Švýcarsko"
+ cl: "Chile"
+ co: "Kolumbie"
+ cr: "Kostarika"
+ cz: "Česká republika"
+ de_bb: "Německo (de_bb)"
+ de_be: "Německo (de_be)"
+ de_bw: "Německo (de_bw)"
+ de_by_augsburg: "Německo (de_by_augsburg)"
+ de_by_cath: "Německo (de_by_cath)"
+ de_by: "Německo (de_by)"
+ de_hb: "Německo (de_hb)"
+ de_he: "Německo (de_he)"
+ de_hh: "Německo (de_hh)"
+ de_mv: "Německo (de_mv)"
+ de_ni: "Německo (de_ni)"
+ de_nw: "Německo (de_nw)"
+ de_rp: "Německo (de_rp)"
+ de_sh: "Německo (de_sh)"
+ de_sl: "Německo (de_sl)"
+ de_sn_sorbian: "Německo (de_sn_sorbian)"
+ de_sn: "Německo (de_sn)"
+ de_st: "Německo (de_st)"
+ de_th_cath: "Německo (de_th_cath)"
+ de_th: "Německo (de_th)"
+ de: "Německo"
+ dk: "Dánsko"
+ ee: "Estonsko"
+ el: "Řecko"
+ es_an: "Španělsko (es_an)"
+ es_ar: "Španělsko (es_ar)"
+ es_ce: "Španělsko (es_ce)"
+ es_cl: "Španělsko (es_cl)"
+ es_cm: "Španělsko (es_cm)"
+ es_cn: "Španělsko (es_cn)"
+ es_ct: "Španělsko (es_ct)"
+ es_ex: "Španělsko (es_ex)"
+ es_ga: "Španělsko (es_ga)"
+ es_ib: "Španělsko (es_ib)"
+ es_lo: "Španělsko (es_lo)"
+ es_m: "Španělsko (es_m)"
+ es_mu: "Španělsko (es_mu)"
+ es_na: "Španělsko (es_na)"
+ es_o: "Španělsko (es_o)"
+ es_pv: "Španělsko (es_pv)"
+ es_v: "Španělsko (es_v)"
+ es_vc: "Španělsko (es_vc)"
+ es: "Španělsko"
+ fi: "Finsko"
+ fr_a: "Francie (fr_a)"
+ fr_m: "Francie (fr_m)"
+ fr: "Francie"
+ gb_con: "Spojené království (gb_con)"
+ gb_eaw: "Spojené království (gb_eaw)"
+ gb_eng: "Spojené království (gb_eng)"
+ gb_gsy: "Spojené království (gb_gsy)"
+ gb_iom: "Spojené království (gb_iom)"
+ gb_jsy: "Spojené království (gb_jsy)"
+ gb_nir: "Spojené království (gb_nir)"
+ gb_sct: "Spojené království (gb_sct)"
+ gb_wls: "Spojené království (gb_wls)"
+ gb: "Spojené království"
+ ge: "Gruzie"
+ gg: "Guernsey"
+ gh: "Ghana"
+ hk: "Hong Kong"
+ hr: "Chorvatsko"
+ hu: "Maďarsko"
+ id: "Indonésie"
+ ie: "Irsko"
+ im: "Ostrov Man"
+ in: "Indie"
+ in_gj: "Indie (in_gj)"
+ in_mh: "Indie (in_mh)"
+ in_rj: "Indie (in_rj)"
+ in_tn: "Indie (in_tn)"
+ in_ka: "Indie (in_ka)"
+ is: "Island"
+ it_bl: "Itálie (it_bl)"
+ it_fi: "Itálie (it_fi)"
+ it_ge: "Itálie (it_ge)"
+ it_pd: "Itálie (it_pd)"
+ it_rm: "Itálie (it_rm)"
+ it_ro: "Itálie (it_ro)"
+ it_to: "Itálie (it_to)"
+ it_tv: "Itálie (it_tv)"
+ it_ve: "Itálie (it_ve)"
+ it_vi: "Itálie (it_vi)"
+ it_vr: "Itálie (it_vr)"
+ it: "Itálie"
+ je: "Jersey"
+ jp: "Japonsko"
+ ke: "Keňa"
+ kr: "Korea (Republika)"
+ kz: "Kazachstán (republika)"
+ li: "Lichtenštejnsko"
+ lt: "Litva"
+ lu: "Lucembursko"
+ lv: "Lotyšsko"
+ ma: "Maroko"
+ mt_en: "Malta (mt_en)"
+ mt_mt: "Malta (mt_mt)"
+ mx_pue: "Mexiko (mx_pue)"
+ mx: "Mexiko"
+ my: "Malajsie"
+ ng: "Nigérie"
+ nl: "Nizozemsko"
+ "no": "Norsko"
+ nz_ak: "Nový Zéland (nz_ak)"
+ nz_ca: "Nový Zéland (nz_ca)"
+ nz_ch: "Nový Zéland (nz_ch)"
+ nz_hb: "Nový Zéland (nz_hb)"
+ nz_mb: "Nový Zéland (nz_mb)"
+ nz_ne: "Nový Zéland (nz_ne)"
+ nz_nl: "Nový Zéland (nz_nl)"
+ nz_ot: "Nový Zéland (nz_ot)"
+ nz_sc: "Nový Zéland (nz_sc)"
+ nz_sl: "Nový Zéland (nz_sl)"
+ nz_ta: "Nový Zéland (nz_ta)"
+ nz_we: "Nový Zéland (nz_we)"
+ nz_wl: "Nový Zéland (nz_wl)"
+ nz: "Nový Zéland"
+ pe: "Peru"
+ ph: "Filipíny"
+ pl: "Polsko"
+ pt_li: "Portugalsko (pt_li)"
+ pt_po: "Portugalsko (pt_po)"
+ pt: "Portugalsko"
+ ro: "Rumunsko"
+ rs_cyrl: "Srbsko (rs_cyrl)"
+ rs_la: "Srbsko (rs_la)"
+ ru: "Ruská federace"
+ se: "Švédsko"
+ sa: "Saudská arábie"
+ sg: "Singapur"
+ si: "Slovinsko"
+ sk: "Slovensko"
+ th: "Thajsko"
+ tn: "Tunisko"
+ tr: "Turecko"
+ ua: "Ukrajina"
+ us_ak: "Spojené státy (us_ak)"
+ us_al: "Spojené státy (us_al)"
+ us_ar: "Spojené státy (us_ar)"
+ us_az: "Spojené státy (us_az)"
+ us_ca: "Spojené státy (us_ca)"
+ us_co: "Spojené státy (us_co)"
+ us_ct: "Spojené státy (us_ct)"
+ us_dc: "Spojené státy (us_dc)"
+ us_de: "Spojené státy (us_de)"
+ us_fl: "Spojené státy (us_fl)"
+ us_ga: "Spojené státy (us_ga)"
+ us_gu: "Spojené státy (us_gu)"
+ us_hi: "Spojené státy (us_hi)"
+ us_ia: "Spojené státy (us_ia)"
+ us_id: "Spojené státy (us_id)"
+ us_il: "Spojené státy (us_il)"
+ us_in: "Spojené státy (us_in)"
+ us_ks: "Spojené státy (us_ks)"
+ us_ky: "Spojené státy (us_ky)"
+ us_la: "Spojené státy (us_la)"
+ us_ma: "Spojené státy (us_ma)"
+ us_md: "Spojené státy (us_md)"
+ us_me: "Spojené státy (us_me)"
+ us_mi: "Spojené státy (us_mi)"
+ us_mn: "Spojené státy (us_mn)"
+ us_mo: "Spojené státy (us_mo)"
+ us_ms: "Spojené státy (us_ms)"
+ us_mt: "Spojené státy (us_mt)"
+ us_nc: "Spojené státy (us_nc)"
+ us_nd: "Spojené státy (us_nd)"
+ us_ne: "Spojené státy (us_ne)"
+ us_nh: "Spojené státy (us_nh)"
+ us_nj: "Spojené státy (us_nj)"
+ us_nm: "Spojené státy (us_nm)"
+ us_nv: "Spojené státy (us_nv)"
+ us_ny: "Spojené státy (us_ny)"
+ us_oh: "Spojené státy (us_oh)"
+ us_ok: "Spojené státy (us_ok)"
+ us_or: "Spojené státy (us_or)"
+ us_pa: "Spojené státy (us_pa)"
+ us_pr: "Spojené státy (us_pr)"
+ us_ri: "Spojené státy (us_ri)"
+ us_sc: "Spojené státy (us_sc)"
+ us_sd: "Spojené státy (us_sd)"
+ us_tn: "Spojené státy (us_tn)"
+ us_tx: "Spojené státy (us_tx)"
+ us_ut: "Spojené státy (us_ut)"
+ us_va: "Spojené státy (us_va)"
+ us_vi: "Spojené státy (us_vi)"
+ us_vt: "Spojené státy (us_vt)"
+ us_wa: "Spojené státy (us_wa)"
+ us_wi: "Spojené státy (us_wi)"
+ us_wv: "Spojené státy (us_wv)"
+ us_wy: "Spojené státy (us_wy)"
+ us: "Spojené státy"
+ ve: "Venezuela"
+ vi: "Panenské ostrovy (U.S.)"
+ za: "Jižní Afrika"
+ zw: "Zimbabwe"
+ toolbar_button:
+ today: "Dnes"
+ month: "Měsíc"
+ week: "Týden"
+ day: "Den"
+ list: "Seznam"
+ group_timezones:
+ search: "Hledat…"
+ group_availability: "Dostupnost %{group}"
+ discourse_post_event:
+ notifications:
+ before_event_reminder: "Brzy začne událost"
+ after_event_reminder: "Událost skončila"
+ ongoing_event_reminder: "Událost probíhá"
+ invite_user_notification: "%{username} %{description}"
+ invite_user_predefined_attendance_notification_html: "%{username} automaticky nastavil vaši účast a pozval vás na %{description}"
+ before_event_reminder_html: "Brzy začne událost %{description}"
+ after_event_reminder_html: "Skončila událost %{description}"
+ ongoing_event_reminder_html: "Probíhá událost %{description}"
+ edit_reason: "Událost aktualizována"
+ edit_reason_closed: "Akce uzavřena"
+ edit_reason_opened: "Událost otevřena"
+ topic_title:
+ starts_at: "Událost začne: %{date}"
+ ended_at: "Událost skončila: %{date}"
+ ends_in_duration: "Končí %{duration}"
+ show_all: "Zobrazit vše"
+ show_participants: "Zobrazit účastníky"
+ participants:
+ one: "Zúčastnil se %{count} uživatel."
+ few: "Zúčastnili se %{count} uživatelé."
+ many: "Zúčastnilo se %{count} uživatelů."
+ other: "Zúčastnilo se %{count} uživatelů."
+ invite: "Oznámit uživateli"
+ add_to_calendar: "Přidat do kalendáře"
+ send_pm_to_creator: "Odeslat SZ %{username}"
+ leave: "Opustit událost"
+ edit_event: "Upravit událost"
+ export_event: "Exportovat událost"
+ created_by: "Vytvořil/a"
+ bulk_invite: "Hromadné pozvání"
+ close_event: "Zavřít událost"
+ open_event: "Otevřít událost"
+ invitees_modal:
+ title_invited: "Účast na události"
+ title_participated: "Seznam uživatelů, kteří se zúčastnili"
+ filter_placeholder: "Filtrovat uživatele"
+ remove_invitee: "Odebrat pozvaného ze seznamu"
+ add_invitee: "Přidat pozvaného do seznamu"
+ bulk_invite_modal:
+ confirm: "potvrdit"
+ text: "Nahrát soubor CSV"
+ title: "Hromadné pozvání"
+ success: "Nahrání souboru proběhlo úspěšně. O dokončení celého procesu budete informování pomocí zprávy."
+ error: "Omlouváme se, soubor by měl být ve formátu CSV."
+ confirmation_message: "Chystáte se upozornit každého v nahraném souboru."
+ description_public: "Veřejné události přijímají pro hromadné pozvánky pouze uživatelská jména."
+ description_private: "Soukromé události přijímají pro hromadné pozvánky pouze názvy skupin."
+ download_sample_csv: "Stáhnout ukázkový soubor CSV"
+ send_bulk_invites: "Odeslat pozvánky"
+ group_selector_placeholder: "Vyberte skupinu..."
+ user_selector_placeholder: "Vyberte uživatele..."
+ inline_title: "Hromadná pozvánka"
+ csv_title: "Hromadná pozvánka CSV"
+ upcoming_events:
+ title: "Nadcházející události"
+ creator: "Vytvořil/a"
+ status: "Stav"
+ starts_at: "Začíná v"
+ upcoming_events_list:
+ title: "Nadcházející události"
+ empty: "Žádné nadcházející události"
+ all_day: "Celodenní"
+ error: "Nepodařilo se načíst události"
+ try_again: "Zkusit znovu"
+ view_all: "Zobrazit vše"
+ category:
+ sort_topics_by_event_start_date: "Seřadit témata podle data zahájení události."
+ disable_topic_resorting: "Zakázat aktualizaci řazení tématu."
+ settings_sections:
+ event_sorting: "Řazení událostí"
+ preview:
+ more_than_one_event: "Nemůžete mít více než jednu událost."
+ models:
+ invitee:
+ no_users: "Nebyli nalezeni žádní uživatelé"
+ status:
+ unknown: "Nemám zájem"
+ going: "Zúčastním se"
+ not_going: "Nezúčastním se"
+ interested: "Mám zájem"
+ going_count:
+ one: "%{count} se účastní"
+ few: "%{count} se účastní"
+ many: "%{count} se účastní"
+ other: "%{count} se účastní"
+ not_going_count:
+ one: "%{count} se neúčastní"
+ few: "%{count} se neúčastní"
+ many: "%{count} se neúčastní"
+ other: "%{count} se neúčastní"
+ interested_count:
+ one: "%{count} má zájem"
+ few: "%{count} mají zájem"
+ many: "%{count} má zájem"
+ other: "%{count} má zájem"
+ invited_count:
+ one: "%{count} pozván/a"
+ few: "%{count} pozváni"
+ many: "%{count} pozváno"
+ other: "%{count} pozváno"
+ event:
+ expired: "Uplynulá"
+ closed: "Zavřené"
+ status:
+ standalone:
+ title: "Samostatná"
+ description: "Samostatné události se nejde účastnit."
+ public:
+ title: "Veřejná"
+ description: "Veřejné události se může účastnit kdokoliv."
+ private:
+ title: "Soukromá"
+ description: "Soukromé události se můžou účastnit jen pozvaní uživatelé."
+ builder_modal:
+ custom_fields:
+ label: "Vlastní pole"
+ placeholder: "Volitelné"
+ description: "Povolená vlastní pole jsou definována v nastavení webu. Vlastní pole se používají k přenosu dat do jiných pluginů."
+ create_event_title: "Vytvořit událost"
+ update_event_title: "Upravit událost"
+ confirm_delete: "Určitě chcete tuto událost smazat?"
+ confirm_close: "Opravdu chcete uzavřít tuto událost?"
+ confirm_open: "Opravdu chcete otevřít tuto událost?"
+ create: "Vytvořit"
+ update: "Uložit"
+ attach: "Vytvořit událost"
+ add_reminder: "Přidat připomínku"
+ show_local_time:
+ label: "Zobrazit místní čas"
+ description: "Data a časy se zobrazí pomocí: %{timezone}. Použijte to pro události na daném místě, aby časy odpovídaly časovému pásmu, kde se událost koná."
+ timezone:
+ label: Časové pásmo
+ remove_timezone: Žádné časové pásmo (UTC)
+ reminders:
+ label: "Připomínky"
+ types:
+ bump_topic: "automaticky pošťouchnout téma"
+ notification: "upozornit účastníky"
+ units:
+ minutes: "minut"
+ hours: "hodin"
+ days: "dnů"
+ weeks: "týdnů"
+ periods:
+ before: "před"
+ after: "po"
+ recurrence_until:
+ label: "Do (včetně)"
+ recurrence:
+ label: "Opakování"
+ none: "Bez opakování"
+ every_day: "každý den"
+ every_month: "Každý měsíc v tento den v týdnu"
+ every_weekday: "Každý den v týdnu"
+ every_week: "Každý týden v tento den"
+ every_two_weeks: "Každé dva týdny v tento den"
+ every_four_weeks: "Každé čtyři týdny v tento den"
+ minimal:
+ label: "Mini-událost"
+ checkbox_label: "Skrýt tlačítka Zúčastním/Nuzúčastním a stav pozvaných osob"
+ allow_chat:
+ label: "Integrace chatu"
+ checkbox_label: "Vytvoří a spravuje speciální kanál chatu pro tuto událost"
+ url:
+ label: "URL"
+ placeholder: "Volitelné"
+ location:
+ label: "Poloha"
+ description:
+ label: "Popis"
+ name:
+ label: "Název události"
+ placeholder: "Volitelný, ve výchozím stavu název tématu"
+ invitees:
+ label: "Pozvané skupiny"
+ status:
+ label: "Stav"
+ invite_user_or_group:
+ title: "Oznámit uživatelům nebo skupinám"
+ invite: "Odeslat"
diff --git a/plugins/discourse-calendar/config/locales/client.da.yml b/plugins/discourse-calendar/config/locales/client.da.yml
new file mode 100644
index 00000000000..7f9390b1ed6
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/client.da.yml
@@ -0,0 +1,411 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+da:
+ admin_js:
+ admin:
+ site_settings:
+ categories:
+ discourse_post_event: "Discourse Begivenhed"
+ discourse_calendar: "Discourse Kalender"
+ js:
+ discourse_automation:
+ triggerables:
+ event_started:
+ fields:
+ topic_id:
+ label: Emne ID
+ discourse_calendar:
+ invite_user_notification: "%{username} inviterede dig til: %{description}"
+ on_holiday: "På Ferie"
+ disable_holiday: "Deaktiver"
+ enable_holiday: "Aktiver"
+ holiday: "Ferie"
+ date: "Dato"
+ add_to_calendar: "Tilføj til Google Kalender"
+ region:
+ title: "Område"
+ none: "Ingen"
+ use_current_region: "Brug det aktuelle område"
+ names:
+ ar: "Argentina"
+ at: "Østrig"
+ au_act: "Australien (au_act)"
+ au_nsw: "Australien (au_nsw)"
+ au_nt: "Australien (au_nt)"
+ au_qld_brisbane: "Australien (au_qld_brisbane)"
+ au_qld_cairns: "Australien (au_qld_cairns)"
+ au_qld: "Australien (au_qld)"
+ au_sa: "Australien (au_sa)"
+ au_tas_north: "Australien (au_tas_north)"
+ au_tas_south: "Australien (au_tas_south)"
+ au_tas: "Australia (au_tas)"
+ au_vic_melbourne: "Australien (au_vic_melbourne)"
+ au_vic: "Australien (au_vic)"
+ au_wa: "Australien (au_wa)"
+ au: "Australien"
+ be_fr: "Belgien (be_fr)"
+ be_nl: "Belgien (be_nl)"
+ bg_bg: "Bulgarien (bg_bg)"
+ bg_en: "Bulgarien (bg_en)"
+ br: "Brasilien"
+ ca_ab: "Canada (ca_ab)"
+ ca_bc: "Canada (ca_bc)"
+ ca_mb: "Canada (ca_mb)"
+ ca_nb: "Canada (ca_nb)"
+ ca_nl: "Canada (ca_nl)"
+ ca_ns: "Canada (ca_ns)"
+ ca_nt: "Canada (ca_nt)"
+ ca_nu: "Canada (ca_nu)"
+ ca_on: "Canada (ca_on)"
+ ca_pe: "Canada (ca_pe)"
+ ca_qc: "Canada (ca_qc)"
+ ca_sk: "Canada (ca_sk)"
+ ca_yt: "Canada (ca_yt)"
+ ca: "Canada"
+ ch_ag: "Schweiz (ch_ag)"
+ ch_ai: "Schweiz (ch_ai)"
+ ch_ar: "Schweiz (ch_ar)"
+ ch_be: "Schweiz (ch_be)"
+ ch_bl: "Schweiz (ch_bl)"
+ ch_bs: "Schweiz (ch_bs)"
+ ch_fr: "Schweiz (ch_fr)"
+ ch_ge: "Schweiz (ch_ge)"
+ ch_gl: "Schweiz (ch_gl)"
+ ch_gr: "Schweiz (ch_gr)"
+ ch_ju: "Schweiz (ch_ju)"
+ ch_lu: "Schweiz (ch_lu)"
+ ch_ne: "Schweiz (ch_ne)"
+ ch_nw: "Schweiz (ch_nw)"
+ ch_ow: "Schweiz (ch_ow)"
+ ch_sg: "Schweiz (ch_sg)"
+ ch_sh: "Schweiz (ch_sh)"
+ ch_so: "Schweiz (ch_so)"
+ ch_sz: "Schweiz (ch_sz)"
+ ch_tg: "Schweiz (ch_tg)"
+ ch_ti: "Schweiz (ch_ti)"
+ ch_ur: "Schweiz (ch_ur)"
+ ch_vd: "Schweiz (ch_vd)"
+ ch_vs: "Schweiz (ch_vs)"
+ ch_zg: "Schweiz (ch_zg)"
+ ch_zh: "Schweiz (ch_zh)"
+ ch: "Schweiz"
+ cl: "Chile"
+ co: "Kolumbien"
+ cr: "Costa Rica"
+ cz: "Tjekkiet"
+ de_bb: "Tyskland (de_bb)"
+ de_be: "Tyskland (de_be)"
+ de_bw: "Tyskland (de_bw)"
+ de_by_augsburg: "Tyskland (de_by_augsburg)"
+ de_by_cath: "Tyskland (de_by_cath)"
+ de_by: "Tyskland (de_by)"
+ de_hb: "Tyskland (de_hb)"
+ de_he: "Tyskland (de_he)"
+ de_hh: "Tyskland (de_hh)"
+ de_mv: "Tyskland (de_mv)"
+ de_ni: "Tyskland (de_ni)"
+ de_nw: "Tyskland (de_nw)"
+ de_rp: "Tyskland (de_rp)"
+ de_sh: "Tyskland (de_sh)"
+ de_sl: "Tyskland (de_sl)"
+ de_sn_sorbian: "Tyskland (de_sn_sorbian)"
+ de_sn: "Tyskland (de_sn)"
+ de_st: "Tyskland (de_st)"
+ de_th_cath: "Tyskland (de_th_cath)"
+ de_th: "Tyskland (de_th)"
+ de: "Tyskland"
+ dk: "Danmark"
+ ee: "Estland"
+ el: "Grækenland"
+ es_an: "Spanien (es_ar)"
+ es_ar: "Spanien (es_ar)"
+ es_ce: "Spanien (es_ce)"
+ es_cl: "Spanien (es_cl)"
+ es_cm: "Spain (es_cm)"
+ es_cn: "Spanien (es_cn)"
+ es_ct: "Spanien (es_ct)"
+ es_ex: "Spanien (es_ex)"
+ es_ga: "Spanien (es_ga)"
+ es_ib: "Spanien (es_ib)"
+ es_lo: "Spanien (es_lo)"
+ es_m: "Spanien (es_m)"
+ es_mu: "Spanien (es_mu)"
+ es_na: "Spanien (es_na)"
+ es_o: "Spanien (es_o)"
+ es_pv: "Spanien (es_pv)"
+ es_v: "Spanien (es_v)"
+ es_vc: "Spanien (es_vc)"
+ es: "Spanien"
+ fi: "Finland"
+ fr_a: "Frankrig (fr_a)"
+ fr_m: "Frankrig (fr_m)"
+ fr: "Frankrig"
+ gb_con: "Storbritannien (gb_con)"
+ gb_eaw: "Storbritannien (gb_eaw)"
+ gb_eng: "Storbritannien (gb_eng)"
+ gb_gsy: "Storbritannien (gb_gsy)"
+ gb_iom: "Storbritannien (gb_iom)"
+ gb_jsy: "Storbritannien (gb_jsy)"
+ gb_nir: "Storbritannien (gb_nir)"
+ gb_sct: "Storbritannien (gb_sct)"
+ gb_wls: "Storbritannien (gb_wls)"
+ gb: "Storbritanien"
+ ge: "Georgien"
+ gg: "Guernsey"
+ hk: "Hongkong"
+ hr: "Kroatien"
+ hu: "Ungarn"
+ ie: "Irland"
+ im: "Isle of Man"
+ in: "Indien"
+ is: "Island"
+ it_bl: "Italien (it_bl)"
+ it_fi: "Italien (it_fi)"
+ it_ge: "Italien (it_ge)"
+ it_pd: "Italien (it_pd)"
+ it_rm: "Italien (it_rm)"
+ it_ro: "Italien (it_ro)"
+ it_to: "Italien (it_to)"
+ it_tv: "Italien (it_tv)"
+ it_ve: "Italien (it_ve)"
+ it_vi: "Italien (it_vi)"
+ it_vr: "Italien (it_vr)"
+ it: "Italien"
+ je: "Jersey"
+ jp: "Japan"
+ kr: "Korea (Republikken )"
+ li: "Liechtenstein"
+ lt: "Litauen"
+ lu: "Luxembourg"
+ lv: "Letland"
+ ma: "Marokko"
+ mt_en: "Malta (mt_da)"
+ mt_mt: "Malta (mt_mt)"
+ mx_pue: "Mexico (mx_pue)"
+ mx: "Mexico"
+ my: "Malaysia"
+ ng: "Nigeria"
+ nl: "Holland"
+ "no": "Norge"
+ nz_ak: "New Zealand (nz_ak)"
+ nz_ca: "New Zealand (nz_ca)"
+ nz_ch: "New Zealand (nz_ch)"
+ nz_hb: "New Zealand (nz_hb)"
+ nz_mb: "New Zealand (nz_mb)"
+ nz_ne: "New Zealand (nz_ne)"
+ nz_nl: "New Zealand (nz_nl)"
+ nz_ot: "New Zealand (nz_ot)"
+ nz_sc: "New Zealand (nz_sc)"
+ nz_sl: "New Zealand (nz_sl)"
+ nz_ta: "New Zealand (nz_ta)"
+ nz_we: "New Zealand (nz_we)"
+ nz_wl: "New Zealand (nz_wl)"
+ nz: "New Zealand"
+ pe: "Peru"
+ ph: "Filippinerne"
+ pl: "Polen"
+ pt_li: "Portugal (pt_li)"
+ pt_po: "Portugal (pt_po)"
+ pt: "Portugal"
+ ro: "Rumænien"
+ rs_cyrl: "Serbien (rs_cyrl)"
+ rs_la: "Serbien (rs_la)"
+ ru: "Den Russiske Føderation"
+ se: "Sverige"
+ sa: "Saudi Arabien"
+ sg: "Singapore"
+ si: "Slovenien"
+ sk: "Slovakiet"
+ th: "Thailand"
+ tn: "Tunesien"
+ tr: "Tyrkiet"
+ ua: "Ukraine"
+ us_ak: "USA (us_ak)"
+ us_al: "USA (us_al)"
+ us_ar: "USA (us_ar)"
+ us_az: "USA (us_az)"
+ us_ca: "USA (us_ca)"
+ us_co: "USA (us_co)"
+ us_ct: "USA (us_ct)"
+ us_dc: "USA (us_dc)"
+ us_de: "USA (us_de)"
+ us_fl: "USA (us_fl)"
+ us_ga: "USA (us_ga)"
+ us_gu: "USA (us_gu)"
+ us_hi: "USA (us_hi)"
+ us_ia: "USA (us_ia)"
+ us_id: "USA (us_id)"
+ us_il: "USA (us_il)"
+ us_in: "USA (us_in)"
+ us_ks: "USA (us_ks)"
+ us_ky: "USA (us_ky)"
+ us_la: "USA (us_la)"
+ us_ma: "USA (us_ma)"
+ us_md: "USA (us_md)"
+ us_me: "USA (us_me)"
+ us_mi: "USA (us_mi)"
+ us_mn: "USA (us_mn)"
+ us_mo: "USA (us_mo)"
+ us_ms: "USA (us_ms)"
+ us_mt: "USA (us_mt)"
+ us_nc: "USA (us_nc)"
+ us_nd: "USA (us_nd)"
+ us_ne: "USA (us_ne)"
+ us_nh: "USA (us_nh)"
+ us_nj: "USA (us_nj)"
+ us_nm: "USA (us_nm)"
+ us_nv: "USA (us_nv)"
+ us_ny: "USA (us_ny)"
+ us_oh: "USA (us_oh)"
+ us_ok: "USA (us_ok)"
+ us_or: "USA (us_or)"
+ us_pa: "USA (us_pa)"
+ us_pr: "USA (us_pr)"
+ us_ri: "USA (us_ri)"
+ us_sc: "USA (us_sc)"
+ us_sd: "USA (us_sd)"
+ us_tn: "USA (us_tn)"
+ us_tx: "USA (us_tx)"
+ us_ut: "USA (us_ut)"
+ us_va: "USA (us_va)"
+ us_vi: "USA (us_vi)"
+ us_vt: "USA (us_vt)"
+ us_wa: "USA (us_wa)"
+ us_wi: "USA (us_wi)"
+ us_wv: "USA (us_wv)"
+ us_wy: "USA (us_wy)"
+ us: "USA"
+ ve: "Venezuela"
+ vi: "Jomfruøerne (USA)"
+ za: "Sydafrika"
+ toolbar_button:
+ today: "I dag"
+ month: "Måned"
+ week: "Uge"
+ day: "Dag"
+ group_timezones:
+ search: "Søg..."
+ group_availability: "%{group} tilgængelighed"
+ discourse_post_event:
+ notifications:
+ invite_user_notification: "%{username} %{description}"
+ invite_user_predefined_attendance_notification_html: "%{username} har automatisk indstillet din deltagelse og inviteret dig til %{description}"
+ before_event_reminder_html: "En begivenhed er ved at starte %{description}"
+ after_event_reminder_html: "En begivenhed er afsluttet %{description}"
+ ongoing_event_reminder_html: "En begivenhed er i gang %{description}"
+ edit_reason: "Begivenhed opdateret"
+ topic_title:
+ starts_at: "Begivenheden starter: %{date}"
+ ended_at: "Begivenhed sluttede: %{date}"
+ ends_in_duration: "Slutter %{duration}"
+ show_all: "Vis alle"
+ participants:
+ one: "%{count} bruger deltog."
+ other: "%{count} brugere deltog."
+ invite: "Underret bruger"
+ add_to_calendar: "Føj til kalender"
+ send_pm_to_creator: "Send PM til %{username}"
+ edit_event: "Redigér begivenhed"
+ export_event: "Eksportér begivenhed"
+ created_by: "Oprettet af"
+ bulk_invite: "Masseinvitation"
+ close_event: "Luk begivenhed"
+ invitees_modal:
+ title_participated: "Liste over brugere, der deltog"
+ filter_placeholder: "Filtrer brugere"
+ bulk_invite_modal:
+ confirm: "bekræft"
+ text: "Upload CSV-fil"
+ title: "Masseinvitation"
+ success: "Filen uploadet korrekt, du vil blive underrettet via besked, når processen er færdig."
+ error: "Beklager, filen skal være i CSV-format."
+ confirmation_message: "Du er ved at underrette alle i den uploadede fil."
+ description_public: "Offentlige begivenheder accepterer kun brugernavne til masseinvitationer."
+ description_private: "Private begivenheder accepterer kun gruppenavne til masseinvitationer."
+ download_sample_csv: "Download et CSV-prøveeksempel"
+ send_bulk_invites: "Send Invitationer"
+ group_selector_placeholder: "Vælg en gruppe..."
+ user_selector_placeholder: "Vælg bruger..."
+ inline_title: "Indlejret masseinvitation"
+ csv_title: "CSV-masseinvitation"
+ upcoming_events:
+ title: "Kommende begivenheder"
+ creator: "Skaber"
+ status: "Status"
+ starts_at: "Begynder"
+ upcoming_events_list:
+ title: "Kommende begivenheder"
+ preview:
+ more_than_one_event: "Du kan ikke have mere end én begivenhed."
+ models:
+ invitee:
+ status:
+ unknown: "Ikke interesseret"
+ going: "Deltager"
+ not_going: "Deltager Ikke"
+ interested: "Interesseret"
+ event:
+ expired: "Udløbet"
+ closed: "Lukket"
+ status:
+ standalone:
+ title: "Enkeltstående"
+ description: "En enkeltstående begivenhed kan ikke tilmeldes"
+ public:
+ title: "Offentlig"
+ description: "En offentlig begivenhed, alle kan tilmeldes."
+ private:
+ title: "Privat"
+ description: "En privat begivenhed kan kun tilmeldes af inviterede brugere."
+ builder_modal:
+ custom_fields:
+ label: "Brugerdefinerede Felter"
+ placeholder: "Valgfri"
+ description: "Tilladte brugerdefinerede felter er defineret i webstedsindstillinger. Brugerdefinerede felter bruges til at overføre data til andre udvidelsesmoduler."
+ create_event_title: "Opret begivenhed"
+ update_event_title: "Rediger begivenhed"
+ confirm_delete: "Er du sikker på, at du vil slette denne begivenhed?"
+ confirm_close: "Er du sikker på, at du vil lukke denne begivenhed?"
+ create: "Opret"
+ update: "Gem"
+ attach: "Opret begivenhed"
+ add_reminder: "Tilføj påmindelse"
+ timezone:
+ label: Tidszone
+ reminders:
+ label: "Påmindelser"
+ units:
+ minutes: "minutter"
+ hours: "timer"
+ days: "dage"
+ periods:
+ before: "før"
+ after: "efter"
+ recurrence:
+ label: "Gentagelse"
+ none: "Ingen gentagelse"
+ every_day: "Hver dag"
+ every_month: "Hver måned på denne ugedag"
+ every_weekday: "Hver ugedag"
+ every_week: "Hver uge på denne ugedag"
+ url:
+ label: "URL"
+ placeholder: "Valgfri"
+ location:
+ label: "Sted"
+ description:
+ label: "Beskrivelse"
+ name:
+ label: "Navn på begivenhed"
+ placeholder: "Valgfri, standard er emnetitel"
+ invitees:
+ label: "Inviterede grupper"
+ status:
+ label: "Status"
+ invite_user_or_group:
+ title: "Underret bruger(e) eller gruppe(r)"
+ invite: "Send"
diff --git a/plugins/discourse-calendar/config/locales/client.de.yml b/plugins/discourse-calendar/config/locales/client.de.yml
new file mode 100644
index 00000000000..ade117470f1
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/client.de.yml
@@ -0,0 +1,496 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+de:
+ admin_js:
+ admin:
+ calendar: "Kalender"
+ site_settings:
+ categories:
+ discourse_post_event: "Discourse-Ereignis"
+ discourse_calendar: "Discourse-Kalender"
+ js:
+ notifications:
+ titles:
+ event_reminder: "Ereigniserinnerung"
+ event_invitation: "Ereigniseinladung"
+ popup:
+ event_reminder: Ereigniserinnerung
+ discourse_automation:
+ triggerables:
+ event_started:
+ title: Ereignis hat begonnen
+ fields:
+ topic_id:
+ label: Themen-ID
+ discourse_calendar:
+ invite_user_notification: "%{username} lädt dich ein zu: %{description}"
+ on_holiday: "Im Urlaub"
+ disable_holiday: "Deaktivieren"
+ enable_holiday: "Aktivieren"
+ holiday: "Urlaub"
+ holidays:
+ header_title: "Feiertage"
+ pick_region_description: "Wähle eine Region aus, um die Feiertage für diese Region zu sehen."
+ disabled_holidays_description: "Deaktivierte Feiertage werden aus dem Team-Feiertagskalender ausgeschlossen."
+ date: "Datum"
+ add_to_calendar: "Zu Google Kalender hinzufügen"
+ toggle_timezone_offset_title: "Zeitzonenoffset umschalten"
+ local_time: "Ortszeit"
+ region:
+ title: "Region"
+ none: "Keine"
+ use_current_region: "Aktuelle Region verwenden"
+ names:
+ ae: "Vereinigte Arabische Emirate"
+ ar: "Argentinien"
+ at: "Österreich"
+ au_act: "Australien (au_act)"
+ au_nsw: "Australien (au_nsw)"
+ au_nt: "Australien (au_nt)"
+ au_qld_brisbane: "Australien (au_qld_brisbane)"
+ au_qld_cairns: "Australien (au_qld_cairns)"
+ au_qld: "Australien (au_qld)"
+ au_sa: "Australien (au_sa)"
+ au_tas_north: "Australien (au_tas_north)"
+ au_tas_south: "Australien (au_tas_south)"
+ au_tas: "Australien (au_tas)"
+ au_vic_melbourne: "Australien (au_vic_melbourne)"
+ au_vic: "Australien (au_vic)"
+ au_wa: "Australien (au_wa)"
+ au: "Australien"
+ be_fr: "Belgien (be_fr)"
+ be_nl: "Belgien (be_nl)"
+ bg_bg: "Bulgarien (bg_bg)"
+ bg_en: "Bulgarien (bg_en)"
+ br: "Brasilien"
+ br_sp: "Brasilien (br_sp)"
+ br_spcapital: "Brasilien (br_scapital)"
+ ca_ab: "Kanada (ca_ab)"
+ ca_bc: "Kanada (ca_bc)"
+ ca_mb: "Kanada (ca_mb)"
+ ca_nb: "Kanada (ca_nb)"
+ ca_nl: "Kanada (ca_nl)"
+ ca_ns: "Kanada (ca_ns)"
+ ca_nt: "Kanada (ca_nt)"
+ ca_nu: "Kanada (ca_nu)"
+ ca_on: "Kanada (ca_on)"
+ ca_pe: "Kanada (ca_pe)"
+ ca_qc: "Kanada (ca_qc)"
+ ca_sk: "Kanada (ca_sk)"
+ ca_yt: "Kanada (ca_yt)"
+ ca: "Kanada"
+ ch_ag: "Schweiz (ch_ag)"
+ ch_ai: "Schweiz (ch_ai)"
+ ch_ar: "Schweiz (ch_ar)"
+ ch_be: "Schweiz (ch_be)"
+ ch_bl: "Schweiz (ch_bl)"
+ ch_bs: "Schweiz (ch_bs)"
+ ch_fr: "Schweiz (ch_fr)"
+ ch_ge: "Schweiz (ch_ge)"
+ ch_gl: "Schweiz (ch_gl)"
+ ch_gr: "Schweiz (ch_gr)"
+ ch_ju: "Schweiz (ch_ju)"
+ ch_lu: "Schweiz (ch_lu)"
+ ch_ne: "Schweiz (ch_ne)"
+ ch_nw: "Schweiz (ch_nw)"
+ ch_ow: "Schweiz (ch_ow)"
+ ch_sg: "Schweiz (ch_sg)"
+ ch_sh: "Schweiz (ch_sh)"
+ ch_so: "Schweiz (ch_so)"
+ ch_sz: "Schweiz (ch_sz)"
+ ch_tg: "Schweiz (ch_tg)"
+ ch_ti: "Schweiz (ch_ti)"
+ ch_ur: "Schweiz (ch_ur)"
+ ch_vd: "Schweiz (ch_vd)"
+ ch_vs: "Schweiz (ch_vs)"
+ ch_zg: "Schweiz (ch_zg)"
+ ch_zh: "Schweiz (ch_zh)"
+ ch: "Schweiz"
+ cl: "Chile"
+ co: "Kolumbien"
+ cr: "Costa Rica"
+ cz: "Tschechien"
+ de_bb: "Deutschland (de_bb)"
+ de_be: "Deutschland (de_be)"
+ de_bw: "Deutschland (de_bw)"
+ de_by_augsburg: "Deutschland (de_by_augsburg)"
+ de_by_cath: "Deutschland (de_by_cath)"
+ de_by: "Deutschland (de_by)"
+ de_hb: "Deutschland (de_hb)"
+ de_he: "Deutschland (de_he)"
+ de_hh: "Deutschland (de_hh)"
+ de_mv: "Deutschland (de_mv)"
+ de_ni: "Deutschland (de_ni)"
+ de_nw: "Deutschland (de_nw)"
+ de_rp: "Deutschland (de_rp)"
+ de_sh: "Deutschland (de_sh)"
+ de_sl: "Deutschland (de_sl)"
+ de_sn_sorbian: "Deutschland (de_sn_sorbian)"
+ de_sn: "Deutschland (de_sn)"
+ de_st: "Deutschland (de_st)"
+ de_th_cath: "Deutschland (de_th_cath)"
+ de_th: "Deutschland (de_th)"
+ de: "Deutschland"
+ dk: "Dänemark"
+ ee: "Estland"
+ el: "Griechenland"
+ es_an: "Spanien (es_an)"
+ es_ar: "Spanien (es_ar)"
+ es_ce: "Spanien (es_ce)"
+ es_cl: "Spanien (es_cl)"
+ es_cm: "Spanien (es_cm)"
+ es_cn: "Spanien (es_cn)"
+ es_ct: "Spanien (es_ct)"
+ es_ex: "Spanien (es_ex)"
+ es_ga: "Spanien (es_ga)"
+ es_ib: "Spanien (es_ib)"
+ es_lo: "Spanien (es_lo)"
+ es_m: "Spanien (es_m)"
+ es_mu: "Spanien (es_mu)"
+ es_na: "Spanien (es_na)"
+ es_o: "Spanien (es_o)"
+ es_pv: "Spanien (es_pv)"
+ es_v: "Spanien (es_v)"
+ es_vc: "Spanien (es_vc)"
+ es: "Spanien"
+ fi: "Finnland"
+ fr_a: "Frankreich (fr_a)"
+ fr_m: "Frankreich (fr_m)"
+ fr: "Frankreich"
+ gb_con: "Vereinigtes Königreich (gb_con)"
+ gb_eaw: "Vereinigtes Königreich (gb_eaw)"
+ gb_eng: "Vereinigtes Königreich (gb_eng)"
+ gb_gsy: "Vereinigtes Königreich (gb_gsy)"
+ gb_iom: "Vereinigtes Königreich (gb_iom)"
+ gb_jsy: "Vereinigtes Königreich (gb_jsy)"
+ gb_nir: "Vereinigtes Königreich (gb_nir)"
+ gb_sct: "Vereinigtes Königreich (gb_sct)"
+ gb_wls: "Vereinigtes Königreich (gb_wls)"
+ gb: "Vereinigtes Königreich"
+ ge: "Georgia"
+ gg: "Guernsey"
+ gh: "Ghana"
+ hk: "Hongkong"
+ hr: "Kroatien"
+ hu: "Ungarn"
+ id: "Indonesien"
+ ie: "Irland"
+ im: "Isle of Man"
+ in: "Indien"
+ in_gj: "Indien (in_gj)"
+ in_mh: "Indien (in_mh)"
+ in_rj: "Indien (in_rj)"
+ in_tn: "Indien (in_tn)"
+ in_ka: "Indien (in_ka)"
+ is: "Island"
+ it_bl: "Italien (it_bl)"
+ it_fi: "Italien (it_fi)"
+ it_ge: "Italien (it_ge)"
+ it_pd: "Italien (it_pd)"
+ it_rm: "Italien (it_rm)"
+ it_ro: "Italien (it_ro)"
+ it_to: "Italien (it_to)"
+ it_tv: "Italien (it_tv)"
+ it_ve: "Italien (it_ve)"
+ it_vi: "Italien (it_vi)"
+ it_vr: "Italien (it_vr)"
+ it: "Italien"
+ je: "Jersey"
+ jp: "Japan"
+ ke: "Kenia"
+ kr: "Korea (Republik)"
+ kz: "Kasachstan (Republik)"
+ li: "Liechtenstein"
+ lt: "Litauen"
+ lu: "Luxemburg"
+ lv: "Lettland"
+ ma: "Marokko"
+ mt_en: "Malta (mt_en)"
+ mt_mt: "Malta (mt_mt)"
+ mx_pue: "Mexiko (mx_pue)"
+ mx: "Mexiko"
+ my: "Malaysia"
+ ng: "Nigeria"
+ nl: "Niederlande"
+ "no": "Norwegen"
+ nz_ak: "Neuseeland (nz_ak)"
+ nz_ca: "Neuseeland (nz_ca)"
+ nz_ch: "Neuseeland (nz_ch)"
+ nz_hb: "Neuseeland (nz_hb)"
+ nz_mb: "Neuseeland (nz_mb)"
+ nz_ne: "Neuseeland (nz_ne)"
+ nz_nl: "Neuseeland (nz_nl)"
+ nz_ot: "Neuseeland (nz_ot)"
+ nz_sc: "Neuseeland (nz_sc)"
+ nz_sl: "Neuseeland (nz_sl)"
+ nz_ta: "Neuseeland (nz_ta)"
+ nz_we: "Neuseeland (nz_we)"
+ nz_wl: "Neuseeland (nz_wl)"
+ nz: "Neuseeland"
+ pe: "Peru"
+ ph: "Philippinen"
+ pl: "Polen"
+ pt_li: "Portugal (pt_li)"
+ pt_po: "Portugal (pt_po)"
+ pt: "Portugal"
+ ro: "Rumänien"
+ rs_cyrl: "Serbien (rs_cyrl)"
+ rs_la: "Serbien (rs_la)"
+ ru: "Russland"
+ se: "Schweden"
+ sa: "Saudi-Arabien"
+ sg: "Singapur"
+ si: "Slowenien"
+ sk: "Slowakei"
+ th: "Thailand"
+ tn: "Tunesien"
+ tr: "Türkei"
+ ua: "Ukraine"
+ us_ak: "Vereinigte Staaten (us_ak)"
+ us_al: "Vereinigte Staaten (us_al)"
+ us_ar: "Vereinigte Staaten (us_ar)"
+ us_az: "Vereinigte Staaten (us_az)"
+ us_ca: "Vereinigte Staaten (us_ca)"
+ us_co: "Vereinigte Staaten (us_co)"
+ us_ct: "Vereinigte Staaten (us_ct)"
+ us_dc: "Vereinigte Staaten (us_dc)"
+ us_de: "Vereinigte Staaten (us_de)"
+ us_fl: "Vereinigte Staaten (us_fl)"
+ us_ga: "Vereinigte Staaten (us_ga)"
+ us_gu: "Vereinigte Staaten (us_gu)"
+ us_hi: "Vereinigte Staaten (us_hi)"
+ us_ia: "Vereinigte Staaten (us_ia)"
+ us_id: "Vereinigte Staaten (us_id)"
+ us_il: "Vereinigte Staaten (us_il)"
+ us_in: "Vereinigte Staaten (us_in)"
+ us_ks: "Vereinigte Staaten (us_ks)"
+ us_ky: "Vereinigte Staaten (us_ky)"
+ us_la: "Vereinigte Staaten (us_la)"
+ us_ma: "Vereinigte Staaten (us_ma)"
+ us_md: "Vereinigte Staaten (us_md)"
+ us_me: "Vereinigte Staaten (us_me)"
+ us_mi: "Vereinigte Staaten (us_mi)"
+ us_mn: "Vereinigte Staaten (us_mn)"
+ us_mo: "Vereinigte Staaten (us_mo)"
+ us_ms: "Vereinigte Staaten (us_ms)"
+ us_mt: "Vereinigte Staaten (us_mt)"
+ us_nc: "Vereinigte Staaten (us_nc)"
+ us_nd: "Vereinigte Staaten (us_nd)"
+ us_ne: "Vereinigte Staaten (us_ne)"
+ us_nh: "Vereinigte Staaten (us_nh)"
+ us_nj: "Vereinigte Staaten (us_nj)"
+ us_nm: "Vereinigte Staaten (us_nm)"
+ us_nv: "Vereinigte Staaten (us_nv)"
+ us_ny: "Vereinigte Staaten (us_ny)"
+ us_oh: "Vereinigte Staaten (us_oh)"
+ us_ok: "Vereinigte Staaten (us_ok)"
+ us_or: "Vereinigte Staaten (us_or)"
+ us_pa: "Vereinigte Staaten (us_pa)"
+ us_pr: "Vereinigte Staaten (us_pr)"
+ us_ri: "Vereinigte Staaten (us_ri)"
+ us_sc: "Vereinigte Staaten (us_sc)"
+ us_sd: "Vereinigte Staaten (us_sd)"
+ us_tn: "Vereinigte Staaten (us_tn)"
+ us_tx: "Vereinigte Staaten (us_tx)"
+ us_ut: "Vereinigte Staaten (us_ut)"
+ us_va: "Vereinigte Staaten (us_va)"
+ us_vi: "Vereinigte Staaten (us_vi)"
+ us_vt: "Vereinigte Staaten (us_vt)"
+ us_wa: "Vereinigte Staaten (us_wa)"
+ us_wi: "Vereinigte Staaten (us_wi)"
+ us_wv: "Vereinigte Staaten (us_wv)"
+ us_wy: "Vereinigte Staaten (us_wy)"
+ us: "Vereinigte Staaten"
+ ve: "Venezuela"
+ vi: "Jungferninseln (USA)"
+ za: "Südafrika"
+ zw: "Simbabwe"
+ toolbar_button:
+ today: "Heute"
+ month: "Monat"
+ week: "Woche"
+ day: "Tag"
+ list: "Auflisten"
+ group_timezones:
+ search: "Suche …"
+ group_availability: "%{group} Verfügbarkeit"
+ discourse_post_event:
+ notifications:
+ before_event_reminder: "Ein Ereignis beginnt in Kürze"
+ after_event_reminder: "Ein Ereignis wurde beendet"
+ ongoing_event_reminder: "Ein Ereignis läuft"
+ invite_user_notification: "%{username} %{description}"
+ invite_user_predefined_attendance_notification_html: "%{username} hat automatisch deine Teilnahme festgelegt und dich zu %{description} eingeladen"
+ before_event_reminder_html: "Ein Ereignis beginnt in Kürze %{description}"
+ after_event_reminder_html: "Ein Ereignis wurde beendet %{description}"
+ ongoing_event_reminder_html: "Ein Ereignis läuft %{description}"
+ edit_reason: "Ereignis aktualisiert"
+ edit_reason_closed: "Ereignis geschlossen"
+ edit_reason_opened: "Ereignis geöffnet"
+ topic_title:
+ starts_at: "Ereignisbeginn: %{date}"
+ ended_at: "Ereignisende: %{date}"
+ ends_in_duration: "Endet %{duration}"
+ show_all: "Alle anzeigen"
+ show_participants: "Teilnehmer anzeigen"
+ participants:
+ one: "%{count} Benutzer nahm teil."
+ other: "%{count} Benutzer nahmen teil."
+ invite: "Benutzer benachrichtigen"
+ add_to_calendar: "Zum Kalender hinzufügen"
+ send_pm_to_creator: "PN senden an %{username}"
+ leave: "Ereignis verlassen"
+ edit_event: "Ereignis bearbeiten"
+ export_event: "Ereignis exportieren"
+ created_by: "Erstellt von"
+ bulk_invite: "Masseneinladung"
+ close_event: "Ereignis beenden"
+ open_event: "Ereignis öffnen"
+ invitees_modal:
+ title_invited: "Teilnahme am Ereignis"
+ title_participated: "Liste von Benutzern, die teilgenommen haben"
+ filter_placeholder: "Benutzer filtern"
+ remove_invitee: "Eingeladene Person aus der Liste entfernen"
+ add_invitee: "Eingeladene Person zur Liste hinzufügen"
+ bulk_invite_modal:
+ confirm: "bestätigen"
+ text: "CSV-Datei hochladen"
+ title: "Massen-Einladung"
+ success: "Die Datei wurde erfolgreich hochgeladen. Du wirst per Nachricht benachrichtigt, wenn der Vorgang abgeschlossen ist."
+ error: "Die Datei sollte im CSV-Format vorliegen."
+ confirmation_message: "Du bist dabei, alle in der hochgeladenen Datei zu benachrichtigen."
+ description_public: "Öffentliche Ereignisse akzeptieren nur Benutzernamen für Massen-Einladungen."
+ description_private: "Private Ereignisse akzeptieren nur Gruppennamen für Massen-Einladungen."
+ download_sample_csv: "Eine Beispiel-CSV-Datei herunterladen"
+ send_bulk_invites: "Einladungen senden"
+ group_selector_placeholder: "Gruppe auswählen …"
+ user_selector_placeholder: "Benutzer auswählen …"
+ inline_title: "Inline-Masseneinladung"
+ csv_title: "CSV-Masseneinladung"
+ upcoming_events:
+ title: "Anstehende Ereignisse"
+ creator: "Ersteller"
+ status: "Status"
+ starts_at: "Beginnt um"
+ all_events: "Alle Ereignisse"
+ my_events: "Meine Ereignisse"
+ upcoming_events_list:
+ title: "Anstehende Ereignisse"
+ empty: "Keine anstehenden Ereignisse"
+ all_day: "Ganztägig"
+ error: "Ereignisse konnten nicht abgerufen werden"
+ try_again: "Erneut versuchen"
+ view_all: "Alle ansehen"
+ category:
+ sort_topics_by_event_start_date: "Themen nach Startdatum des Ereignisses sortieren."
+ disable_topic_resorting: "Themensortierung deaktivieren."
+ settings_sections:
+ event_sorting: "Ereignissortierung"
+ preview:
+ more_than_one_event: "Du kannst nicht mehr als ein Ereignis haben."
+ models:
+ invitee:
+ no_users: "Keine Benutzer gefunden"
+ status:
+ unknown: "Nicht interessiert"
+ going: "Dabei"
+ not_going: "Nicht dabei"
+ interested: "Interessiert"
+ going_count:
+ one: "%{count} nimmt teil"
+ other: "%{count} nehmen teil"
+ not_going_count:
+ one: "%{count} nimmt nicht teil"
+ other: "%{count} nehmen nicht teil"
+ interested_count:
+ one: "%{count} interessiert"
+ other: "%{count} interessiert"
+ invited_count:
+ one: "%{count} Benutzer eingeladen"
+ other: "%{count} Benutzer eingeladen"
+ event:
+ expired: "Ausgelaufen"
+ closed: "Geschlossen"
+ status:
+ standalone:
+ title: "Einzeln"
+ description: "An einem Einzelereignis kann nicht teilgenommen werden."
+ public:
+ title: "Öffentlich"
+ description: "An einem öffentlichen Ereignis kann jeder teilnehmen."
+ private:
+ title: "Privat"
+ description: "An einem privaten Ereignis können nur eingeladene Benutzer teilnehmen."
+ builder_modal:
+ custom_fields:
+ label: "Benutzerdefinierte Felder"
+ placeholder: "Optional"
+ description: "Erlaubte benutzerdefinierte Felder werden in den Website-Einstellungen definiert. Benutzerdefinierte Felder werden verwendet, um Daten an andere Plug-ins zu übermitteln."
+ create_event_title: "Ereignis erstellen"
+ update_event_title: "Ereignis bearbeiten"
+ confirm_delete: "Bist du sicher, dass du dieses Ereignis löschen möchtest?"
+ confirm_close: "Bist du sicher, dass du dieses Ereignis beenden möchtest?"
+ confirm_open: "Bist du sicher, dass du dieses Ereignis öffnen möchtest?"
+ create: "Erstellen"
+ update: "Speichern"
+ attach: "Ereignis erstellen"
+ add_reminder: "Erinnerung hinzufügen"
+ show_local_time:
+ label: "Ortszeit anzeigen"
+ description: "%{timezone} wird für die Anzeige von Daten und Zeiten verwendet. Verwende dies für Veranstaltungen an einem Ort, damit die Zeiten die Zeitzone widerspiegeln, in der die Veranstaltung stattfindet."
+ timezone:
+ label: Zeitzone
+ remove_timezone: Keine Zeitzone (UTC)
+ reminders:
+ label: "Erinnerungen"
+ types:
+ bump_topic: "Thema automatisch nach oben verschieben"
+ notification: "Teilnehmer benachrichtigen"
+ units:
+ minutes: "Minuten"
+ hours: "Stunden"
+ days: "Tage"
+ weeks: "Wochen"
+ periods:
+ before: "vor"
+ after: "nach"
+ recurrence_until:
+ label: "Bis (einschließlich)"
+ recurrence:
+ label: "Wiederholung"
+ none: "Keine Wiederholung"
+ every_day: "Täglich"
+ every_month: "Jeden Monat an diesem Wochentag"
+ every_weekday: "Jeden Wochentag"
+ every_week: "Jede Woche an diesem Wochentag"
+ every_two_weeks: "Alle zwei Wochen an diesem Wochentag"
+ every_four_weeks: "Alle vier Wochen an diesem Wochentag"
+ minimal:
+ label: "Minimales Event"
+ checkbox_label: "Schaltflächen „Dabei“/„Nicht dabei“ und Status der eingeladenen Personen ausblenden"
+ allow_chat:
+ label: "Chat-Integration"
+ checkbox_label: "Erstelle und verwalte ereignisspezifische Chat-Kanäle"
+ url:
+ label: "URL"
+ placeholder: "Optional"
+ location:
+ label: "Ort"
+ placeholder: "Füge einen Ort, einen Link oder etwas anderes hinzu."
+ description:
+ label: "Beschreibung"
+ placeholder: "Erzähle den Leuten ein bisschen mehr über deine Veranstaltung. Neue Zeilen und Links werden unterstützt."
+ name:
+ label: "Name des Ereignisses"
+ placeholder: "Optional, standardmäßig Titel des Themas"
+ invitees:
+ label: "Eingeladene Gruppen"
+ status:
+ label: "Status"
+ invite_user_or_group:
+ title: "Benachrichtige Benutzer oder Gruppe(n)"
+ invite: "Senden"
diff --git a/plugins/discourse-calendar/config/locales/client.el.yml b/plugins/discourse-calendar/config/locales/client.el.yml
new file mode 100644
index 00000000000..3a6bc4135fa
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/client.el.yml
@@ -0,0 +1,216 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+el:
+ admin_js:
+ admin:
+ calendar: "Ημερολόγιο"
+ js:
+ discourse_automation:
+ triggerables:
+ event_started:
+ fields:
+ topic_id:
+ label: Topic ID
+ discourse_calendar:
+ disable_holiday: "Απενεργοποίηση"
+ enable_holiday: "Ενεργοποίηση"
+ date: "Ημερομηνία"
+ region:
+ title: "Περιοχή"
+ none: "Κανένα"
+ use_current_region: "Χρήση τρέχουσας περιοχής"
+ names:
+ ae: "Ηνωμένα Αραβικά Εμιράτα"
+ ar: "Αργεντινή"
+ at: "Αυστρία"
+ au_act: "Αυστραλία (au_act)"
+ au_nsw: "Αυστραλία (au_nsw)"
+ au_nt: "Αυστραλία (au_nt)"
+ au_qld_brisbane: "Αυστραλία (au_qld_brisbane)"
+ au_qld_cairns: "Αυστραλία (au_qld_cairns)"
+ au_qld: "Αυστραλία (au_qld)"
+ au_sa: "Australia (au_sa)"
+ au_tas_north: "Αυστραλία (au_tas_north)"
+ au_tas_south: "Αυστραλία (au_tas_south)"
+ au_tas: "Australia (au_tas)"
+ au_vic_melbourne: "Αυστραλία (au_vic_melbourne)"
+ au_vic: "Αυστραλία (au_vic)"
+ au_wa: "Αυστραλία (au_wa)"
+ au: "Αυστραλία"
+ be_fr: "Βέλγιο (be_fr)"
+ be_nl: "Βέλγιο (be_nl)"
+ bg_bg: "Βουλγαρία (bg_bg)"
+ bg_en: "Βουλγαρία (bg_en)"
+ br: "Βραζιλία"
+ br_sp: "Βραζιλία (br_sp)"
+ br_spcapital: "Βραζιλία (br_spcapital)"
+ ca_ab: "Καναδάς (ca_ab)"
+ ca_bc: "Καναδάς (ca_bc)"
+ ca_mb: "Καναδάς (ca_mb)"
+ ca_nb: "Καναδάς (ca_nb)"
+ ca_nl: "Καναδάς (ca_nl)"
+ ca_ns: "Καναδάς (ca_ns)"
+ ca_nt: "Καναδάς (ca_nt)"
+ ca_nu: "Καναδάς (ca_nu)"
+ ca_on: "Καναδάς (ca_on)"
+ de_bb: "Γερμανία (de_bb)"
+ de_be: "Γερμανία (de_be)"
+ de_bw: "Γερμανία (de_bw)"
+ de_by_augsburg: "Γερμανία (de_by_augsburg)"
+ de_by_cath: "Γερμανία (de_by_cath)"
+ de_by: "Γερμανία (de_by)"
+ de_hb: "Γερμανία (de_hb)"
+ de_he: "Γερμανία (de_he)"
+ de_hh: "Γερμανία (de_hh)"
+ de_mv: "Γερμανία (de_mv)"
+ de_ni: "Γερμανία (de_ni)"
+ de_nw: "Γερμανία (de_nw)"
+ de_rp: "Γερμανία (de_rp)"
+ de_sh: "Γερμανία (de_sh)"
+ de_sl: "Γερμανία (de_sl)"
+ de_sn_sorbian: "Γερμανία (de_sn_sorbian)"
+ de_sn: "Γερμανία (de_sn)"
+ de_st: "Γερμανία (de_st)"
+ de_th_cath: "Γερμανία (de_th_cath)"
+ de_th: "Γερμανία (de_th)"
+ de: "Γερμανία"
+ dk: "Δανία"
+ ee: "Εσθονία"
+ el: "Ελλάδα"
+ es_an: "Ισπανία (es_an)"
+ es_ar: "Ισπανία (es_ar)"
+ es_ce: "Ισπανία (es_ce)"
+ es_cl: "Ισπανία (es_cl)"
+ es_cm: "Ισπανία (es_cm)"
+ es_cn: "Ισπανία (es_cn)"
+ es_ct: "Ισπανία (es_ct)"
+ es_ex: "Ισπανία (es_ex)"
+ es_ga: "Ισπανία (es_ga)"
+ es_ib: "Ισπανία (es_ib)"
+ es_lo: "Ισπανία (es_lo)"
+ es_m: "Ισπανία (es_m)"
+ es_mu: "Ισπανία (es_mu)"
+ es_na: "Ισπανία (es_na)"
+ es_o: "Ισπανία (es_o)"
+ es_pv: "Ισπανία (es_pv)"
+ es_v: "Ισπανία (es_v)"
+ es_vc: "Ισπανία (es_vc)"
+ es: "Ισπανία"
+ fi: "Φινλανδία"
+ fr_a: "Γαλλία (fr_a)"
+ fr_m: "Γαλλία (fr_m)"
+ fr: "Γαλλία"
+ gb_con: "Ηνωμένο Βασίλειο (gb_con)"
+ gb_eaw: "Ηνωμένο Βασίλειο (gb_eaw)"
+ gb_eng: "Ηνωμένο Βασίλειο (gb_eng)"
+ ng: "Νιγηρία"
+ nl: "Ολλανδία"
+ "no": "Νορβηγία"
+ nz_ak: "Νέα Ζηλανδία (nz_ak)"
+ nz_ca: "Νέα Ζηλανδία (nz_ca)"
+ nz_ch: "Νέα Ζηλανδία (nz_ch)"
+ nz_hb: "Νέα Ζηλανδία (nz_hb)"
+ nz_mb: "Νέα Ζηλανδία (nz_mb)"
+ nz_ne: "Νέα Ζηλανδία (nz_ne)"
+ nz_nl: "Νέα Ζηλανδία (nz_nl)"
+ nz_ot: "Νέα Ζηλανδία (nz_ot)"
+ nz_sc: "Νέα Ζηλανδία (nz_sc)"
+ nz_sl: "Νέα Ζηλανδία (nz_sl)"
+ nz_ta: "Νέα Ζηλανδία (nz_ta)"
+ nz_we: "Νέα Ζηλανδία (nz_we)"
+ nz_wl: "Νέα Ζηλανδία (nz_wl)"
+ nz: "Νέα Ζηλανδία"
+ pe: "Περού"
+ ph: "Φιλιππίνες"
+ pl: "Πολωνία"
+ pt_li: "Πορτογαλία (pt_li)"
+ pt_po: "Πορτογαλία (pt_po)"
+ pt: "Πορτογαλία"
+ ro: "Ρουμανία"
+ rs_cyrl: "Σερβία (rs_cyrl)"
+ rs_la: "Σερβία (rs_la)"
+ ru: "Ρωσική Ομοσπονδία"
+ se: "Σουηδία"
+ sa: "Σαουδική Αραβία"
+ sg: "Σιγκαπούρη"
+ si: "Σλοβενία"
+ sk: "Σλοβακία"
+ th: "Ταϊλάνδη"
+ tn: "Τυνησία"
+ tr: "Τουρκία"
+ ua: "Ουκρανία"
+ us_ak: "Ηνωμένες Πολιτείες (us_ak)"
+ us_al: "Ηνωμένες Πολιτείες (us_al)"
+ us_ar: "Ηνωμένες Πολιτείες (us_ar)"
+ us_az: "Ηνωμένες Πολιτείες (us_az)"
+ us_ca: "Ηνωμένες Πολιτείες (us_ca)"
+ us_co: "Ηνωμένες Πολιτείες (us_co)"
+ us_ct: "Ηνωμένες Πολιτείες (us_ct)"
+ us_dc: "Ηνωμένες Πολιτείες (us_dc)"
+ us_de: "Ηνωμένες Πολιτείες (us_de)"
+ us_fl: "Ηνωμένες Πολιτείες (us_fl)"
+ us_ga: "Ηνωμένες Πολιτείες (us_ga)"
+ us_gu: "Ηνωμένες Πολιτείες (us_gu)"
+ us_hi: "Ηνωμένες Πολιτείες (us_hi)"
+ us_ia: "Ηνωμένες Πολιτείες (us_ia)"
+ toolbar_button:
+ today: "Σήμερα"
+ month: "Μήνας"
+ week: "Εβδομάδα"
+ day: "Ημέρα"
+ group_timezones:
+ search: "Αναζήτηση..."
+ discourse_post_event:
+ notifications:
+ invite_user_notification: "%{username} %{description}"
+ show_all: "Εμφάνιση όλων"
+ add_to_calendar: "Προσθήκη στο ημερολόγιο"
+ created_by: "Δημιουργήθηκε από"
+ bulk_invite: "Μαζική πρόσκληση"
+ bulk_invite_modal:
+ title: "Μαζική πρόσκληση"
+ success: "Το αρχείο ανέβηκε. Θα ενημερωθείς με ένα μήνυμα όταν ολοκληρωθεί η διαδικασία."
+ error: "Λυπούμαστε, το αρχείο πρέπει να έχει την μορφή CSV."
+ upcoming_events:
+ status: "Κατάσταση"
+ models:
+ event:
+ closed: "Κλειστό"
+ status:
+ public:
+ title: "Δημόσια"
+ private:
+ title: "Ιδιωτική"
+ builder_modal:
+ custom_fields:
+ placeholder: "Προεραιτικό"
+ create: "Δημιουργία"
+ update: "Αποθήκευση"
+ timezone:
+ label: Ζώνη ώρας
+ reminders:
+ units:
+ hours: "ώρες"
+ days: "ημέρες"
+ periods:
+ before: "πριν"
+ after: "μετά"
+ recurrence:
+ label: "Επανάληψη"
+ none: "Χωρίς επανάληψη"
+ every_day: "Κάθε μέρα"
+ url:
+ label: "URL"
+ placeholder: "Προεραιτικό"
+ location:
+ label: "Τοποθεσία"
+ description:
+ label: "Περιγραφή"
+ status:
+ label: "Κατάσταση"
+ invite_user_or_group:
+ invite: "Αποστολή"
diff --git a/plugins/discourse-calendar/config/locales/client.en.yml b/plugins/discourse-calendar/config/locales/client.en.yml
new file mode 100644
index 00000000000..41cfadd19a2
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/client.en.yml
@@ -0,0 +1,493 @@
+en:
+ admin_js:
+ admin:
+ calendar: "Calendar"
+ site_settings:
+ categories:
+ discourse_post_event: "Discourse Event"
+ discourse_calendar: "Discourse Calendar"
+ js:
+ notifications:
+ titles:
+ event_reminder: "event reminder"
+ event_invitation: "event invitation"
+ popup:
+ event_reminder: Event reminder
+ discourse_automation:
+ triggerables:
+ event_started:
+ title: Event started
+ fields:
+ topic_id:
+ label: Topic ID
+ discourse_calendar:
+ invite_user_notification: "%{username} invited you to: %{description}"
+ on_holiday: "On Holiday"
+ disable_holiday: "Disable"
+ enable_holiday: "Enable"
+ holiday: "Holiday"
+ holidays:
+ header_title: "Holidays"
+ pick_region_description: "Pick a region to see the holidays for that region."
+ disabled_holidays_description: "Disabled holidays will be excluded from the staff holiday calendar."
+ date: "Date"
+ add_to_calendar: "Add to Google Calendar"
+ toggle_timezone_offset_title: "Toggle timezone offset"
+ local_time: "Local time"
+ region:
+ title: "Region"
+ none: "None"
+ use_current_region: "Use Current Region"
+ names:
+ ae: "United Arab Emirates"
+ ar: "Argentina"
+ at: "Austria"
+ au_act: "Australia (au_act)"
+ au_nsw: "Australia (au_nsw)"
+ au_nt: "Australia (au_nt)"
+ au_qld_brisbane: "Australia (au_qld_brisbane)"
+ au_qld_cairns: "Australia (au_qld_cairns)"
+ au_qld: "Australia (au_qld)"
+ au_sa: "Australia (au_sa)"
+ au_tas_north: "Australia (au_tas_north)"
+ au_tas_south: "Australia (au_tas_south)"
+ au_tas: "Australia (au_tas)"
+ au_vic_melbourne: "Australia (au_vic_melbourne)"
+ au_vic: "Australia (au_vic)"
+ au_wa: "Australia (au_wa)"
+ au: "Australia"
+ be_fr: "Belgium (be_fr)"
+ be_nl: "Belgium (be_nl)"
+ bg_bg: "Bulgaria (bg_bg)"
+ bg_en: "Bulgaria (bg_en)"
+ br: "Brazil"
+ br_sp: "Brazil (br_sp)"
+ br_spcapital: "Brazil (br_spcapital)"
+ ca_ab: "Canada (ca_ab)"
+ ca_bc: "Canada (ca_bc)"
+ ca_mb: "Canada (ca_mb)"
+ ca_nb: "Canada (ca_nb)"
+ ca_nl: "Canada (ca_nl)"
+ ca_ns: "Canada (ca_ns)"
+ ca_nt: "Canada (ca_nt)"
+ ca_nu: "Canada (ca_nu)"
+ ca_on: "Canada (ca_on)"
+ ca_pe: "Canada (ca_pe)"
+ ca_qc: "Canada (ca_qc)"
+ ca_sk: "Canada (ca_sk)"
+ ca_yt: "Canada (ca_yt)"
+ ca: "Canada"
+ ch_ag: "Switzerland (ch_ag)"
+ ch_ai: "Switzerland (ch_ai)"
+ ch_ar: "Switzerland (ch_ar)"
+ ch_be: "Switzerland (ch_be)"
+ ch_bl: "Switzerland (ch_bl)"
+ ch_bs: "Switzerland (ch_bs)"
+ ch_fr: "Switzerland (ch_fr)"
+ ch_ge: "Switzerland (ch_ge)"
+ ch_gl: "Switzerland (ch_gl)"
+ ch_gr: "Switzerland (ch_gr)"
+ ch_ju: "Switzerland (ch_ju)"
+ ch_lu: "Switzerland (ch_lu)"
+ ch_ne: "Switzerland (ch_ne)"
+ ch_nw: "Switzerland (ch_nw)"
+ ch_ow: "Switzerland (ch_ow)"
+ ch_sg: "Switzerland (ch_sg)"
+ ch_sh: "Switzerland (ch_sh)"
+ ch_so: "Switzerland (ch_so)"
+ ch_sz: "Switzerland (ch_sz)"
+ ch_tg: "Switzerland (ch_tg)"
+ ch_ti: "Switzerland (ch_ti)"
+ ch_ur: "Switzerland (ch_ur)"
+ ch_vd: "Switzerland (ch_vd)"
+ ch_vs: "Switzerland (ch_vs)"
+ ch_zg: "Switzerland (ch_zg)"
+ ch_zh: "Switzerland (ch_zh)"
+ ch: "Switzerland"
+ cl: "Chile"
+ co: "Colombia"
+ cr: "Costa Rica"
+ cz: "Czech Republic"
+ de_bb: "Germany (de_bb)"
+ de_be: "Germany (de_be)"
+ de_bw: "Germany (de_bw)"
+ de_by_augsburg: "Germany (de_by_augsburg)"
+ de_by_cath: "Germany (de_by_cath)"
+ de_by: "Germany (de_by)"
+ de_hb: "Germany (de_hb)"
+ de_he: "Germany (de_he)"
+ de_hh: "Germany (de_hh)"
+ de_mv: "Germany (de_mv)"
+ de_ni: "Germany (de_ni)"
+ de_nw: "Germany (de_nw)"
+ de_rp: "Germany (de_rp)"
+ de_sh: "Germany (de_sh)"
+ de_sl: "Germany (de_sl)"
+ de_sn_sorbian: "Germany (de_sn_sorbian)"
+ de_sn: "Germany (de_sn)"
+ de_st: "Germany (de_st)"
+ de_th_cath: "Germany (de_th_cath)"
+ de_th: "Germany (de_th)"
+ de: "Germany"
+ dk: "Denmark"
+ ee: "Estonia"
+ el: "Greece"
+ es_an: "Spain (es_an)"
+ es_ar: "Spain (es_ar)"
+ es_ce: "Spain (es_ce)"
+ es_cl: "Spain (es_cl)"
+ es_cm: "Spain (es_cm)"
+ es_cn: "Spain (es_cn)"
+ es_ct: "Spain (es_ct)"
+ es_ex: "Spain (es_ex)"
+ es_ga: "Spain (es_ga)"
+ es_ib: "Spain (es_ib)"
+ es_lo: "Spain (es_lo)"
+ es_m: "Spain (es_m)"
+ es_mu: "Spain (es_mu)"
+ es_na: "Spain (es_na)"
+ es_o: "Spain (es_o)"
+ es_pv: "Spain (es_pv)"
+ es_v: "Spain (es_v)"
+ es_vc: "Spain (es_vc)"
+ es: "Spain"
+ fi: "Finland"
+ fr_a: "France (fr_a)"
+ fr_m: "France (fr_m)"
+ fr: "France"
+ gb_con: "United Kingdom (gb_con)"
+ gb_eaw: "United Kingdom (gb_eaw)"
+ gb_eng: "United Kingdom (gb_eng)"
+ gb_gsy: "United Kingdom (gb_gsy)"
+ gb_iom: "United Kingdom (gb_iom)"
+ gb_jsy: "United Kingdom (gb_jsy)"
+ gb_nir: "United Kingdom (gb_nir)"
+ gb_sct: "United Kingdom (gb_sct)"
+ gb_wls: "United Kingdom (gb_wls)"
+ gb: "United Kingdom"
+ ge: "Georgia"
+ gg: "Guernsey"
+ gh: "Ghana"
+ hk: "Hong Kong"
+ hr: "Croatia"
+ hu: "Hungary"
+ id: "Indonesia"
+ ie: "Ireland"
+ im: "Isle of Man"
+ in: "India"
+ in_gj: "India (in_gj)"
+ in_mh: "India (in_mh)"
+ in_rj: "India (in_rj)"
+ in_tn: "India (in_tn)"
+ in_ka: "India (in_ka)"
+ is: "Iceland"
+ it_bl: "Italy (it_bl)"
+ it_fi: "Italy (it_fi)"
+ it_ge: "Italy (it_ge)"
+ it_pd: "Italy (it_pd)"
+ it_rm: "Italy (it_rm)"
+ it_ro: "Italy (it_ro)"
+ it_to: "Italy (it_to)"
+ it_tv: "Italy (it_tv)"
+ it_ve: "Italy (it_ve)"
+ it_vi: "Italy (it_vi)"
+ it_vr: "Italy (it_vr)"
+ it: "Italy"
+ je: "Jersey"
+ jp: "Japan"
+ ke: "Kenya"
+ kr: "Korea (Republic of)"
+ kz: "Kazakhstan (Republic of)"
+ li: "Liechtenstein"
+ lt: "Lithuania"
+ lu: "Luxembourg"
+ lv: "Latvia"
+ ma: "Morocco"
+ mt_en: "Malta (mt_en)"
+ mt_mt: "Malta (mt_mt)"
+ mx_pue: "Mexico (mx_pue)"
+ mx: "Mexico"
+ my: "Malaysia"
+ ng: "Nigeria"
+ nl: "Netherlands"
+ "no": "Norway"
+ nz_ak: "New Zealand (nz_ak)"
+ nz_ca: "New Zealand (nz_ca)"
+ nz_ch: "New Zealand (nz_ch)"
+ nz_hb: "New Zealand (nz_hb)"
+ nz_mb: "New Zealand (nz_mb)"
+ nz_ne: "New Zealand (nz_ne)"
+ nz_nl: "New Zealand (nz_nl)"
+ nz_ot: "New Zealand (nz_ot)"
+ nz_sc: "New Zealand (nz_sc)"
+ nz_sl: "New Zealand (nz_sl)"
+ nz_ta: "New Zealand (nz_ta)"
+ nz_we: "New Zealand (nz_we)"
+ nz_wl: "New Zealand (nz_wl)"
+ nz: "New Zealand"
+ pe: "Peru"
+ ph: "Philippines"
+ pl: "Poland"
+ pt_li: "Portugal (pt_li)"
+ pt_po: "Portugal (pt_po)"
+ pt: "Portugal"
+ ro: "Romania"
+ rs_cyrl: "Serbia (rs_cyrl)"
+ rs_la: "Serbia (rs_la)"
+ ru: "Russian Federation"
+ se: "Sweden"
+ sa: "Saudi Arabia"
+ sg: "Singapore"
+ si: "Slovenia"
+ sk: "Slovakia"
+ th: "Thailand"
+ tn: "Tunisia"
+ tr: "Turkey"
+ ua: "Ukraine"
+ us_ak: "United States (us_ak)"
+ us_al: "United States (us_al)"
+ us_ar: "United States (us_ar)"
+ us_az: "United States (us_az)"
+ us_ca: "United States (us_ca)"
+ us_co: "United States (us_co)"
+ us_ct: "United States (us_ct)"
+ us_dc: "United States (us_dc)"
+ us_de: "United States (us_de)"
+ us_fl: "United States (us_fl)"
+ us_ga: "United States (us_ga)"
+ us_gu: "United States (us_gu)"
+ us_hi: "United States (us_hi)"
+ us_ia: "United States (us_ia)"
+ us_id: "United States (us_id)"
+ us_il: "United States (us_il)"
+ us_in: "United States (us_in)"
+ us_ks: "United States (us_ks)"
+ us_ky: "United States (us_ky)"
+ us_la: "United States (us_la)"
+ us_ma: "United States (us_ma)"
+ us_md: "United States (us_md)"
+ us_me: "United States (us_me)"
+ us_mi: "United States (us_mi)"
+ us_mn: "United States (us_mn)"
+ us_mo: "United States (us_mo)"
+ us_ms: "United States (us_ms)"
+ us_mt: "United States (us_mt)"
+ us_nc: "United States (us_nc)"
+ us_nd: "United States (us_nd)"
+ us_ne: "United States (us_ne)"
+ us_nh: "United States (us_nh)"
+ us_nj: "United States (us_nj)"
+ us_nm: "United States (us_nm)"
+ us_nv: "United States (us_nv)"
+ us_ny: "United States (us_ny)"
+ us_oh: "United States (us_oh)"
+ us_ok: "United States (us_ok)"
+ us_or: "United States (us_or)"
+ us_pa: "United States (us_pa)"
+ us_pr: "United States (us_pr)"
+ us_ri: "United States (us_ri)"
+ us_sc: "United States (us_sc)"
+ us_sd: "United States (us_sd)"
+ us_tn: "United States (us_tn)"
+ us_tx: "United States (us_tx)"
+ us_ut: "United States (us_ut)"
+ us_va: "United States (us_va)"
+ us_vi: "United States (us_vi)"
+ us_vt: "United States (us_vt)"
+ us_wa: "United States (us_wa)"
+ us_wi: "United States (us_wi)"
+ us_wv: "United States (us_wv)"
+ us_wy: "United States (us_wy)"
+ us: "United States"
+ ve: "Venezuela"
+ vi: "Virgin Islands (U.S.)"
+ za: "South Africa"
+ zw: "Zimbabwe"
+ toolbar_button:
+ today: "Today"
+ month: "Month"
+ week: "Week"
+ day: "Day"
+ list: "List"
+ group_timezones:
+ search: "Search..."
+ group_availability: "%{group} availability"
+ discourse_post_event:
+ notifications:
+ before_event_reminder: "An event is about to start"
+ after_event_reminder: "An event has ended"
+ ongoing_event_reminder: "An event is ongoing"
+ # TODO: delete the following keys (until ongoing_event_reminder_html)
+ # when event-invitation and event-reminder notification item widgets
+ # are removed
+ invite_user_notification: "%{username} %{description}"
+ invite_user_predefined_attendance_notification_html: "%{username} has automatically set your attendance and invited you to %{description}"
+ before_event_reminder_html: "An event is about to start %{description}"
+ after_event_reminder_html: "An event has ended %{description}"
+ ongoing_event_reminder_html: "An event is ongoing %{description}"
+ edit_reason: "Event updated"
+ edit_reason_closed: "Event closed"
+ edit_reason_opened: "Event opened"
+ topic_title:
+ starts_at: "Event will start: %{date}"
+ ended_at: "Event ended: %{date}"
+ ends_in_duration: "Ends %{duration}"
+ show_all: "Show all"
+ show_participants: "Show participants"
+ participants:
+ one: "%{count} user participated."
+ other: "%{count} users participated."
+ invite: "Notify user"
+ add_to_calendar: "Add to calendar"
+ send_pm_to_creator: "Send PM to %{username}"
+ leave: "Leave event"
+ edit_event: "Edit event"
+ export_event: "Export event"
+ created_by: "Created by"
+ bulk_invite: "Bulk Invite"
+ close_event: "Close event"
+ open_event: "Open event"
+ invitees_modal:
+ title_invited: "Event Participation"
+ title_participated: "List of users who participated"
+ filter_placeholder: "Filter users"
+ remove_invitee: "Remove invitee from list"
+ add_invitee: "Add invitee to list"
+ bulk_invite_modal:
+ confirm: "confirm"
+ text: "Upload CSV file"
+ title: "Bulk Invite"
+ success: "File uploaded successfully, you will be notified via message when the process is complete."
+ error: "Sorry, file should be CSV format."
+ confirmation_message: "You’re about to notify everyone in the uploaded file."
+ description_public: "Public events only accept usernames for bulk invites."
+ description_private: "Private events only accept group names for bulk invites."
+ download_sample_csv: "Download a sample CSV file"
+ send_bulk_invites: "Send invites"
+ group_selector_placeholder: "Choose a group..."
+ user_selector_placeholder: "Choose user..."
+ inline_title: "Inline bulk invite"
+ csv_title: "CSV bulk invite"
+ upcoming_events:
+ title: "Upcoming events"
+ creator: "Creator"
+ status: "Status"
+ starts_at: "Starts at"
+ all_events: "All events"
+ my_events: "My events"
+ upcoming_events_list:
+ title: "Upcoming events"
+ empty: "No upcoming events"
+ all_day: "All-day"
+ error: "Failed to retrieve events"
+ try_again: "Try again"
+ view_all: "View all"
+ category:
+ sort_topics_by_event_start_date: "Sort topics by event start date."
+ disable_topic_resorting: "Disable topic resorting."
+ settings_sections:
+ event_sorting: "Event Sorting"
+ preview:
+ more_than_one_event: "You can’t have more than one event."
+ models:
+ invitee:
+ no_users: "No users found"
+ status:
+ unknown: "Not interested"
+ going: "Going"
+ not_going: "Not Going"
+ interested: "Interested"
+ going_count:
+ one: "%{count} going"
+ other: "%{count} going"
+ not_going_count:
+ one: "%{count} not going"
+ other: "%{count} not going"
+ interested_count:
+ one: "%{count} interested"
+ other: "%{count} interested"
+ invited_count:
+ one: "%{count} user invited"
+ other: "%{count} users invited"
+ event:
+ expired: "Expired"
+ closed: "Closed"
+ status:
+ standalone:
+ title: "Standalone"
+ description: "A standalone event can't be joined."
+ public:
+ title: "Public"
+ description: "A public event can be joined by anyone."
+ private:
+ title: "Private"
+ description: "A private event can only be joined by invited users."
+ builder_modal:
+ custom_fields:
+ label: "Custom Fields"
+ placeholder: "Optional"
+ description: "Allowed custom fields are defined in site settings. Custom fields are used to transmit data to other plugins."
+ create_event_title: "Create Event"
+ update_event_title: "Edit Event"
+ confirm_delete: "Are you sure you want to delete this event?"
+ confirm_close: "Are you sure you want to close this event?"
+ confirm_open: "Are you sure you want to open this event?"
+ create: "Create"
+ update: "Save"
+ attach: "Create event"
+ add_reminder: "Add reminder"
+ show_local_time:
+ label: "Show local time"
+ description: "Dates and times will be displayed using: %{timezone}. Use this for events at a location, so times reflect the timezone where the event takes place."
+ timezone:
+ label: Timezone
+ remove_timezone: No timezone (UTC)
+ reminders:
+ label: "Reminders"
+ types:
+ bump_topic: "auto-bump topic"
+ notification: "notify participants"
+ units:
+ minutes: "minutes"
+ hours: "hours"
+ days: "days"
+ weeks: "weeks"
+ periods:
+ before: "before"
+ after: "after"
+ recurrence_until:
+ label: "Until (included)"
+ recurrence:
+ label: "Recurrence"
+ none: "No recurrence"
+ every_day: "Every day"
+ every_month: "Every month at this weekday"
+ every_weekday: "Every weekday"
+ every_week: "Every week at this weekday"
+ every_two_weeks: "Every two weeks at this weekday"
+ every_four_weeks: "Every four weeks at this weekday"
+ minimal:
+ label: "Minimal event"
+ checkbox_label: "Hide Going/Not going buttons and invitees status"
+ allow_chat:
+ label: "Chat integration"
+ checkbox_label: "Create and manage event specific chat channel"
+ url:
+ label: "URL"
+ placeholder: "Optional"
+ location:
+ label: "Location"
+ placeholder: "Add a location, link or something."
+ description:
+ label: "Description"
+ placeholder: "Tell people a little bit more about your event. New lines and links are supported."
+ name:
+ label: "Event name"
+ placeholder: "Optional, defaults to topic title"
+ invitees:
+ label: "Invited groups"
+ status:
+ label: "Status"
+ invite_user_or_group:
+ title: "Notify user(s) or group(s)"
+ invite: "Send"
diff --git a/plugins/discourse-calendar/config/locales/client.en_GB.yml b/plugins/discourse-calendar/config/locales/client.en_GB.yml
new file mode 100644
index 00000000000..cd19bdd839e
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/client.en_GB.yml
@@ -0,0 +1,12 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+en_GB:
+ js:
+ discourse_post_event:
+ builder_modal:
+ description:
+ label: "Description"
diff --git a/plugins/discourse-calendar/config/locales/client.es.yml b/plugins/discourse-calendar/config/locales/client.es.yml
new file mode 100644
index 00000000000..a611af7eed6
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/client.es.yml
@@ -0,0 +1,482 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+es:
+ admin_js:
+ admin:
+ calendar: "Calendario"
+ site_settings:
+ categories:
+ discourse_post_event: "Evento de Discourse"
+ discourse_calendar: "Calendario de Discourse"
+ js:
+ notifications:
+ titles:
+ event_reminder: "recordatorio de evento"
+ event_invitation: "invitación al evento"
+ popup:
+ event_reminder: Recordatorio de evento
+ discourse_automation:
+ triggerables:
+ event_started:
+ title: Evento iniciado
+ fields:
+ topic_id:
+ label: ID del tema
+ discourse_calendar:
+ invite_user_notification: "%{username} te invitó a: %{description}"
+ on_holiday: "De vacaciones"
+ disable_holiday: "Desactivar"
+ enable_holiday: "Activar"
+ holiday: "Vacaciones"
+ holidays:
+ header_title: "Días festivos"
+ pick_region_description: "Elija una región para ver los días festivos de esa región."
+ disabled_holidays_description: "Los días festivos para discapacitados se excluirán del calendario de días festivos del personal."
+ date: "Fecha"
+ add_to_calendar: "Añadir a Google Calendar"
+ toggle_timezone_offset_title: "Alternar el desfase de zona horaria"
+ region:
+ title: "Región"
+ none: "Ninguna"
+ use_current_region: "Usar región actual"
+ names:
+ ae: "Emiratos Árabes Unidos"
+ ar: "Argentina"
+ at: "Austria"
+ au_act: "Australia (au_act)"
+ au_nsw: "Australia (au_nsw)"
+ au_nt: "Australia (au_nt)"
+ au_qld_brisbane: "Australia (au_qld_brisbane)"
+ au_qld_cairns: "Australia (au_qld_cairns)"
+ au_qld: "Australia (au_qld)"
+ au_sa: "Australia (au_sa)"
+ au_tas_north: "Australia (au_tas_north)"
+ au_tas_south: "Australia (au_tas_south)"
+ au_tas: "Australia (au_tas)"
+ au_vic_melbourne: "Australia (au_vic_melbourne)"
+ au_vic: "Australia (au_vic)"
+ au_wa: "Australia (au_wa)"
+ au: "Australia"
+ be_fr: "Bélgica (be_fr)"
+ be_nl: "Bélgica (be_nl)"
+ bg_bg: "Bulgaria (bg_bg)"
+ bg_en: "Bulgaria (bg_en)"
+ br: "Brasil"
+ br_sp: "Brasil (br_sp)"
+ br_spcapital: "Brasil (br_spcapital)"
+ ca_ab: "Canadá (ca_ab)"
+ ca_bc: "Canadá (ca_bc)"
+ ca_mb: "Canadá (ca_mb)"
+ ca_nb: "Canadá (ca_nb)"
+ ca_nl: "Canadá (ca_nl)"
+ ca_ns: "Canadá (ca_ns)"
+ ca_nt: "Canadá (ca_nt)"
+ ca_nu: "Canadá (ca_nu)"
+ ca_on: "Canadá (ca_on)"
+ ca_pe: "Canadá (ca_pe)"
+ ca_qc: "Canadá (ca_qc)"
+ ca_sk: "Canadá (ca_sk)"
+ ca_yt: "Canadá (ca_yt)"
+ ca: "Canadá"
+ ch_ag: "Suiza (ch_ag)"
+ ch_ai: "Suiza (ch_ai)"
+ ch_ar: "Suiza (ch_ar)"
+ ch_be: "Suiza (ch_be)"
+ ch_bl: "Suiza (ch_bl)"
+ ch_bs: "Suiza (ch_bs)"
+ ch_fr: "Suiza (ch_fr)"
+ ch_ge: "Suiza (ch_ge)"
+ ch_gl: "Suiza (ch_gl)"
+ ch_gr: "Suiza (ch_gr)"
+ ch_ju: "Suiza (ch_ju)"
+ ch_lu: "Suiza (ch_lu)"
+ ch_ne: "Suiza (ch_ne)"
+ ch_nw: "Suiza (ch_nw)"
+ ch_ow: "Suiza (ch_ow)"
+ ch_sg: "Suiza (ch_sg)"
+ ch_sh: "Suiza (ch_sh)"
+ ch_so: "Suiza (ch_so)"
+ ch_sz: "Suiza (ch_sz)"
+ ch_tg: "Suiza (ch_tg)"
+ ch_ti: "Suiza (ch_ti)"
+ ch_ur: "Suiza (ch_ur)"
+ ch_vd: "Suiza (ch_vd)"
+ ch_vs: "Suiza (ch_vs)"
+ ch_zg: "Suiza (ch_zg)"
+ ch_zh: "Suiza (ch_zh)"
+ ch: "Suiza"
+ cl: "Chile"
+ co: "Colombia"
+ cr: "Costa Rica"
+ cz: "Republica checa"
+ de_bb: "Alemania (de_bb)"
+ de_be: "Alemania (de_be)"
+ de_bw: "Alemania (de_bw)"
+ de_by_augsburg: "Alemania (de_by_augsburg)"
+ de_by_cath: "Alemania (de_by_cath)"
+ de_by: "Alemania (de_by)"
+ de_hb: "Alemania (de_hb)"
+ de_he: "Alemania (de_he)"
+ de_hh: "Alemania (de_hh)"
+ de_mv: "Alemania (de_mv)"
+ de_ni: "Alemania (de_ni)"
+ de_nw: "Alemania (de_nw)"
+ de_rp: "Alemania (de_rp)"
+ de_sh: "Alemania (de_sh)"
+ de_sl: "Alemania (de_sl)"
+ de_sn_sorbian: "Alemania (de_sn_sorbian)"
+ de_sn: "Alemania (de_sn)"
+ de_st: "Alemania (de_st)"
+ de_th_cath: "Alemania (de_th_cath)"
+ de_th: "Alemania (de_th)"
+ de: "Alemania"
+ dk: "Dinamarca"
+ ee: "Estonia"
+ el: "Grecia"
+ es_an: "España (es_an)"
+ es_ar: "España (es_ar)"
+ es_ce: "España (es_ce)"
+ es_cl: "España (es_cl)"
+ es_cm: "España (es_cm)"
+ es_cn: "España (es_cn)"
+ es_ct: "España (es_ct)"
+ es_ex: "España (es_ex)"
+ es_ga: "España (es_ga)"
+ es_ib: "España (es_ib)"
+ es_lo: "España (es_lo)"
+ es_m: "España (es_m)"
+ es_mu: "España (es_mu)"
+ es_na: "España (es_na)"
+ es_o: "España (es_o)"
+ es_pv: "España (es_pv)"
+ es_v: "España (es_v)"
+ es_vc: "España (es_vc)"
+ es: "España"
+ fi: "Finlandia"
+ fr_a: "Francia (fr_a)"
+ fr_m: "Francia (fr_a)"
+ fr: "Francia"
+ gb_con: "Reino Unido (gb_con)"
+ gb_eaw: "Reino Unido (gb_eaw)"
+ gb_eng: "Reino Unido (gb_eng)"
+ gb_gsy: "Reino Unido (gb_gsy)"
+ gb_iom: "Reino Unido (gb_iom)"
+ gb_jsy: "Reino Unido (gb_jsy)"
+ gb_nir: "Reino Unido (gb_nir)"
+ gb_sct: "Reino Unido (gb_sct)"
+ gb_wls: "Reino Unido (gb_wls)"
+ gb: "Reino Unido"
+ ge: "Georgia"
+ gg: "Guernsey"
+ gh: "Ghana"
+ hk: "Hong Kong"
+ hr: "Croacia"
+ hu: "Hungría"
+ id: "Indonesia"
+ ie: "Irlanda"
+ im: "Isla de Man"
+ in: "India"
+ in_gj: "India (in_gj)"
+ in_mh: "India (in_mh)"
+ in_rj: "India (in_rj)"
+ in_tn: "India (in_tn)"
+ in_ka: "India (in_ka)"
+ is: "Islandia"
+ it_bl: "Italia (it_bl)"
+ it_fi: "Italia (it_fi)"
+ it_ge: "Italia (it_bl)"
+ it_pd: "Italia (it_pd)"
+ it_rm: "Italia (it_rm)"
+ it_ro: "Italia (it_ro)"
+ it_to: "Italia (it_to)"
+ it_tv: "Italia (it_tv)"
+ it_ve: "Italia (it_ve)"
+ it_vi: "Italia (it_vi)"
+ it_vr: "Italia (it_vr)"
+ it: "Italia"
+ je: "Jersey"
+ jp: "Japón"
+ ke: "Kenia"
+ kr: "Corea (República de)"
+ kz: "Kazajstán (República de)"
+ li: "Liechtenstein"
+ lt: "Lituania"
+ lu: "Luxemburgo"
+ lv: "Letonia"
+ ma: "Marruecos"
+ mt_en: "Malta (mt_en)"
+ mt_mt: "Malta (mt_mt)"
+ mx_pue: "México (mx_pue)"
+ mx: "México"
+ my: "Malasia"
+ ng: "Nigeria"
+ nl: "Países Bajos"
+ "no": "Noruega"
+ nz_ak: "Nueva Zelanda (nz_ak)"
+ nz_ca: "Nueva Zelanda (nz_ca)"
+ nz_ch: "Nueva Zelanda (nz_ch)"
+ nz_hb: "Nueva Zelanda (nz_hb)"
+ nz_mb: "Nueva Zelanda (nz_mb)"
+ nz_ne: "Nueva Zelanda (nz_ne)"
+ nz_nl: "Nueva Zelanda (nz_nl)"
+ nz_ot: "Nueva Zelanda (nz_ot)"
+ nz_sc: "Nueva Zelanda (nz_sc)"
+ nz_sl: "Nueva Zelanda (nz_sl)"
+ nz_ta: "Nueva Zelanda (nz_ta)"
+ nz_we: "Nueva Zelanda (nz_we)"
+ nz_wl: "Nueva Zelanda (nz_wl)"
+ nz: "Nueva Zelanda"
+ pe: "Perú"
+ ph: "Filipinas"
+ pl: "Polonia"
+ pt_li: "Portugal (pt_li)"
+ pt_po: "Portugal (pt_po)"
+ pt: "Portugal"
+ ro: "Rumania"
+ rs_cyrl: "Serbia (rs_cyrl)"
+ rs_la: "Serbia (rs_la)"
+ ru: "Federación Rusa"
+ se: "Suecia"
+ sa: "Arabia Saudita"
+ sg: "Singapur"
+ si: "Eslovenia"
+ sk: "Eslovaquia"
+ th: "Tailandia"
+ tn: "Túnez"
+ tr: "Turquía"
+ ua: "Ucrania"
+ us_ak: "Estados Unidos (us_ak)"
+ us_al: "Estados Unidos (us_al)"
+ us_ar: "Estados Unidos (us_ar)"
+ us_az: "Estados Unidos (us_az)"
+ us_ca: "Estados Unidos (us_ca)"
+ us_co: "Estados Unidos (us_co)"
+ us_ct: "Estados Unidos (us_ct)"
+ us_dc: "Estados Unidos (us_dc)"
+ us_de: "Estados Unidos (us_de)"
+ us_fl: "Estados Unidos (us_fl)"
+ us_ga: "Estados Unidos (us_ga)"
+ us_gu: "Estados Unidos (us_gu)"
+ us_hi: "Estados Unidos (us_hi)"
+ us_ia: "Estados Unidos (us_ia)"
+ us_id: "Estados Unidos (us_id)"
+ us_il: "Estados Unidos (us_il)"
+ us_in: "Estados Unidos (us_in)"
+ us_ks: "Estados Unidos (us_ks)"
+ us_ky: "Estados Unidos (us_ky)"
+ us_la: "Estados Unidos (us_la)"
+ us_ma: "Estados Unidos (us_ma)"
+ us_md: "Estados Unidos (us_md)"
+ us_me: "Estados Unidos (us_me)"
+ us_mi: "Estados Unidos (us_mi)"
+ us_mn: "Estados Unidos (us_mn)"
+ us_mo: "Estados Unidos (us_mo)"
+ us_ms: "Estados Unidos (us_ms)"
+ us_mt: "Estados Unidos (us_mt)"
+ us_nc: "Estados Unidos (us_nc)"
+ us_nd: "Estados Unidos (us_nd)"
+ us_ne: "Estados Unidos (us_ne)"
+ us_nh: "Estados Unidos (us_nh)"
+ us_nj: "Estados Unidos (us_nj)"
+ us_nm: "Estados Unidos (us_nm)"
+ us_nv: "Estados Unidos (us_nv)"
+ us_ny: "Estados Unidos (us_ny)"
+ us_oh: "Estados Unidos (us_oh)"
+ us_ok: "Estados Unidos (us_ok)"
+ us_or: "Estados Unidos (us_or)"
+ us_pa: "Estados Unidos (us_pa)"
+ us_pr: "Estados Unidos (us_pr)"
+ us_ri: "Estados Unidos (us_ri)"
+ us_sc: "Estados Unidos (us_sc)"
+ us_sd: "Estados Unidos (us_sd)"
+ us_tn: "Estados Unidos (us_tn)"
+ us_tx: "Estados Unidos (us_tx)"
+ us_ut: "Estados Unidos (us_ut)"
+ us_va: "Estados Unidos (us_va)"
+ us_vi: "Estados Unidos (us_vi)"
+ us_vt: "Estados Unidos (us_vt)"
+ us_wa: "Estados Unidos (us_wa)"
+ us_wi: "Estados Unidos (us_wi)"
+ us_wv: "Estados Unidos (us_wv)"
+ us_wy: "Estados Unidos (us_wy)"
+ us: "Estados Unidos"
+ ve: "Venezuela"
+ vi: "Islas Vírgenes (EE. UU.)"
+ za: "Sudáfrica"
+ toolbar_button:
+ today: "Hoy"
+ month: "Mes"
+ week: "Semana"
+ day: "Día"
+ list: "Lista"
+ group_timezones:
+ search: "Buscar..."
+ group_availability: "Disponibilidad de %{group}"
+ discourse_post_event:
+ notifications:
+ before_event_reminder: "Un evento está a punto de comenzar"
+ after_event_reminder: "Un evento ha terminado"
+ ongoing_event_reminder: "Un evento está en curso"
+ invite_user_notification: "%{username} %{description}"
+ invite_user_predefined_attendance_notification_html: "%{username} ha configurado automáticamente tu asistencia y te ha invitado a %{description}"
+ before_event_reminder_html: "Un evento está a punto de comenzar %{description}"
+ after_event_reminder_html: "Un evento ha finalizado %{description}"
+ ongoing_event_reminder_html: "Hay un evento en curso %{description}"
+ edit_reason: "Evento actualizado"
+ edit_reason_closed: "Evento cerrado"
+ edit_reason_opened: "Evento abierto"
+ topic_title:
+ starts_at: "El evento comenzará: %{date}"
+ ended_at: "Evento terminó: %{date}"
+ ends_in_duration: "Termina en %{duration}"
+ show_all: "Mostrar todos"
+ show_participants: "Mostrar participantes"
+ participants:
+ one: "%{count} usuario participó."
+ other: "%{count} usuarios participaron."
+ invite: "Notificar al usuario"
+ add_to_calendar: "Añadir al calendario"
+ send_pm_to_creator: "Enviar MP a %{username}"
+ leave: "Abandonar evento"
+ edit_event: "Editar evento"
+ export_event: "Exportar evento"
+ created_by: "Creado por"
+ bulk_invite: "Invitaciones en masa"
+ close_event: "Cerrar evento"
+ open_event: "Abrir evento"
+ invitees_modal:
+ title_invited: "Participación en eventos"
+ title_participated: "Lista de usuarios que participaron"
+ filter_placeholder: "Filtrar usuarios"
+ remove_invitee: "Eliminar invitado de la lista"
+ add_invitee: "Añadir invitado a la lista"
+ bulk_invite_modal:
+ confirm: "confirmar"
+ text: "Subir archivo CSV"
+ title: "Invitaciones masivas"
+ success: "Archivo subido correctamente, se te notificará mediante un mensaje cuando se complete el proceso."
+ error: "Lo sentimos, el formato del archivo debe ser CSV."
+ confirmation_message: "Está a punto de notificar a todos en el archivo subido."
+ description_public: "Los eventos públicos solo aceptan nombres de usuario para invitaciones masivas."
+ description_private: "Los eventos privados solo aceptan nombres de grupos para invitaciones masivas."
+ download_sample_csv: "Descarga un archivo CSV de muestra"
+ send_bulk_invites: "Enviar invitaciones"
+ group_selector_placeholder: "Elige un grupo..."
+ user_selector_placeholder: "Elegir usuario..."
+ inline_title: "Invitación masiva en línea"
+ csv_title: "Invitación masiva en CSV"
+ upcoming_events:
+ title: "Próximos eventos"
+ creator: "Creador"
+ status: "Estado"
+ starts_at: "Comienza el"
+ upcoming_events_list:
+ title: "Próximos eventos"
+ empty: "No hay eventos próximos"
+ all_day: "Todo el día"
+ error: "No se pudieron recuperar los eventos"
+ try_again: "Intentar de nuevo"
+ view_all: "Ver todo"
+ category:
+ sort_topics_by_event_start_date: "Ordenar temas por fecha de inicio del evento."
+ disable_topic_resorting: "Desactivar reordenación de temas."
+ settings_sections:
+ event_sorting: "Clasificación de eventos"
+ preview:
+ more_than_one_event: "No puedes tener más de un evento."
+ models:
+ invitee:
+ no_users: "No se han encontrado usuarios"
+ status:
+ unknown: "No estoy interesado"
+ going: "Asistiré"
+ not_going: "No asistiré"
+ interested: "Estoy interesado"
+ going_count:
+ one: "%{count} asistirá"
+ other: "%{count} asistirán"
+ not_going_count:
+ one: "%{count} no asistirá"
+ other: "%{count} no asistirán"
+ interested_count:
+ one: "%{count} interesado"
+ other: "%{count} interesados"
+ invited_count:
+ one: "%{count} usuario invitado"
+ other: "%{count} usuarios invitados"
+ event:
+ expired: "Caducado"
+ closed: "Cerrado"
+ status:
+ standalone:
+ title: "Independiente"
+ description: "No se puede unir a un evento independiente."
+ public:
+ title: "Público"
+ description: "Cualquier persona puede unirse a un evento público."
+ private:
+ title: "Privado"
+ description: "Solo los usuarios invitados pueden unirse a un evento privado."
+ builder_modal:
+ custom_fields:
+ label: "Campos personalizados"
+ placeholder: "Opcional"
+ description: "Los campos personalizados permitidos se definen en la configuración del sitio. Los campos personalizados se utilizan para transmitir datos a otros plugins."
+ create_event_title: "Crear evento"
+ update_event_title: "Editar evento"
+ confirm_delete: "¿Seguro que quieres eliminar este evento?"
+ confirm_close: "¿Seguro que quieres cerrar ese evento?"
+ confirm_open: "¿Seguro que quieres abrir ese evento?"
+ create: "Crear"
+ update: "Guardar"
+ attach: "Crear evento"
+ add_reminder: "Añadir recordatorio"
+ timezone:
+ label: Zona horaria
+ remove_timezone: Sin zona horaria (UTC)
+ reminders:
+ label: "Recordatorios"
+ types:
+ bump_topic: "reflotar tema automáticamente"
+ notification: "notificar a los participantes"
+ units:
+ minutes: "minutos"
+ hours: "horas"
+ days: "días"
+ weeks: "semanas"
+ periods:
+ before: "antes de"
+ after: "después de"
+ recurrence:
+ label: "Periodicidad"
+ none: "Sin repetición"
+ every_day: "Cada día"
+ every_month: "Todos los meses en este día de la semana"
+ every_weekday: "Todos los días de la semana"
+ every_week: "Todas las semanas en este día de la semana"
+ every_two_weeks: "Cada dos semanas en este día de la semana"
+ every_four_weeks: "Cada cuatro semanas en este día de la semana"
+ minimal:
+ label: "Evento mínimo"
+ checkbox_label: "Ocultar los botones Asistiré/No asistiré y el estado de los invitados"
+ url:
+ label: "URL"
+ placeholder: "Opcional"
+ location:
+ label: "Ubicación"
+ description:
+ label: "Descripción"
+ name:
+ label: "Nombre del evento"
+ placeholder: "Opcional, por defecto es el título del tema"
+ invitees:
+ label: "Grupos invitados"
+ status:
+ label: "Estado"
+ invite_user_or_group:
+ title: "Notificar a usuario(s) o grupo(s)"
+ invite: "Enviar"
diff --git a/plugins/discourse-calendar/config/locales/client.et.yml b/plugins/discourse-calendar/config/locales/client.et.yml
new file mode 100644
index 00000000000..233005da7e4
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/client.et.yml
@@ -0,0 +1,72 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+et:
+ js:
+ discourse_automation:
+ triggerables:
+ event_started:
+ fields:
+ topic_id:
+ label: Teema ID
+ discourse_calendar:
+ disable_holiday: "Lülita välja"
+ enable_holiday: "Lülita sisse"
+ date: "Date"
+ region:
+ none: "Pole"
+ toolbar_button:
+ today: "Täna"
+ month: "Kuu"
+ week: "Nädal"
+ day: "Päev"
+ group_timezones:
+ search: "Otsi..."
+ discourse_post_event:
+ notifications:
+ invite_user_notification: "%{username} %{description}"
+ show_all: "Näita kõiki"
+ bulk_invite_modal:
+ success: "Fail edukalt üles laetud. Sulle saabub teade, kui protsess on lõpule jõudnud."
+ error: "Vabandust, fail peab olema CSV vormingus."
+ upcoming_events:
+ status: "Staatus"
+ models:
+ event:
+ closed: "Suletud"
+ status:
+ public:
+ title: "Avalik"
+ private:
+ title: "Privaatne"
+ builder_modal:
+ custom_fields:
+ placeholder: "Valikuline"
+ create: "Loo"
+ update: "Salvesta"
+ timezone:
+ label: Ajavöönd
+ reminders:
+ units:
+ minutes: "minutit"
+ hours: "tundi"
+ days: "päeva"
+ periods:
+ before: "enne"
+ after: "pärast"
+ recurrence:
+ every_day: "Iga päev"
+ url:
+ label: "URL"
+ placeholder: "Valikuline"
+ location:
+ label: "Asukoht"
+ description:
+ label: "Kirjeldus"
+ status:
+ label: "Staatus"
+ invite_user_or_group:
+ invite: "Saada"
diff --git a/plugins/discourse-calendar/config/locales/client.fa_IR.yml b/plugins/discourse-calendar/config/locales/client.fa_IR.yml
new file mode 100644
index 00000000000..0a2f3e4744a
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/client.fa_IR.yml
@@ -0,0 +1,448 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+fa_IR:
+ admin_js:
+ admin:
+ calendar: "تقویم"
+ site_settings:
+ categories:
+ discourse_post_event: "رویداد دیسکورس"
+ discourse_calendar: "تقویم دیسکورس"
+ js:
+ notifications:
+ titles:
+ event_reminder: "یادآور رویداد"
+ event_invitation: "دعوتنامه رویداد"
+ popup:
+ event_reminder: یادآور رویداد
+ discourse_automation:
+ triggerables:
+ event_started:
+ title: رویداد شروع شد
+ fields:
+ topic_id:
+ label: شناسه موضوع
+ discourse_calendar:
+ invite_user_notification: "%{username} شما را دعوت کرده به: %{description}"
+ on_holiday: "در تعطیلات"
+ disable_holiday: "غیرفعال کردن"
+ enable_holiday: "فعال کردن"
+ holiday: "تعطیلات"
+ holidays:
+ header_title: "تعطیلات"
+ pick_region_description: "منطقه ای را برای دیدن تعطیلاتش انتخاب کنید."
+ date: "تاریخ"
+ add_to_calendar: "افزودن به تقویم گوگل"
+ region:
+ title: "منطقه"
+ none: "هیچکدام"
+ use_current_region: "استفاده از منطقه فعلی"
+ names:
+ ae: "امارات متحده عربی"
+ ar: "آرژانتین"
+ at: "اتریش"
+ au_act: "استرالیا (au_act)"
+ au_nsw: "استرالیا (au_nsw)"
+ au_nt: "استرالیا (au_nt)"
+ au_qld_brisbane: "استرالیا (au_qld_brisbane)"
+ au_qld_cairns: "استرالیا (au_qld_cairns)"
+ au_qld: "استرالیا (au_qld)"
+ au_sa: "استرالیا (au_sa)"
+ au_tas_north: "استرالیا (au_tas_north)"
+ au_tas_south: "استرالیا (au_tas_south)"
+ au_tas: "استرالیا (au_tas)"
+ au_vic_melbourne: "استرالیا (au_vic_melbourne)"
+ au_vic: "استرالیا (au_vic)"
+ au_wa: "استرالیا (au_wa)"
+ au: "استرالیا"
+ be_fr: "بلژیک (be_fr)"
+ be_nl: "بلژیک (be_nl)"
+ bg_bg: "بلغارستان (bg_bg)"
+ bg_en: "بلغارستان (bg_en)"
+ br: "برزیل"
+ br_sp: "برزیل (br_sp)"
+ br_spcapital: "برزیل (br_spcapital)"
+ ca_ab: "کانادا (ca_ab)"
+ ca_bc: "کانادا (ca_bc)"
+ ca_mb: "کانادا (ca_mb)"
+ ca_nb: "کانادا (ca_nb)"
+ ca_nl: "کانادا (ca_nl)"
+ ca_ns: "کانادا (ca_ns)"
+ ca_nt: "کانادا (ca_nt)"
+ ca_nu: "کانادا (ca_nu)"
+ ca_on: "کانادا (ca_on)"
+ ca_pe: "کانادا (ca_pe)"
+ ca_qc: "کانادا (ca_qc)"
+ ca_sk: "کانادا (ca_sk)"
+ ca_yt: "کانادا (ca_yt)"
+ ca: "کانادا"
+ ch_ag: "سوئیس (ch_ag)"
+ ch_ai: "سوئیس (ch_ai)"
+ ch_ar: "سوئیس (ch_ar)"
+ ch_be: "سوئیس (ch_be)"
+ ch_bl: "سوئیس (ch_bl)"
+ ch_bs: "سوئیس (ch_bs)"
+ ch_fr: "سوئیس (ch_fr)"
+ ch_ge: "سوئیس (ch_ge)"
+ ch_gl: "سوئیس (ch_gl)"
+ ch_gr: "سوئیس (ch_gr)"
+ ch_ju: "سوئیس (ch_ju)"
+ ch_lu: "سوئیس (ch_lu)"
+ ch_ne: "سوئیس (ch_ne)"
+ ch_nw: "سوئیس (ch_nw)"
+ ch_ow: "سوئیس (ch_ow)"
+ ch_sg: "سوئیس (ch_sg)"
+ ch_sh: "سوئیس (ch_sh)"
+ ch_so: "سوئیس (ch_so)"
+ ch_sz: "سوئیس (ch_sz)"
+ ch_tg: "سوئیس (ch_tg)"
+ ch_ti: "سوئیس (ch_ti)"
+ ch_ur: "سوئیس (ch_ur)"
+ ch_vd: "سوئیس (ch_vd)"
+ ch_vs: "سوئیس (ch_vs)"
+ ch_zg: "سوئیس (ch_zg)"
+ ch_zh: "سوئیس (ch_zh)"
+ ch: "سوئیس"
+ cl: "شیلی"
+ co: "کلمبیا"
+ cr: "کاستاریکا"
+ cz: "جمهوری چک"
+ de_bb: "آلمان (de_bb)"
+ de_be: "آلمان (de_be)"
+ de_bw: "آلمان (de_bw)"
+ de_by_augsburg: "آلمان (de_by_augsburg)"
+ de_by_cath: "آلمان (de_by_cath)"
+ de_by: "آلمان (de_by)"
+ de_hb: "آلمان (de_hb)"
+ de_he: "آلمان (de_he)"
+ de_hh: "آلمان (de_hh)"
+ de_mv: "آلمان (de_mv)"
+ de_ni: "آلمان (de_ni)"
+ de_nw: "آلمان (de_nw)"
+ de_rp: "آلمان (de_rp)"
+ de_sh: "آلمان (de_sh)"
+ de_sl: "آلمان (de_sl)"
+ de_sn_sorbian: "آلمان (de_sn_sorbian)"
+ de_sn: "آلمان (de_sn)"
+ de_st: "آلمان (de_st)"
+ de_th_cath: "آلمان (de_th_cath)"
+ de_th: "آلمان (de_th)"
+ de: "آلمان"
+ dk: "دانمارک"
+ ee: "استونی"
+ el: "یونان"
+ es_an: "اسپانیا (es_an)"
+ es_ar: "اسپانیا (es_ar)"
+ es_ce: "اسپانیا (es_ce)"
+ es_cl: "اسپانیا (es_cl)"
+ es_cm: "اسپانیا (es_cm)"
+ es_cn: "اسپانیا (es_cn)"
+ es_ct: "اسپانیا (es_ct)"
+ es_ex: "اسپانیا (es_ex)"
+ es_ga: "اسپانیا (es_ga)"
+ es_ib: "اسپانیا (es_ib)"
+ es_lo: "اسپانیا (es_lo)"
+ es_m: "اسپانیا (es_m)"
+ es_mu: "اسپانیا (es_mu)"
+ es_na: "اسپانیا (es_na)"
+ es_o: "اسپانیا (es_o)"
+ es_pv: "اسپانیا (es_pv)"
+ es_v: "اسپانیا (es_v)"
+ es_vc: "اسپانیا (es_vc)"
+ es: "اسپانیا"
+ fi: "فنلاند"
+ fr_a: "فرانسه (fr_a)"
+ fr_m: "فرانسه (fr_m)"
+ fr: "فرانسه"
+ gb_con: "بریتانیا (gb_con)"
+ gb_eaw: "بریتانیا (gb_eaw)"
+ gb_eng: "بریتانیا (gb_eng)"
+ gb_gsy: "بریتانیا (gb_gsy)"
+ gb_iom: "بریتانیا (gb_iom)"
+ gb_jsy: "بریتانیا (gb_jsy)"
+ gb_nir: "بریتانیا (gb_nir)"
+ gb_sct: "بریتانیا (gb_sct)"
+ gb_wls: "بریتانیا (gb_wls)"
+ gb: "بریتانیا"
+ ge: "گرجستان"
+ gg: "گرنزی"
+ gh: "غنا"
+ hk: "هنگ کنگ"
+ hr: "کرواسی"
+ hu: "مجارستان"
+ id: "اندونزی"
+ ie: "ایرلند"
+ im: "جزیره من"
+ in: "هند"
+ in_gj: "هند (in_gj)"
+ in_mh: "هند (in_mh)"
+ in_rj: "هند (in_rj)"
+ in_tn: "هند (in_tn)"
+ in_ka: "هند (in_ka)"
+ is: "ایسلند"
+ it_bl: "ایتالیا (it_bl)"
+ it_fi: "ایتالیا (it_fi)"
+ it_ge: "ایتالیا (it_ge)"
+ it_pd: "ایتالیا (it_pd)"
+ it_rm: "ایتالیا (it_rm)"
+ it_ro: "ایتالیا (it_ro)"
+ it_to: "ایتالیا (it_to)"
+ it_tv: "ایتالیا (it_tv)"
+ it_ve: "ایتالیا (it_ve)"
+ it_vi: "ایتالیا (it_vi)"
+ it_vr: "ایتالیا (it_vr)"
+ it: "ایتالیا"
+ je: "جرزی"
+ jp: "ژاپن"
+ ke: "کنیا"
+ kr: "کره جنوبی"
+ kz: "جمهوری قزاقستان"
+ li: "لیختناشتاین"
+ lt: "لیتوانی"
+ lu: "لوکزامبورگ"
+ lv: "لتونی"
+ ma: "مراکش"
+ mt_en: "مالت (mt_en)"
+ mt_mt: "مالت (mt_mt)"
+ mx_pue: "مکزیک (mx_pue)"
+ mx: "مکزیک"
+ my: "مالزی"
+ ng: "نیجریه"
+ nl: "هلند"
+ "no": "نروژ"
+ nz_ak: "نیوزلند (nz_ak)"
+ nz_ca: "نیوزلند (nz_ca)"
+ nz_ch: "نیوزلند (nz_ch)"
+ nz_hb: "نیوزلند (nz_hb)"
+ nz_mb: "نیوزلند (nz_mb)"
+ nz_ne: "نیوزلند (nz_ne)"
+ nz_nl: "نیوزلند (nz_nl)"
+ nz_ot: "نیوزلند (nz_ot)"
+ nz_sc: "نیوزلند (nz_sc)"
+ nz_sl: "نیوزلند (nz_sl)"
+ nz_ta: "نیوزلند (nz_ta)"
+ nz_we: "نیوزلند (nz_we)"
+ nz_wl: "نیوزلند (nz_wl)"
+ nz: "نیوزلند"
+ pe: "پرو"
+ ph: "فیلیپین"
+ pl: "لهستان"
+ pt_li: "پرتغال (pt_li)"
+ pt_po: "پرتغال (pt_po)"
+ pt: "پرتغال"
+ ro: "رومانی"
+ rs_cyrl: "صربستان (rs_cyrl)"
+ rs_la: "صربستان (rs_la)"
+ ru: "روسیه"
+ se: "سوئد"
+ sa: "عربستان سعودی"
+ sg: "سنگاپور"
+ si: "اسلوونی"
+ sk: "اسلواکی"
+ th: "تایلند"
+ tn: "تونس"
+ tr: "ترکیه"
+ ua: "اوکراین"
+ us_ak: "ایالات متحده (us_ak)"
+ us_al: "ایالات متحده (us_al)"
+ us_ar: "ایالات متحده (us_ar)"
+ us_az: "ایالات متحده (us_az)"
+ us_ca: "ایالات متحده (us_ca)"
+ us_co: "ایالات متحده (us_co)"
+ us_ct: "ایالات متحده (us_ct)"
+ us_dc: "ایالات متحده (us_dc)"
+ us_de: "ایالات متحده (us_de)"
+ us_fl: "ایالات متحده (us_fl)"
+ us_ga: "ایالات متحده (us_ga)"
+ us_gu: "ایالات متحده (us_gu)"
+ us_hi: "ایالات متحده (us_hi)"
+ us_ia: "ایالات متحده (us_ia)"
+ us_id: "ایالات متحده (us_id)"
+ us_il: "ایالات متحده (us_il)"
+ us_in: "ایالات متحده (us_in)"
+ us_ks: "ایالات متحده (us_ks)"
+ us_ky: "ایالات متحده (us_ky)"
+ us_la: "ایالات متحده (us_la)"
+ us_ma: "ایالات متحده (us_ma)"
+ us_md: "ایالات متحده (us_md)"
+ us_me: "ایالات متحده (us_me)"
+ us_mi: "ایالات متحده (us_mi)"
+ us_mn: "ایالات متحده (us_mn)"
+ us_mo: "ایالات متحده (us_mo)"
+ us_ms: "ایالات متحده (us_ms)"
+ us_mt: "ایالات متحده (us_mt)"
+ us_nc: "ایالات متحده (us_nc)"
+ us_nd: "ایالات متحده (us_nd)"
+ us_ne: "ایالات متحده (us_ne)"
+ us_nh: "ایالات متحده (us_nh)"
+ us_nj: "ایالات متحده (us_nj)"
+ us_nm: "ایالات متحده (us_nm)"
+ us_nv: "ایالات متحده (us_nv)"
+ us_ny: "ایالات متحده (us_ny)"
+ us_oh: "ایالات متحده (us_oh)"
+ us_ok: "ایالات متحده (us_ok)"
+ us_or: "ایالات متحده (us_or)"
+ us_pa: "ایالات متحده (us_pa)"
+ us_pr: "ایالات متحده (us_pr)"
+ us_ri: "ایالات متحده (us_ri)"
+ us_sc: "ایالات متحده (us_sc)"
+ us_sd: "ایالات متحده (us_sd)"
+ us_tn: "ایالات متحده (us_tn)"
+ us_tx: "ایالات متحده (us_tx)"
+ us_ut: "ایالات متحده (us_ut)"
+ us_va: "ایالات متحده (us_va)"
+ us_vi: "ایالات متحده (us_vi)"
+ us_vt: "ایالات متحده (us_vt)"
+ us_wa: "ایالات متحده (us_wa)"
+ us_wi: "ایالات متحده (us_wi)"
+ us_wv: "ایالات متحده (us_wv)"
+ us_wy: "ایالات متحده (us_wy)"
+ us: "ایالات متحده"
+ ve: "ونزوئلا"
+ vi: "جزایر ویرجین (ایالات متحده)"
+ za: "آفریقایی جنوبی"
+ toolbar_button:
+ today: "امروز"
+ month: "ماه"
+ week: "هفته"
+ day: "روز"
+ list: "فهرست"
+ group_timezones:
+ search: "جستجو..."
+ discourse_post_event:
+ notifications:
+ before_event_reminder: "یک رویداد در حال شروع است"
+ after_event_reminder: "یک رویداد به پایان رسیده است"
+ ongoing_event_reminder: "یک رویداد در حال انجام است"
+ invite_user_notification: "%{username} %{description}"
+ invite_user_predefined_attendance_notification_html: "%{username} به طور خودکار حضور شما را تنظیم کرده و شما را به %{description} دعوت کرده است"
+ before_event_reminder_html: "یک رویداد در حال شروع است %{description}"
+ after_event_reminder_html: "یک رویداد به پایان رسیده است %{description}"
+ ongoing_event_reminder_html: "یک رویداد در حال انجام است %{description}"
+ edit_reason: "رویداد بهروز شد"
+ edit_reason_closed: "رویداد بسته شد"
+ edit_reason_opened: "رویداد باز شد"
+ topic_title:
+ starts_at: "رویداد در: %{date} شروع خواهد شد"
+ ended_at: "رویداد در: %{date} پایان یافت"
+ ends_in_duration: "به پایان می رسد بعد از %{duration}"
+ show_all: "نمایش همه"
+ show_participants: "نمایش شرکت کنندگان"
+ participants:
+ one: "%{count} کاربر شرکت کرد."
+ other: "%{count} کاربر شرکت کردند."
+ add_to_calendar: "افزودن به تقویم"
+ send_pm_to_creator: "ارسال پیام خصوصی به %{username}"
+ leave: "ترک رویداد"
+ edit_event: "ویرایش رویداد"
+ export_event: "صادر کردن رویداد"
+ created_by: "ایجاد شده توسط"
+ bulk_invite: "دعوت انبوه"
+ close_event: "بستن رویداد"
+ open_event: "بازکردن رویداد"
+ invitees_modal:
+ title_invited: "مشارکت در رویداد"
+ title_participated: "فهرست کاربرانی که شرکت کردهاند"
+ filter_placeholder: "فیلتر کردن کاربران"
+ remove_invitee: "حذف دعوت شده از لیست"
+ bulk_invite_modal:
+ confirm: "تایید"
+ text: "آپلود فایل CSV"
+ title: "دعوت انبوه"
+ success: "فایل با موفقیت بارگذاری شد. هنگامی که پروسه تمام شود، به شما از طریق پیام اطلاع داده خواهد شد."
+ error: "با عرض پوزش، نوع فایل باید CSV باشد."
+ confirmation_message: "شما می خواهید به همه افراد موجود در فایل آپلود شده اطلاع دهید."
+ download_sample_csv: "یک فایل CSV نمونه را بارگیری کنید."
+ send_bulk_invites: "ارسال دعوتنامه"
+ group_selector_placeholder: "یک گروه را انتخاب کنید..."
+ user_selector_placeholder: "کاربر را انتخاب کنید..."
+ upcoming_events:
+ title: "رویدادهای آینده"
+ creator: "سازنده"
+ status: "وضعیت"
+ starts_at: "شروع می شود در"
+ upcoming_events_list:
+ title: "رویدادهای آینده"
+ empty: "هیچ رویدادی در آینده وجود ندارد"
+ all_day: "تمام روز"
+ error: "بازیابی رویدادها شکست خورد"
+ try_again: "دوباره امتحان کنید"
+ view_all: "مشاهده همه"
+ category:
+ sort_topics_by_event_start_date: "موضوعات را بر اساس تاریخ شروع رویداد مرتب کن."
+ settings_sections:
+ event_sorting: "مرتبسازی رویداد"
+ preview:
+ more_than_one_event: "شما نمی توانید بیش از یک رویداد داشته باشید."
+ models:
+ invitee:
+ no_users: "هیچ کاربری یافت نشد"
+ event:
+ expired: "منقضی شده"
+ closed: "بسته شده"
+ status:
+ public:
+ title: "عمومی"
+ description: "هر کس می تواند به یک رویداد عمومی بپیوندد."
+ private:
+ title: "خصوصی"
+ description: "فقط کاربران دعوت شده میتوانند به رویداد خصوصی بپیوندند"
+ builder_modal:
+ custom_fields:
+ label: "فیلدهای سفارشی"
+ placeholder: "اختیاری"
+ description: "فیلدهای سفارشی مجاز در تنظیمات سایت تعریف شده اند. از فیلدهای سفارشی برای انتقال داده ها به پلاگین های دیگر استفاده می شود."
+ create_event_title: "ایجاد رویداد"
+ update_event_title: "ویرایش رویداد"
+ confirm_delete: "آیا از حذف کردن این رویداد مطمئن هستید؟"
+ confirm_close: "آیا از بستن این رویداد مطمئن هستید؟"
+ confirm_open: "آیا از باز کردن این رویداد مطمئن هستید؟"
+ create: "ایجاد"
+ update: "ذخیره"
+ attach: "ایجاد رویداد"
+ add_reminder: "افزودن یادآور"
+ timezone:
+ label: منطقه زمانی
+ remove_timezone: بدون منطقه زمانی (UTC)
+ reminders:
+ label: "یادآورها"
+ types:
+ notification: "اطلاع دادن به شرکتکنندگان"
+ units:
+ minutes: "دقیقه"
+ hours: "ساعت"
+ days: "روز"
+ weeks: "هفته"
+ periods:
+ before: "قبل از"
+ after: "بعد از"
+ recurrence:
+ label: "همروندی"
+ none: "بدون همروندی"
+ every_day: "هر روز"
+ every_month: "هر ماه در این روز"
+ every_weekday: "هر هفته"
+ every_week: "هر هفته در این روز"
+ every_two_weeks: "هر دو هفته در این روز"
+ every_four_weeks: "هر چهار هفته در این روز"
+ url:
+ label: "نشانی اینترنتی"
+ placeholder: "اختیاری"
+ location:
+ label: "موقعیت"
+ description:
+ label: "توضیح"
+ name:
+ label: "نام رویداد"
+ placeholder: "اختیاری، پیشفرض عنوان موضوع است"
+ invitees:
+ label: "گروههای دعوت شده"
+ status:
+ label: "وضعیت"
+ invite_user_or_group:
+ title: "اطلاع دادن به کاربر(ها) یا گروه(ها)"
+ invite: "ارسال"
diff --git a/plugins/discourse-calendar/config/locales/client.fi.yml b/plugins/discourse-calendar/config/locales/client.fi.yml
new file mode 100644
index 00000000000..a7f7073981f
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/client.fi.yml
@@ -0,0 +1,482 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+fi:
+ admin_js:
+ admin:
+ calendar: "Kalenteri"
+ site_settings:
+ categories:
+ discourse_post_event: "Discourse Event"
+ discourse_calendar: "Discourse Calendar"
+ js:
+ notifications:
+ titles:
+ event_reminder: "tapahtumamuistutus"
+ event_invitation: "tapahtumakutsu"
+ popup:
+ event_reminder: Tapahtumamuistutus
+ discourse_automation:
+ triggerables:
+ event_started:
+ title: Tapahtuma alkoi
+ fields:
+ topic_id:
+ label: Ketjun tunnus
+ discourse_calendar:
+ invite_user_notification: "%{username} kutsui sinut: %{description}"
+ on_holiday: "Lomalla"
+ disable_holiday: "Poista käytöstä"
+ enable_holiday: "Ota käyttöön"
+ holiday: "Loma"
+ holidays:
+ header_title: "Pyhäpäivät"
+ pick_region_description: "Valitse alue nähdäksesi kyseisen alueen pyhäpäivät."
+ disabled_holidays_description: "Käytöstä poistetut pyhäpäivät jätetään pois henkilökunnan lomakalenterista."
+ date: "Päivämäärä"
+ add_to_calendar: "Lisää Google-kalenteriin"
+ toggle_timezone_offset_title: "Vaihda aikavyöhykesiirtymä"
+ region:
+ title: "Alue"
+ none: "Ei valittu"
+ use_current_region: "Käytä nykyistä aluetta"
+ names:
+ ae: "Yhdistyneet arabiemiirikunnat"
+ ar: "Argentiina"
+ at: "Itävalta"
+ au_act: "Australia (au_act)"
+ au_nsw: "Australia (au_nsw)"
+ au_nt: "Australia (au_nt)"
+ au_qld_brisbane: "Australia (au_qld_brisbane)"
+ au_qld_cairns: "Australia (au_qld_cairns)"
+ au_qld: "Australia (au_qld)"
+ au_sa: "Australia (au_sa)"
+ au_tas_north: "Australia (au_tas_north)"
+ au_tas_south: "Australia (au_tas_south)"
+ au_tas: "Australia (au_tas)"
+ au_vic_melbourne: "Australia (au_vic_melbourne)"
+ au_vic: "Australia (au_vic)"
+ au_wa: "Australia (au_wa)"
+ au: "Australia"
+ be_fr: "Belgia (be_fr)"
+ be_nl: "Belgia (be_nl)"
+ bg_bg: "Bulgaria (bg_bg)"
+ bg_en: "Bulgaria (bg_en)"
+ br: "Brasilia"
+ br_sp: "Brasilia (br_sp)"
+ br_spcapital: "Brasilia (br_spcapital)"
+ ca_ab: "Kanada (ca_ab)"
+ ca_bc: "Kanada (ca_bc)"
+ ca_mb: "Kanada (ca_mb)"
+ ca_nb: "Kanada (ca_nb)"
+ ca_nl: "Kanada (ca_nl)"
+ ca_ns: "Kanada (ca_ns)"
+ ca_nt: "Kanada (ca_nt)"
+ ca_nu: "Kanada (ca_nu)"
+ ca_on: "Kanada (ca_on)"
+ ca_pe: "Kanada (ca_pe)"
+ ca_qc: "Kanada (ca_qc)"
+ ca_sk: "Canada (ca_sk)"
+ ca_yt: "Canada (ca_yt)"
+ ca: "Kanada"
+ ch_ag: "Sveitsi (ch_ag)"
+ ch_ai: "Sveitsi (ch_ai)"
+ ch_ar: "Sveitsi (ch_ar)"
+ ch_be: "Sveitsi (ch_be)"
+ ch_bl: "Sveitsi (ch_bl)"
+ ch_bs: "Sveitsi (ch_bs)"
+ ch_fr: "Sveitsi (ch_fr)"
+ ch_ge: "Sveitsi (ch_ge)"
+ ch_gl: "Sveitsi (ch_gl)"
+ ch_gr: "Sveitsi (ch_gr)"
+ ch_ju: "Sveitsi (ch_ju)"
+ ch_lu: "Sveitsi (ch_lu)"
+ ch_ne: "Sveitsi (ch_ne)"
+ ch_nw: "Sveitsi (ch_nw)"
+ ch_ow: "Sveitsi (ch_ow)"
+ ch_sg: "Sveitsi (ch_sg)"
+ ch_sh: "Sveitsi (ch_sh)"
+ ch_so: "Sveitsi (ch_so)"
+ ch_sz: "Sveitsi (ch_sz)"
+ ch_tg: "Sveitsi (ch_tg)"
+ ch_ti: "Sveitsi (ch_ti)"
+ ch_ur: "Sveitsi (ch_ur)"
+ ch_vd: "Sveitsi (ch_vd)"
+ ch_vs: "Sveitsi (ch_vs)"
+ ch_zg: "Sveitsi (ch_zg)"
+ ch_zh: "Sveitsi (ch_zh)"
+ ch: "Sveitsi"
+ cl: "Chile"
+ co: "Kolumbia"
+ cr: "Costa Rica"
+ cz: "Tšekin tasavalta"
+ de_bb: "Saksa (de_bb)"
+ de_be: "Saksa (de_be)"
+ de_bw: "Saksa (de_bw)"
+ de_by_augsburg: "Saksa (de_by_augsburg)"
+ de_by_cath: "Saksa (de_by_cath)"
+ de_by: "Saksa (de_by)"
+ de_hb: "Saksa (de_hb)"
+ de_he: "Saksa (de_he)"
+ de_hh: "Saksa (de_hh)"
+ de_mv: "Saksa (de_mv)"
+ de_ni: "Saksa (de_ni)"
+ de_nw: "Saksa (de_nw)"
+ de_rp: "Germany (de_rp)"
+ de_sh: "Saksa (de_sh)"
+ de_sl: "Saksa (de_sl)"
+ de_sn_sorbian: "Saksa (de_sn_sorbian)"
+ de_sn: "Saksa (de_sn)"
+ de_st: "Saksa (de_st)"
+ de_th_cath: "Saksa (de_th_cath)"
+ de_th: "Saksa (de_th)"
+ de: "Saksa"
+ dk: "Tanska"
+ ee: "Viro"
+ el: "Kreikka"
+ es_an: "Espanja (es_an)"
+ es_ar: "Espanja (es_ar)"
+ es_ce: "Espanja (es_ce)"
+ es_cl: "Espanja (es_cl)"
+ es_cm: "Espanja (es_cm)"
+ es_cn: "Espanja (es_cn)"
+ es_ct: "Espanja (es_ct)"
+ es_ex: "Espanja (es_ex)"
+ es_ga: "Espanja (es_ga)"
+ es_ib: "Espanja (es_ib)"
+ es_lo: "Espanja (es_lo)"
+ es_m: "Espanja (es_m)"
+ es_mu: "Espanja (es_mu)"
+ es_na: "Espanja (es_na)"
+ es_o: "Espanja (es_o)"
+ es_pv: "Espanja (es_pv)"
+ es_v: "Espanja (es_v)"
+ es_vc: "Espanja (es_vc)"
+ es: "Espanja"
+ fi: "Suomi"
+ fr_a: "Ranska (fr_a)"
+ fr_m: "Ranska (fr_m)"
+ fr: "Ranska"
+ gb_con: "Yhdistynyt kuningaskunta (gb_con)"
+ gb_eaw: "Yhdistynyt kuningaskunta (gb_eaw)"
+ gb_eng: "Yhdistynyt kuningaskunta (gb_eng)"
+ gb_gsy: "Yhdistynyt kuningaskunta (gb_gsy)"
+ gb_iom: "Yhdistynyt kuningaskunta (gb_iom)"
+ gb_jsy: "Yhdistynyt kuningaskunta (gb_jsy)"
+ gb_nir: "Yhdistynyt kuningaskunta (gb_nir)"
+ gb_sct: "Yhdistynyt kuningaskunta (gb_sct)"
+ gb_wls: "Yhdistynyt kuningaskunta (gb_wls)"
+ gb: "Yhdistynyt kuningaskunta"
+ ge: "Georgia"
+ gg: "Guernsey"
+ gh: "Ghana"
+ hk: "Hongkong"
+ hr: "Kroatia"
+ hu: "Unkari"
+ id: "Indonesia"
+ ie: "Irlanti"
+ im: "Mansaari"
+ in: "Intia"
+ in_gj: "Intia (in_gj)"
+ in_mh: "Intia (in_mh)"
+ in_rj: "Intia (in_rj)"
+ in_tn: "Intia (in_tn)"
+ in_ka: "Intia (in_ka)"
+ is: "Islanti"
+ it_bl: "Italia (it_bl)"
+ it_fi: "Italia (it_fi)"
+ it_ge: "Italia (it_ge)"
+ it_pd: "Italia (it_pd)"
+ it_rm: "Italia (it_rm)"
+ it_ro: "Italia (it_ro)"
+ it_to: "Italia (it_to)"
+ it_tv: "Italia (it_tv)"
+ it_ve: "Italia (it_ve)"
+ it_vi: "Italia (it_vi)"
+ it_vr: "Italia (it_vr)"
+ it: "Italia"
+ je: "Jersey"
+ jp: "Japani"
+ ke: "Kenia"
+ kr: "Korean tasavalta"
+ kz: "Kazakstanin tasavalta"
+ li: "Liechtenstein"
+ lt: "Liettua"
+ lu: "Luxemburg"
+ lv: "Latvia"
+ ma: "Marokko"
+ mt_en: "Malta (mt_en)"
+ mt_mt: "Malta (mt_mt)"
+ mx_pue: "Meksiko (mx_pue)"
+ mx: "Meksiko"
+ my: "Malesia"
+ ng: "Nigeria"
+ nl: "Alankomaat"
+ "no": "Norja"
+ nz_ak: "Uusi-Seelanti (nz_ak)"
+ nz_ca: "Uusi-Seelanti (nz_ca)"
+ nz_ch: "Uusi-Seelanti (nz_ch)"
+ nz_hb: "Uusi-Seelanti (nz_hb)"
+ nz_mb: "Uusi-Seelanti (nz_mb)"
+ nz_ne: "Uusi-Seelanti (nz_ne)"
+ nz_nl: "Uusi-Seelanti (nz_nl)"
+ nz_ot: "Uusi-Seelanti (nz_ot)"
+ nz_sc: "Uusi-Seelanti (nz_sc)"
+ nz_sl: "Uusi-Seelanti (nz_sl)"
+ nz_ta: "Uusi-Seelanti (nz_ta)"
+ nz_we: "Uusi-Seelanti (nz_we)"
+ nz_wl: "Uusi-Seelanti (nz_wl)"
+ nz: "Uusi-Seelanti"
+ pe: "Peru"
+ ph: "Filippiinit"
+ pl: "Puola"
+ pt_li: "Portugali (pt_li)"
+ pt_po: "Portugali (pt_po)"
+ pt: "Portugali"
+ ro: "Romania"
+ rs_cyrl: "Serbia (rs_cyrl)"
+ rs_la: "Serbia (rs_la)"
+ ru: "Venäjän federaatio"
+ se: "Ruotsi"
+ sa: "Saudi-Arabia"
+ sg: "Singapore"
+ si: "Slovenia"
+ sk: "Slovakia"
+ th: "Thaimaa"
+ tn: "Tunisia"
+ tr: "Turkki"
+ ua: "Ukraina"
+ us_ak: "Yhdysvallat (us_ak)"
+ us_al: "Yhdysvallat (us_al)"
+ us_ar: "Yhdysvallat (us_ar)"
+ us_az: "Yhdysvallat (us_az)"
+ us_ca: "Yhdysvallat (us_ca)"
+ us_co: "Yhdysvallat (us_co)"
+ us_ct: "Yhdysvallat (us_ct)"
+ us_dc: "Yhdysvallat (us_dc)"
+ us_de: "Yhdysvallat (us_de)"
+ us_fl: "Yhdysvallat (us_fl)"
+ us_ga: "Yhdysvallat (us_ga)"
+ us_gu: "Yhdysvallat (us_gu)"
+ us_hi: "Yhdysvallat (us_hi)"
+ us_ia: "Yhdysvallat (us_ia)"
+ us_id: "Yhdysvallat (us_id)"
+ us_il: "Yhdysvallat (us_il)"
+ us_in: "Yhdysvallat (us_in)"
+ us_ks: "Yhdysvallat (us_ks)"
+ us_ky: "Yhdysvallat (us_ky)"
+ us_la: "Yhdysvallat (us_la)"
+ us_ma: "Yhdysvallat (us_ma)"
+ us_md: "Yhdysvallat (us_md)"
+ us_me: "Yhdysvallat (us_me)"
+ us_mi: "Yhdysvallat (us_mi)"
+ us_mn: "Yhdysvallat (us_mn)"
+ us_mo: "Yhdysvallat (us_mo)"
+ us_ms: "Yhdysvallat (us_ms)"
+ us_mt: "Yhdysvallat (us_mt)"
+ us_nc: "Yhdysvallat (us_nc)"
+ us_nd: "Yhdysvallat (us_nd)"
+ us_ne: "Yhdysvallat (us_ne)"
+ us_nh: "Yhdysvallat (us_nh)"
+ us_nj: "Yhdysvallat (us_nj)"
+ us_nm: "Yhdysvallat (us_nm)"
+ us_nv: "Yhdysvallat (us_nv)"
+ us_ny: "Yhdysvallat (us_ny)"
+ us_oh: "Yhdysvallat (us_oh)"
+ us_ok: "Yhdysvallat (us_ok)"
+ us_or: "Yhdysvallat (us_or)"
+ us_pa: "Yhdysvallat (us_pa)"
+ us_pr: "Yhdysvallat (us_pr)"
+ us_ri: "Yhdysvallat (us_ri)"
+ us_sc: "Yhdysvallat (us_sc)"
+ us_sd: "Yhdysvallat (us_sd)"
+ us_tn: "Yhdysvallat (us_tn)"
+ us_tx: "Yhdysvallat (us_tx)"
+ us_ut: "Yhdysvallat (us_ut)"
+ us_va: "Yhdysvallat (us_va)"
+ us_vi: "Yhdysvallat (us_vi)"
+ us_vt: "Yhdysvallat (us_vt)"
+ us_wa: "Yhdysvallat (us_wa)"
+ us_wi: "Yhdysvallat (us_wi)"
+ us_wv: "Yhdysvallat (us_wv)"
+ us_wy: "Yhdysvallat (us_wy)"
+ us: "Yhdysvallat"
+ ve: "Venezuela"
+ vi: "Neitsytsaaret (Yhdysvallat)"
+ za: "Etelä-Afrikka"
+ toolbar_button:
+ today: "Tänään"
+ month: "Kuukausi"
+ week: "Viikko"
+ day: "Päivä"
+ list: "Luettelo"
+ group_timezones:
+ search: "Hae..."
+ group_availability: "%{group} – saatavuus"
+ discourse_post_event:
+ notifications:
+ before_event_reminder: "Tapahtuma on alkamassa"
+ after_event_reminder: "Tapahtuma on päättynyt"
+ ongoing_event_reminder: "Tapahtuma on käynnissä"
+ invite_user_notification: "%{username} %{description}"
+ invite_user_predefined_attendance_notification_html: "%{username} on asettanut osallistumisesi automaattisesti ja kutsunut sinut tapahtumaan %{description}"
+ before_event_reminder_html: "Tapahtuma on alkamassa %{description}"
+ after_event_reminder_html: "Tapahtuma on päättynyt %{description}"
+ ongoing_event_reminder_html: "Tapahtuma on käynnissä %{description}"
+ edit_reason: "Tapahtuma päivitetty"
+ edit_reason_closed: "Tapahtuma suljettu"
+ edit_reason_opened: "Tapahtuma avattu"
+ topic_title:
+ starts_at: "Tapahtuma alkaa: %{date}"
+ ended_at: "Tapahtuma päättyi: %{date}"
+ ends_in_duration: "Päättyy %{duration}"
+ show_all: "Näytä kaikki"
+ show_participants: "Näytä osallistujat"
+ participants:
+ one: "%{count} käyttäjä osallistui."
+ other: "%{count} käyttäjää osallistui."
+ invite: "Ilmoita käyttäjälle"
+ add_to_calendar: "Lisää kalenteriin"
+ send_pm_to_creator: "Lähetä yksityisviesti käyttäjälle %{username}"
+ leave: "Poistu tapahtumasta"
+ edit_event: "Muokkaa tapahtumaa"
+ export_event: "Vie tapahtuma"
+ created_by: "Luonut"
+ bulk_invite: "Joukkokutsu"
+ close_event: "Sulje tapahtuma"
+ open_event: "Avaa tapahtuma"
+ invitees_modal:
+ title_invited: "Tapahtumaan osallistuminen"
+ title_participated: "Osallistuneiden käyttäjien luettelo"
+ filter_placeholder: "Suodata käyttäjiä"
+ remove_invitee: "Poista kutsuttu luettelosta"
+ add_invitee: "Lisää kutsuttu luetteloon"
+ bulk_invite_modal:
+ confirm: "vahvista"
+ text: "Lataa CSV-tiedosto"
+ title: "Joukkokutsu"
+ success: "Tiedoston lataaminen onnistui. Saat viestin, kun prosessi on valmis."
+ error: "Tiedoston tulee olla CSV-muodossa."
+ confirmation_message: "Olet ilmoittamassa kaikille, jotka sisältyvät ladattuun tiedostoon."
+ description_public: "Julkiset tapahtumat hyväksyvät vain käyttäjätunnuksia joukkokutsuissa."
+ description_private: "Yksityiset tapahtumat hyväksyvät vain ryhmien nimiä joukkokutsuissa."
+ download_sample_csv: "Lataa malli-CSV-tiedosto"
+ send_bulk_invites: "Lähetä kutsut"
+ group_selector_placeholder: "Valitse ryhmä..."
+ user_selector_placeholder: "Valitse käyttäjä..."
+ inline_title: "Upotettu joukkokutsu"
+ csv_title: "CSV-joukkokutsu"
+ upcoming_events:
+ title: "Tulevat tapahtumat"
+ creator: "Luoja"
+ status: "Tila"
+ starts_at: "Alkaa"
+ upcoming_events_list:
+ title: "Tulevat tapahtumat"
+ empty: "Ei tulevia tapahtumia"
+ all_day: "Koko päivä"
+ error: "Tapahtumien hakeminen epäonnistui"
+ try_again: "Yritä uudelleen"
+ view_all: "Näytä kaikki"
+ category:
+ sort_topics_by_event_start_date: "Lajittele ketjut tapahtuman alkamispäivän mukaan."
+ disable_topic_resorting: "Poista ketjujen lajittelu uudelleen käytöstä."
+ settings_sections:
+ event_sorting: "Tapahtumien lajittelu"
+ preview:
+ more_than_one_event: "Sinulla ei voi olla enempää kuin yksi tapahtuma."
+ models:
+ invitee:
+ no_users: "Käyttäjiä ei löydy"
+ status:
+ unknown: "Ei kiinnostunut"
+ going: "Menossa"
+ not_going: "Ei menossa"
+ interested: "Kiinnostunut"
+ going_count:
+ one: "%{count} menossa"
+ other: "%{count} menossa"
+ not_going_count:
+ one: "%{count} ei ole menossa"
+ other: "%{count} ei ole menossa"
+ interested_count:
+ one: "%{count} kiinnostunut"
+ other: "%{count} kiinnostunutta"
+ invited_count:
+ one: "%{count} käyttäjä kutsuttu"
+ other: "%{count} käyttäjää kutsuttu"
+ event:
+ expired: "Vanhentunut"
+ closed: "Suljettu"
+ status:
+ standalone:
+ title: "Erillinen"
+ description: "Erilliseen tapahtumaan ei voi liittyä."
+ public:
+ title: "Julkinen"
+ description: "Julkiseen tapahtumaan voi liittyä kuka tahansa."
+ private:
+ title: "Yksityinen"
+ description: "Yksityiseen tapahtumaan voivat liittyä vain kutsutut käyttäjät."
+ builder_modal:
+ custom_fields:
+ label: "Mukautetut kentät"
+ placeholder: "Valinnainen"
+ description: "Sallitut mukautetut kentät määritetään sivuston asetuksissa. Mukautettuja kenttiä käytetään tietojen siirtämiseen muille lisäosille."
+ create_event_title: "Luo tapahtuma"
+ update_event_title: "Muokkaa tapahtumaa"
+ confirm_delete: "Haluatko varmasti poistaa tämän tapahtuman?"
+ confirm_close: "Haluatko varmasti sulkea tämän tapahtuman?"
+ confirm_open: "Haluatko varmasti avata tämän tapahtuman?"
+ create: "Luo"
+ update: "Tallenna"
+ attach: "Luo tapahtuma"
+ add_reminder: "Lisää muistutus"
+ timezone:
+ label: Aikavyöhyke
+ remove_timezone: Ei aikavyöhykettä (UTC)
+ reminders:
+ label: "Muistutukset"
+ types:
+ bump_topic: "nosta ketjua automaattisesti"
+ notification: "Ilmoita osallistujille"
+ units:
+ minutes: "minuuttia"
+ hours: "tuntia"
+ days: "päivää"
+ weeks: "viikkoa"
+ periods:
+ before: "ennen"
+ after: "jälkeen"
+ recurrence:
+ label: "Toistuvuus"
+ none: "Ei toistu"
+ every_day: "Joka päivä"
+ every_month: "Kuukausittain tänä arkipäivänä"
+ every_weekday: "Arkipäivisin"
+ every_week: "Viikoittain tänä arkipäivänä"
+ every_two_weeks: "Kahden viikon välein tänä arkipäivänä"
+ every_four_weeks: "Neljän viikon välein tänä arkipäivänä"
+ minimal:
+ label: "Minimaalinen tapahtuma"
+ checkbox_label: "Piilota Menossa- ja Ei menossa -painikeet sekä kutsuttujen tila"
+ url:
+ label: "URL"
+ placeholder: "Valinnainen"
+ location:
+ label: "Paikka"
+ description:
+ label: "Kuvaus"
+ name:
+ label: "Tapahtuman nimi"
+ placeholder: "Valinnainen, oletuksena ketjun otsikko"
+ invitees:
+ label: "Kutsutut ryhmät"
+ status:
+ label: "Tila"
+ invite_user_or_group:
+ title: "Ilmoita käyttäjille tai ryhmille"
+ invite: "Lähetä"
diff --git a/plugins/discourse-calendar/config/locales/client.fr.yml b/plugins/discourse-calendar/config/locales/client.fr.yml
new file mode 100644
index 00000000000..cf57716b33e
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/client.fr.yml
@@ -0,0 +1,482 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+fr:
+ admin_js:
+ admin:
+ calendar: "Calendrier"
+ site_settings:
+ categories:
+ discourse_post_event: "Événement Discourse"
+ discourse_calendar: "Calendrier Discourse"
+ js:
+ notifications:
+ titles:
+ event_reminder: "rappel d'événement"
+ event_invitation: "invitation à un événement"
+ popup:
+ event_reminder: Rappel d'événement
+ discourse_automation:
+ triggerables:
+ event_started:
+ title: L'événement a commencé
+ fields:
+ topic_id:
+ label: Identifiant du sujet
+ discourse_calendar:
+ invite_user_notification: "%{username} vous a invité(e) à rejoindre : %{description}"
+ on_holiday: "En vacances"
+ disable_holiday: "Désactiver"
+ enable_holiday: "Activer"
+ holiday: "Vacances"
+ holidays:
+ header_title: "Vacances"
+ pick_region_description: "Choisissez une région pour voir les jours fériés de cette région."
+ disabled_holidays_description: "Les vacances pour personnes handicapées seront exclues du calendrier des jours fériés du personnel."
+ date: "Date"
+ add_to_calendar: "Ajouter à Google Calendar"
+ toggle_timezone_offset_title: "Activer/désactiver le décalage horaire"
+ region:
+ title: "Région"
+ none: "Aucune"
+ use_current_region: "Utiliser la région actuelle"
+ names:
+ ae: "Émirats arabes unis"
+ ar: "Argentine"
+ at: "Autriche"
+ au_act: "Australie (au_act)"
+ au_nsw: "Australie (au_nsw)"
+ au_nt: "Australie (au_nt)"
+ au_qld_brisbane: "Australie (au_qld_brisbane)"
+ au_qld_cairns: "Australie (au_qld_cairns)"
+ au_qld: "Australie (au_qld)"
+ au_sa: "Australie (au_sa)"
+ au_tas_north: "Australie (au_tas_north)"
+ au_tas_south: "Australie (au_tas_south)"
+ au_tas: "Australie (au_tas)"
+ au_vic_melbourne: "Australie (au_vic_melbourne)"
+ au_vic: "Australie (au_vic)"
+ au_wa: "Australie (au_wa)"
+ au: "Australie"
+ be_fr: "Belgique (be_fr)"
+ be_nl: "Belgique (be_nl)"
+ bg_bg: "Bulgarie (bg_bg)"
+ bg_en: "Bulgarie (bg_en)"
+ br: "Brésil"
+ br_sp: "Brésil (br_sp)"
+ br_spcapital: "Brésil (br_spcapital)"
+ ca_ab: "Canada (ca_ab)"
+ ca_bc: "Canada (ca_bc)"
+ ca_mb: "Canada (ca_mb)"
+ ca_nb: "Canada (ca_nb)"
+ ca_nl: "Canada (ca_nl)"
+ ca_ns: "Canada (ca_ns)"
+ ca_nt: "Canada (ca_nt)"
+ ca_nu: "Canada (ca_nu)"
+ ca_on: "Canada (ca_on)"
+ ca_pe: "Canada (ca_pe)"
+ ca_qc: "Canada (ca_qc)"
+ ca_sk: "Canada (ca_sk)"
+ ca_yt: "Canada (ca_yt)"
+ ca: "Canada"
+ ch_ag: "Suisse (ch_ag)"
+ ch_ai: "Suisse (ch_ai)"
+ ch_ar: "Suisse (ch_ar)"
+ ch_be: "Suisse (ch_be)"
+ ch_bl: "Suisse (ch_bl)"
+ ch_bs: "Suisse (ch_bs)"
+ ch_fr: "Suisse (ch_fr)"
+ ch_ge: "Suisse (ch_ge)"
+ ch_gl: "Suisse (ch_gl)"
+ ch_gr: "Suisse (ch_gr)"
+ ch_ju: "Suisse (ch_ju)"
+ ch_lu: "Suisse (ch_lu)"
+ ch_ne: "Suisse (ch_ne)"
+ ch_nw: "Suisse (ch_nw)"
+ ch_ow: "Suisse (ch_ow)"
+ ch_sg: "Suisse (ch_sg)"
+ ch_sh: "Suisse (ch_sh)"
+ ch_so: "Suisse (ch_so)"
+ ch_sz: "Suisse (ch_sz)"
+ ch_tg: "Suisse (ch_tg)"
+ ch_ti: "Suisse (ch_ti)"
+ ch_ur: "Suisse (ch_ur)"
+ ch_vd: "Suisse (ch_vd)"
+ ch_vs: "Suisse (ch_vs)"
+ ch_zg: "Suisse (ch_zg)"
+ ch_zh: "Suisse (ch_zh)"
+ ch: "Suisse"
+ cl: "Chili"
+ co: "Colombie"
+ cr: "Costa Rica"
+ cz: "République Tchèque"
+ de_bb: "Allemagne (de_bb)"
+ de_be: "Allemagne (de_be)"
+ de_bw: "Allemagne (de_bw)"
+ de_by_augsburg: "Allemagne (de_by_augsburg)"
+ de_by_cath: "Allemagne (de_by_cath)"
+ de_by: "Allemagne (de_by)"
+ de_hb: "Allemagne (de_hb)"
+ de_he: "Allemagne (de_he)"
+ de_hh: "Allemagne (de_hh)"
+ de_mv: "Allemagne (de_mv)"
+ de_ni: "Allemagne (de_ni)"
+ de_nw: "Allemagne (de_nw)"
+ de_rp: "Allemagne (de_rp)"
+ de_sh: "Allemagne (de_sh)"
+ de_sl: "Allemagne (de_sl)"
+ de_sn_sorbian: "Allemagne (de_sn_sorbian)"
+ de_sn: "Allemagne (de_sn)"
+ de_st: "Allemagne (de_st)"
+ de_th_cath: "Allemagne (de_th_cath)"
+ de_th: "Allemagne (de_th)"
+ de: "Allemagne"
+ dk: "Danemark"
+ ee: "Estonie"
+ el: "Grèce"
+ es_an: "Espagne (es_an)"
+ es_ar: "Espagne (es_ar)"
+ es_ce: "Espagne (es_ce)"
+ es_cl: "Espagne (es_cl)"
+ es_cm: "Espagne (es_cm)"
+ es_cn: "Espagne (es_cn)"
+ es_ct: "Espagne (es_ct)"
+ es_ex: "Espagne (es_ex)"
+ es_ga: "Espagne (es_ga)"
+ es_ib: "Espagne (es_ib)"
+ es_lo: "Espagne (es_lo)"
+ es_m: "Espagne (es_m)"
+ es_mu: "Espagne (es_mu)"
+ es_na: "Espagne (es_na)"
+ es_o: "Espagne (es_o)"
+ es_pv: "Espagne (es_pv)"
+ es_v: "Espagne (es_v)"
+ es_vc: "Espagne (es_vc)"
+ es: "Espagne"
+ fi: "Finlande"
+ fr_a: "France (fr_a)"
+ fr_m: "France (fr_m)"
+ fr: "France"
+ gb_con: "Royaume-Uni (gb_con)"
+ gb_eaw: "Royaume-Uni (gb_eaw)"
+ gb_eng: "Royaume-Uni (gb_eng)"
+ gb_gsy: "Royaume-Uni (gb_gsy)"
+ gb_iom: "Royaume-Uni (gb_iom)"
+ gb_jsy: "Royaume-Uni (gb_jsy)"
+ gb_nir: "Royaume-Uni (gb_nir)"
+ gb_sct: "Royaume-Uni (gb_sct)"
+ gb_wls: "Royaume-Uni (gb_wls)"
+ gb: "Royaume-Uni"
+ ge: "Géorgie"
+ gg: "Guernesey"
+ gh: "Ghana"
+ hk: "Hong Kong"
+ hr: "Croatie"
+ hu: "Hongrie"
+ id: "Indonésie"
+ ie: "Irlande"
+ im: "Île de Man"
+ in: "Inde"
+ in_gj: "Inde (in_gj)"
+ in_mh: "Inde (in_mh)"
+ in_rj: "Inde (in_rj)"
+ in_tn: "Inde (in_tn)"
+ in_ka: "Inde (in_ka)"
+ is: "Islande"
+ it_bl: "Italie (it_bl)"
+ it_fi: "Italie (it_fi)"
+ it_ge: "Italie (it_ge)"
+ it_pd: "Italie (it_pd)"
+ it_rm: "Italie (it_rm)"
+ it_ro: "Italie (it_ro)"
+ it_to: "Italie (it_to)"
+ it_tv: "Italie (it_tv)"
+ it_ve: "Italie (it_ve)"
+ it_vi: "Italie (it_vi)"
+ it_vr: "Italie (it_vr)"
+ it: "Italie"
+ je: "Jersey"
+ jp: "Japon"
+ ke: "Kenya"
+ kr: "Corée (République de)"
+ kz: "Kazakhstan (République du)"
+ li: "Liechtenstein"
+ lt: "Lituanie"
+ lu: "Luxembourg"
+ lv: "Lettonie"
+ ma: "Maroc"
+ mt_en: "Malte (mt_en)"
+ mt_mt: "Malte (mt_mt)"
+ mx_pue: "Mexique (mx_pue)"
+ mx: "Mexique"
+ my: "Malaisie"
+ ng: "Nigeria"
+ nl: "Pays-Bas"
+ "no": "Norvège"
+ nz_ak: "Nouvelle-Zélande (nz_ak)"
+ nz_ca: "Nouvelle-Zélande (nz_ca)"
+ nz_ch: "Nouvelle-Zélande (nz_ch)"
+ nz_hb: "Nouvelle-Zélande (nz_hb)"
+ nz_mb: "Nouvelle-Zélande (nz_mb)"
+ nz_ne: "Nouvelle-Zélande (nz_ne)"
+ nz_nl: "Nouvelle-Zélande (nz_nl)"
+ nz_ot: "Nouvelle-Zélande (nz_ot)"
+ nz_sc: "Nouvelle-Zélande (nz_sc)"
+ nz_sl: "Nouvelle-Zélande (nz_sl)"
+ nz_ta: "Nouvelle-Zélande (nz_ta)"
+ nz_we: "Nouvelle-Zélande (nz_we)"
+ nz_wl: "Nouvelle-Zélande (nz_wl)"
+ nz: "Nouvelle-Zélande"
+ pe: "Pérou"
+ ph: "Philippines"
+ pl: "Pologne"
+ pt_li: "Portugal (pt_li)"
+ pt_po: "Portugal (pt_po)"
+ pt: "Portugal"
+ ro: "Roumanie"
+ rs_cyrl: "Serbie (rs_cyrl)"
+ rs_la: "Serbie (rs_la)"
+ ru: "Fédération de Russie"
+ se: "Suède"
+ sa: "Arabie Saoudite"
+ sg: "Singapour"
+ si: "Slovénie"
+ sk: "Slovaquie"
+ th: "Thaïlande"
+ tn: "Tunisie"
+ tr: "Turquie"
+ ua: "Ukraine"
+ us_ak: "États-Unis (us_ak)"
+ us_al: "États-Unis (us_al)"
+ us_ar: "États-Unis (us_ar)"
+ us_az: "États-Unis (us_az)"
+ us_ca: "États-Unis (us_ca)"
+ us_co: "États-Unis (us_co)"
+ us_ct: "États-Unis (us_ct)"
+ us_dc: "États-Unis (us_dc)"
+ us_de: "États-Unis (us_de)"
+ us_fl: "États-Unis (us_fl)"
+ us_ga: "États-Unis (us_ga)"
+ us_gu: "États-Unis (us_gu)"
+ us_hi: "États-Unis (us_hi)"
+ us_ia: "États-Unis (us_ia)"
+ us_id: "États-Unis (us_id)"
+ us_il: "États-Unis (us_il)"
+ us_in: "États-Unis (us_in)"
+ us_ks: "États-Unis (us_ks)"
+ us_ky: "États-Unis (us_ky)"
+ us_la: "États-Unis (us_la)"
+ us_ma: "États-Unis (us_ma)"
+ us_md: "États-Unis (us_md)"
+ us_me: "États-Unis (us_me)"
+ us_mi: "États-Unis (us_mi)"
+ us_mn: "États-Unis (us_mn)"
+ us_mo: "États-Unis (us_mo)"
+ us_ms: "États-Unis (us_ms)"
+ us_mt: "États-Unis (us_mt)"
+ us_nc: "États-Unis (us_nc)"
+ us_nd: "États-Unis (us_nd)"
+ us_ne: "États-Unis (us_ne)"
+ us_nh: "États-Unis (us_nh)"
+ us_nj: "États-Unis (us_nj)"
+ us_nm: "États-Unis (us_nm)"
+ us_nv: "États-Unis (us_nv)"
+ us_ny: "États-Unis (us_ny)"
+ us_oh: "États-Unis (us_oh)"
+ us_ok: "États-Unis (us_ok)"
+ us_or: "États-Unis (us_ou)"
+ us_pa: "États-Unis (us_pa)"
+ us_pr: "États-Unis (us_pr)"
+ us_ri: "États-Unis (us_ri)"
+ us_sc: "États-Unis (us_sc)"
+ us_sd: "États-Unis (us_sd)"
+ us_tn: "États-Unis (us_tn)"
+ us_tx: "États-Unis (us_tx)"
+ us_ut: "États-Unis (us_ut)"
+ us_va: "États-Unis (us_va)"
+ us_vi: "États-Unis (us_vi)"
+ us_vt: "États-Unis (us_vt)"
+ us_wa: "États-Unis (us_wa)"
+ us_wi: "États-Unis (us_wi)"
+ us_wv: "États-Unis (us_wv)"
+ us_wy: "États-Unis (us_wy)"
+ us: "États-Unis"
+ ve: "Venezuela"
+ vi: "Îles Vierges (États-Unis)"
+ za: "Afrique du Sud"
+ toolbar_button:
+ today: "Aujourd'hui"
+ month: "Mois"
+ week: "Semaine"
+ day: "Jour"
+ list: "Liste"
+ group_timezones:
+ search: "Recherche…"
+ group_availability: "disponibilité de %{group}"
+ discourse_post_event:
+ notifications:
+ before_event_reminder: "Un événement est sur le point de commencer"
+ after_event_reminder: "Un événement est terminé"
+ ongoing_event_reminder: "Un événement est en cours"
+ invite_user_notification: "%{username} %{description}"
+ invite_user_predefined_attendance_notification_html: "%{username} a automatiquement défini votre participation et vous a invité(e) à rejoindre %{description}"
+ before_event_reminder_html: "Un événement est sur le point de commencer %{description}"
+ after_event_reminder_html: "Un événement s'est terminé %{description}"
+ ongoing_event_reminder_html: "Un événement est en cours %{description}"
+ edit_reason: "Événement mis à jour"
+ edit_reason_closed: "Événement fermé"
+ edit_reason_opened: "Événement ouvert"
+ topic_title:
+ starts_at: "Début de l'événement : %{date}"
+ ended_at: "Événement terminé : %{date}"
+ ends_in_duration: "Se termine %{duration}"
+ show_all: "Tout afficher"
+ show_participants: "Afficher les participants"
+ participants:
+ one: "%{count} utilisateur a participé."
+ other: "%{count} utilisateurs ont participé."
+ invite: "Notifier l'utilisateur"
+ add_to_calendar: "Ajouter au calendrier"
+ send_pm_to_creator: "Envoyer un MD à %{username}"
+ leave: "Quitter l'événement"
+ edit_event: "Modifier l'événement"
+ export_event: "Exporter l'événement"
+ created_by: "Créé par "
+ bulk_invite: "Invitation groupée"
+ close_event: "Fermer l'événement"
+ open_event: "Ouvrir l'événement"
+ invitees_modal:
+ title_invited: "Participation à l'événement"
+ title_participated: "Liste des utilisateurs ayant participé"
+ filter_placeholder: "Filtrer les utilisateurs"
+ remove_invitee: "Supprimer l'invité de la liste"
+ add_invitee: "Ajouter l'invité à la liste"
+ bulk_invite_modal:
+ confirm: "confirmer"
+ text: "Téléverser un fichier CSV"
+ title: "Invitation groupée"
+ success: "Le téléversement du fichier est effectué. Vous recevrez un message de notification lorsque le processus sera terminé."
+ error: "Nous sommes désolés, le fichier doit être au format CSV."
+ confirmation_message: "Vous êtes sur le point d'informer tous ceux qui figurent dans le fichier téléversé."
+ description_public: "Les événements publics n'acceptent que les noms d'utilisateur pour les invitations groupées."
+ description_private: "Les événements privés n'acceptent que les noms de groupes pour les invitations groupées."
+ download_sample_csv: "Télécharger un exemple de fichier CSV"
+ send_bulk_invites: "Envoyer les invitations"
+ group_selector_placeholder: "Choisir un groupe…"
+ user_selector_placeholder: "Choisir un utilisateur…"
+ inline_title: "Invitation groupée en ligne"
+ csv_title: "Invitation groupée au format CSV"
+ upcoming_events:
+ title: "Événements à venir"
+ creator: "Créateur"
+ status: "Statut"
+ starts_at: "Commence à"
+ upcoming_events_list:
+ title: "Événements à venir"
+ empty: "Aucun événement à venir"
+ all_day: "Toute la journée"
+ error: "Échec de la récupération des événements"
+ try_again: "Réessayer"
+ view_all: "Tout voir"
+ category:
+ sort_topics_by_event_start_date: "Trier les sujets par date de début d'événement."
+ disable_topic_resorting: "Désactiver le tri des sujets."
+ settings_sections:
+ event_sorting: "Tri des événements"
+ preview:
+ more_than_one_event: "Vous ne pouvez pas avoir plus d'un événement."
+ models:
+ invitee:
+ no_users: "Aucun utilisateur trouvé"
+ status:
+ unknown: "Pas intéressé(e)"
+ going: "Participera"
+ not_going: "Ne participera pas"
+ interested: "Intéressé(e)"
+ going_count:
+ one: "%{count} utilisateur participera"
+ other: "%{count} utilisateurs participeront"
+ not_going_count:
+ one: "%{count} utilisateur ne participera pas"
+ other: "%{count} utilisateurs ne participeront pas"
+ interested_count:
+ one: "%{count} utilisateur est intéressé"
+ other: "%{count} utilisateurs sont intéressés"
+ invited_count:
+ one: "%{count} utilisateur est invité"
+ other: "%{count} utilisateurs sont invités"
+ event:
+ expired: "Expiré"
+ closed: "Fermé"
+ status:
+ standalone:
+ title: "Indépendant"
+ description: "Un événement indépendant ne peut pas être rejoint."
+ public:
+ title: "Public"
+ description: "Un événement public peut être rejoint par n'importe qui."
+ private:
+ title: "Privé"
+ description: "Un événement privé ne peut être rejoint que par les utilisateurs invités."
+ builder_modal:
+ custom_fields:
+ label: "Champs personnalisés"
+ placeholder: "Facultatif"
+ description: "Les champs personnalisés autorisés sont définis dans les paramètres du site. Les champs personnalisés sont utilisés pour transmettre des données à d'autres extensions."
+ create_event_title: "Créer un événement"
+ update_event_title: "Modifier l'événement"
+ confirm_delete: "Voulez-vous vraiment supprimer cet événement ?"
+ confirm_close: "Voulez-vous vraiment fermer cet événement ?"
+ confirm_open: "Voulez-vous vraiment ouvrir cet événement ?"
+ create: "Créer"
+ update: "Enregistrer"
+ attach: "Créer un événement"
+ add_reminder: "Ajouter un rappel"
+ timezone:
+ label: Fuseau horaire
+ remove_timezone: Pas de fuseau horaire (UTC)
+ reminders:
+ label: "Rappels"
+ types:
+ bump_topic: "remonter automatiquement le sujet"
+ notification: "informer les participants"
+ units:
+ minutes: "minutes"
+ hours: "heures"
+ days: "jours"
+ weeks: "semaines"
+ periods:
+ before: "avant"
+ after: "après"
+ recurrence:
+ label: "Périodicité"
+ none: "Pas de périodicité"
+ every_day: "Tous les jours"
+ every_month: "Chaque mois en ce jour de la semaine"
+ every_weekday: "Chaque jour de la semaine"
+ every_week: "Chaque semaine en ce jour de la semaine"
+ every_two_weeks: "Toutes les deux semaines en ce jour de la semaine"
+ every_four_weeks: "Toutes les quatre semaines en ce jour de la semaine"
+ minimal:
+ label: "Événement minimal"
+ checkbox_label: "Masquer les boutons Participera/Ne participera pas et le statut des invités"
+ url:
+ label: "URL"
+ placeholder: "Facultatif"
+ location:
+ label: "Localisation"
+ description:
+ label: "Description"
+ name:
+ label: "Nom de l'événement"
+ placeholder: "Facultatif, par défaut le titre du sujet"
+ invitees:
+ label: "Groupes invités"
+ status:
+ label: "Statut"
+ invite_user_or_group:
+ title: "Avertir les utilisateurs ou les groupes"
+ invite: "Envoyer"
diff --git a/plugins/discourse-calendar/config/locales/client.gl.yml b/plugins/discourse-calendar/config/locales/client.gl.yml
new file mode 100644
index 00000000000..86e6c0a3c58
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/client.gl.yml
@@ -0,0 +1,79 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+gl:
+ js:
+ discourse_automation:
+ triggerables:
+ event_started:
+ fields:
+ topic_id:
+ label: ID do tema
+ discourse_calendar:
+ disable_holiday: "Desactivar"
+ enable_holiday: "Activar"
+ date: "Data"
+ region:
+ none: "Ningunha"
+ toolbar_button:
+ today: "Hoxe"
+ month: "Mes"
+ week: "Semana"
+ day: "Día"
+ group_timezones:
+ search: "Buscar..."
+ discourse_post_event:
+ notifications:
+ invite_user_notification: "%{username} %{description}"
+ show_all: "Amosar todas"
+ created_by: "Creado por"
+ bulk_invite: "Convite en grupo"
+ bulk_invite_modal:
+ confirm: "confirmar"
+ title: "Convite en grupo"
+ success: "O ficheiro cargouse correctamente, notificaráselle por mensaxe cando remate o proceso."
+ error: "O ficheiro ten que ter formato CSV."
+ upcoming_events:
+ status: "Estado"
+ models:
+ event:
+ expired: "Caducou"
+ closed: "Pechado"
+ status:
+ public:
+ title: "Pública"
+ private:
+ title: "Privada"
+ builder_modal:
+ custom_fields:
+ placeholder: "Opcional"
+ create: "Crear"
+ update: "Gardar"
+ timezone:
+ label: Zona horaria
+ reminders:
+ units:
+ minutes: "minutos"
+ hours: "horas"
+ days: "días"
+ periods:
+ before: "antes"
+ after: "despois"
+ recurrence:
+ label: "Periodicidade"
+ none: "Sen periodicidade"
+ every_day: "Todos os días"
+ url:
+ label: "URL"
+ placeholder: "Opcional"
+ location:
+ label: "Localización"
+ description:
+ label: "Descrición"
+ status:
+ label: "Estado"
+ invite_user_or_group:
+ invite: "Enviar"
diff --git a/plugins/discourse-calendar/config/locales/client.he.yml b/plugins/discourse-calendar/config/locales/client.he.yml
new file mode 100644
index 00000000000..04d79fb2d71
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/client.he.yml
@@ -0,0 +1,498 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+he:
+ admin_js:
+ admin:
+ calendar: "לוח שנה"
+ site_settings:
+ categories:
+ discourse_post_event: "אירוע Discourse"
+ discourse_calendar: "לוח שנה Discourse"
+ js:
+ notifications:
+ titles:
+ event_reminder: "תזכורת לאירוע"
+ event_invitation: "הזמנה לאירוע"
+ popup:
+ event_reminder: תזכורת לאירוע
+ discourse_automation:
+ triggerables:
+ event_started:
+ title: האירוע התחיל
+ fields:
+ topic_id:
+ label: מזהה נושא
+ discourse_calendar:
+ invite_user_notification: "קיבלת הזמנה מאת %{username} אל: %{description}"
+ on_holiday: "בחג"
+ disable_holiday: "השבתה"
+ enable_holiday: "הפעלה"
+ holiday: "חג"
+ holidays:
+ header_title: "חגים"
+ pick_region_description: "נא לבחור אזור כדי לראות את החגים לאותו האזור."
+ disabled_holidays_description: "חגים מושבתים יוחרגו מלוח השנה של חגי הסגל."
+ date: "תאריך"
+ add_to_calendar: "הוספה ללוח השנה של Google"
+ toggle_timezone_offset_title: "החלפת מצב היסט אזור זמן"
+ region:
+ title: "אזור"
+ none: "ללא"
+ use_current_region: "להשתמש באזור הנוכחי"
+ names:
+ ae: "איחוד האמירויות הערביות"
+ ar: "ארגנטינה"
+ at: "אוסטריה"
+ au_act: "אוסטרליה (au_act)"
+ au_nsw: "אוסטרליה (au_nsw)"
+ au_nt: "אוסטרליה (au_nt)"
+ au_qld_brisbane: "אוסטרליה (au_qld_brisbane)"
+ au_qld_cairns: "אוסטרליה (au_qld_cairns)"
+ au_qld: "אוסטרליה (au_qld)"
+ au_sa: "אוסטרליה (au_sa)"
+ au_tas_north: "אוסטרליה (au_tas_north)"
+ au_tas_south: "אוסטרליה (au_tas_south)"
+ au_tas: "אוסטרליה (au_tas)"
+ au_vic_melbourne: "אוסטרליה (au_vic_melbourne)"
+ au_vic: "אוסטרליה (au_vic)"
+ au_wa: "אוסטרליה (au_wa)"
+ au: "אוסטרליה"
+ be_fr: "בלגיה (be_fr)"
+ be_nl: "בלגיה (be_nl)"
+ bg_bg: "בולגריה (bg_bg)"
+ bg_en: "בולגריה (bg_en)"
+ br: "ברזיל"
+ br_sp: "ברזיל (br_sp)"
+ br_spcapital: "ברזיל (br_spcapital)"
+ ca_ab: "קנדה (ca_ab)"
+ ca_bc: "קנדה (ca_bc)"
+ ca_mb: "קנדה (ca_mb)"
+ ca_nb: "קנדה (ca_nb)"
+ ca_nl: "קנדה (ca_nl)"
+ ca_ns: "קנדה (ca_ns)"
+ ca_nt: "קנדה (ca_nt)"
+ ca_nu: "קנדה (ca_nu)"
+ ca_on: "קנדה (ca_on)"
+ ca_pe: "קנדה (ca_pe)"
+ ca_qc: "קנדה (ca_qc)"
+ ca_sk: "קנדה (ca_sk)"
+ ca_yt: "קנדה (ca_yt)"
+ ca: "קנדה"
+ ch_ag: "שוויץ (ch_ag)"
+ ch_ai: "שוויץ (ch_ai)"
+ ch_ar: "שוויץ (ch_ar)"
+ ch_be: "שוויץ (ch_be)"
+ ch_bl: "שוויץ (ch_bl)"
+ ch_bs: "שוויץ (ch_bs)"
+ ch_fr: "שוויץ (ch_fr)"
+ ch_ge: "שוויץ (ch_ge)"
+ ch_gl: "שוויץ (ch_gl)"
+ ch_gr: "שוויץ (ch_gr)"
+ ch_ju: "שוויץ (ch_ju)"
+ ch_lu: "שוויץ (ch_lu)"
+ ch_ne: "שוויץ (ch_ne)"
+ ch_nw: "שוויץ (ch_nw)"
+ ch_ow: "שוויץ (ch_ow)"
+ ch_sg: "שוויץ (ch_sg)"
+ ch_sh: "שוויץ (ch_sh)"
+ ch_so: "שוויץ (ch_so)"
+ ch_sz: "שוויץ (ch_sz)"
+ ch_tg: "שוויץ (ch_tg)"
+ ch_ti: "שוויץ (ch_ti)"
+ ch_ur: "שוויץ (ch_ur)"
+ ch_vd: "שוויץ (ch_vd)"
+ ch_vs: "שוויץ (ch_vs)"
+ ch_zg: "שוויץ (ch_zg)"
+ ch_zh: "שוויץ (ch_zh)"
+ ch: "שוויץ"
+ cl: "צ׳ילה"
+ co: "קולומביה"
+ cr: "קוסטה ריקה"
+ cz: "צ׳כיה"
+ de_bb: "גרמניה (de_bb)"
+ de_be: "גרמניה (de_be)"
+ de_bw: "גרמניה (de_bw)"
+ de_by_augsburg: "גרמניה (de_by_augsburg)"
+ de_by_cath: "גרמניה (de_by_cath)"
+ de_by: "גרמניה (de_by)"
+ de_hb: "גרמניה (de_hb)"
+ de_he: "גרמניה (de_he)"
+ de_hh: "גרמניה (de_hh)"
+ de_mv: "גרמניה (de_mv)"
+ de_ni: "גרמניה (de_ni)"
+ de_nw: "גרמניה (de_nw)"
+ de_rp: "גרמניה (de_rp)"
+ de_sh: "גרמניה (de_sh)"
+ de_sl: "גרמניה (de_sl)"
+ de_sn_sorbian: "גרמניה (de_sn_sorbian)"
+ de_sn: "גרמניה (de_sn)"
+ de_st: "גרמניה (de_st)"
+ de_th_cath: "גרמניה (de_th_cath)"
+ de_th: "גרמניה (de_th)"
+ de: "גרמניה"
+ dk: "דנמרק"
+ ee: "אסטוניה"
+ el: "יוון"
+ es_an: "ספרד (es_an)"
+ es_ar: "ספרד (es_ar)"
+ es_ce: "ספרד (es_ce)"
+ es_cl: "ספרד (es_cl)"
+ es_cm: "ספרד (es_cm)"
+ es_cn: "ספרד (es_cn)"
+ es_ct: "ספרד (es_ct)"
+ es_ex: "ספרד (es_ex)"
+ es_ga: "ספרד (es_ga)"
+ es_ib: "ספרד (es_ib)"
+ es_lo: "ספרד (es_lo)"
+ es_m: "ספרד (es_m)"
+ es_mu: "ספרד (es_mu)"
+ es_na: "ספרד (es_na)"
+ es_o: "ספרד (es_o)"
+ es_pv: "ספרד (es_pv)"
+ es_v: "ספרד (es_v)"
+ es_vc: "ספרד (es_vc)"
+ es: "ספרד"
+ fi: "פינלנד"
+ fr_a: "צרפת (fr_a)"
+ fr_m: "צרפת (fr_m)"
+ fr: "צרפת"
+ gb_con: "אנגליה (gb_con)"
+ gb_eaw: "אנגליה (gb_eaw)"
+ gb_eng: "אנגליה (gb_eng)"
+ gb_gsy: "אנגליה (gb_gsy)"
+ gb_iom: "אנגליה (gb_iom)"
+ gb_jsy: "אנגליה (gb_jsy)"
+ gb_nir: "אנגליה (gb_nir)"
+ gb_sct: "אנגליה (gb_sct)"
+ gb_wls: "אנגליה (gb_wls)"
+ gb: "אנגליה"
+ ge: "גאורגיה"
+ gg: "גרנזי"
+ gh: "גאנה"
+ hk: "הונג קונג"
+ hr: "קרואטיה"
+ hu: "הונגריה"
+ id: "אינדונזיה"
+ ie: "אירלנד"
+ im: "האי מאן"
+ in: "הודו"
+ in_gj: "הודו (in_gj)"
+ in_mh: "הודו (in_mh)"
+ in_rj: "הודו (in_rj)"
+ in_tn: "הודו (in_tn)"
+ in_ka: "הודו (in_ka)"
+ is: "איסלנד"
+ it_bl: "איטליה (it_bl)"
+ it_fi: "איטליה (it_fi)"
+ it_ge: "איטליה (it_ge)"
+ it_pd: "איטליה (it_pd)"
+ it_rm: "איטליה (it_rm)"
+ it_ro: "איטליה (it_ro)"
+ it_to: "איטליה (it_to)"
+ it_tv: "איטליה (it_tv)"
+ it_ve: "איטליה (it_ve)"
+ it_vi: "איטליה (it_vi)"
+ it_vr: "איטליה (it_vr)"
+ it: "איטליה"
+ je: "ג׳רזי"
+ jp: "יפן"
+ ke: "קניה"
+ kr: "קוריאה (הרפובליקה של)"
+ kz: "קזחסטן (הרפובליקה של)"
+ li: "ליכטנשטיין"
+ lt: "ליטא"
+ lu: "לוקסמבורג"
+ lv: "לטביה"
+ ma: "מרוקו"
+ mt_en: "מלטה (mt_en)"
+ mt_mt: "מלטה (mt_mt)"
+ mx_pue: "מקסיקו (mx_pue)"
+ mx: "מקסיקו"
+ my: "מלזיה"
+ ng: "ניגריה"
+ nl: "הולנד"
+ "no": "נורווגיה"
+ nz_ak: "ניו זילנד (nz_ak)"
+ nz_ca: "ניו זילנד (nz_ca)"
+ nz_ch: "ניו זילנד (nz_ch)"
+ nz_hb: "ניו זילנד (nz_hb)"
+ nz_mb: "ניו זילנד (nz_mb)"
+ nz_ne: "ניו זילנד (nz_ne)"
+ nz_nl: "ניו זילנד (nz_nl)"
+ nz_ot: "ניו זילנד (nz_ot)"
+ nz_sc: "ניו זילנד (nz_sc)"
+ nz_sl: "ניו זילנד (nz_sl)"
+ nz_ta: "ניו זילנד (nz_ta)"
+ nz_we: "ניו זילנד (nz_we)"
+ nz_wl: "ניו זילנד (nz_wl)"
+ nz: "ניו זילנד"
+ pe: "פרו"
+ ph: "הפיליפינים"
+ pl: "פולין"
+ pt_li: "פורטוגל (pt_li)"
+ pt_po: "פורטוגל (pt_po)"
+ pt: "פורטוגל"
+ ro: "רומניה"
+ rs_cyrl: "סרביה (rs_cyrl)"
+ rs_la: "סרביה (rs_la)"
+ ru: "הפדרציה הרוסית"
+ se: "שבדיה"
+ sa: "ערב הסעודית"
+ sg: "סינגפור"
+ si: "סלובניה"
+ sk: "סלובקיה"
+ th: "תאילנד"
+ tn: "תוניסיה"
+ tr: "טורקיה"
+ ua: "אוקראינה"
+ us_ak: "ארצות הברית (us_ak)"
+ us_al: "ארצות הברית (us_al)"
+ us_ar: "ארצות הברית (us_ar)"
+ us_az: "ארצות הברית (us_az)"
+ us_ca: "ארצות הברית (us_ca)"
+ us_co: "ארצות הברית (us_co)"
+ us_ct: "ארצות הברית (us_ct)"
+ us_dc: "ארצות הברית (us_dc)"
+ us_de: "ארצות הברית (us_de)"
+ us_fl: "ארצות הברית (us_fl)"
+ us_ga: "ארצות הברית (us_ga)"
+ us_gu: "ארצות הברית (us_gu)"
+ us_hi: "ארצות הברית (us_hi)"
+ us_ia: "ארצות הברית (us_ia)"
+ us_id: "ארצות הברית (us_id)"
+ us_il: "ארצות הברית (us_il)"
+ us_in: "ארצות הברית (us_in)"
+ us_ks: "ארצות הברית (us_ks)"
+ us_ky: "ארצות הברית (us_ky)"
+ us_la: "ארצות הברית (us_la)"
+ us_ma: "ארצות הברית (us_ma)"
+ us_md: "ארצות הברית (us_md)"
+ us_me: "ארצות הברית (us_me)"
+ us_mi: "ארצות הברית (us_mi)"
+ us_mn: "ארצות הברית (us_mn)"
+ us_mo: "ארצות הברית (us_mo)"
+ us_ms: "ארצות הברית (us_ms)"
+ us_mt: "ארצות הברית (us_mt)"
+ us_nc: "ארצות הברית (us_nc)"
+ us_nd: "ארצות הברית (us_nd)"
+ us_ne: "ארצות הברית (us_ne)"
+ us_nh: "ארצות הברית (us_nh)"
+ us_nj: "ארצות הברית (us_nj)"
+ us_nm: "ארצות הברית (us_nm)"
+ us_nv: "ארצות הברית (us_nv)"
+ us_ny: "ארצות הברית (us_ny)"
+ us_oh: "ארצות הברית (us_oh)"
+ us_ok: "ארצות הברית (us_ok)"
+ us_or: "ארצות הברית (us_or)"
+ us_pa: "ארצות הברית (us_pa)"
+ us_pr: "ארצות הברית (us_pr)"
+ us_ri: "ארצות הברית (us_ri)"
+ us_sc: "ארצות הברית (us_sc)"
+ us_sd: "ארצות הברית (us_sd)"
+ us_tn: "ארצות הברית (us_tn)"
+ us_tx: "ארצות הברית (us_tx)"
+ us_ut: "ארצות הברית (us_ut)"
+ us_va: "ארצות הברית (us_va)"
+ us_vi: "ארצות הברית (us_vi)"
+ us_vt: "ארצות הברית (us_vt)"
+ us_wa: "ארצות הברית (us_wa)"
+ us_wi: "ארצות הברית (us_wi)"
+ us_wv: "ארצות הברית (us_wv)"
+ us_wy: "ארצות הברית (us_wy)"
+ us: "ארצות הברית"
+ ve: "ונצואלה"
+ vi: "איי הבתולה (ארה״ב)"
+ za: "דרום אפריקה"
+ toolbar_button:
+ today: "היום"
+ month: "חודש"
+ week: "שבוע"
+ day: "יום"
+ list: "רשימה"
+ group_timezones:
+ search: "חיפוש…"
+ group_availability: "זמינות %{group}"
+ discourse_post_event:
+ notifications:
+ before_event_reminder: "אירוע עומד להתחיל"
+ after_event_reminder: "אירוע הסתיים"
+ ongoing_event_reminder: "אירוע מתקיים"
+ invite_user_notification: "%{username} %{description}"
+ invite_user_predefined_attendance_notification_html: "הנוכחות שלך הוגדרה על ידי %{username} שגם הזמין או הזמינה אותך אל %{description}"
+ before_event_reminder_html: "אירוע עומד להתחיל %{description}"
+ after_event_reminder_html: "אירוע הסתיים %{description}"
+ ongoing_event_reminder_html: "אירוע מתקיים %{description}"
+ edit_reason: "האירוע עודכן"
+ edit_reason_closed: "אירוע נסגר"
+ edit_reason_opened: "אירוע נפתח"
+ topic_title:
+ starts_at: "האירוע יתחיל: %{date}"
+ ended_at: "האירוע הסתיים: %{date}"
+ ends_in_duration: "מסתיים %{duration}"
+ show_all: "להציג הכול"
+ show_participants: "הצגת משתתפים"
+ participants:
+ one: "משתמש %{count} השתתף."
+ two: "%{count} משתמשים השתתפו."
+ many: "%{count} משתמשים השתתפו."
+ other: "%{count} משתמשים השתתפו."
+ invite: "הודעה למשתמש"
+ add_to_calendar: "הוספה ללוח השנה"
+ send_pm_to_creator: "שליחת הודעה פרטית על %{username}"
+ leave: "עזיבת האירוע"
+ edit_event: "עריכת אירוע"
+ export_event: "ייצוא אירוע"
+ created_by: "נוצר על ידי"
+ bulk_invite: "הזמנה מרוכזת"
+ close_event: "סגירת אירוע"
+ open_event: "פתיחת אירוע?"
+ invitees_modal:
+ title_invited: "השתתפות באירועים"
+ title_participated: "רשימת משתמשים שהשתתפו"
+ filter_placeholder: "סינון משתמשים"
+ remove_invitee: "הסרת מוזמנים מהרשימה"
+ add_invitee: "הוספת מוזמנים לרשימה"
+ bulk_invite_modal:
+ confirm: "אישור"
+ text: "העלאת קובץ CSV"
+ title: "הזמנה מרוכזת"
+ success: "העלאת הקובץ החלה בהצלחה, תישלח הודעה כאשר התהליך יושלם."
+ error: "הקובץ אמור להיות בתצורת CSV, עמך הסליחה."
+ confirmation_message: "פעולה זו תשלח הודעה לכל מי שבקובץ שהועלה."
+ description_public: "אירועים ציבוריים מקבלים רק שמות משתמשים עבור הזמנות מרוכזות."
+ description_private: "אירועים פרטיים מקבלים רק שמות קבוצות עבור הזמנות מרוכזות."
+ download_sample_csv: "הורדת קובץ CSV לדוגמה"
+ send_bulk_invites: "משלוח הזמנות"
+ group_selector_placeholder: "בחירת קבוצה…"
+ user_selector_placeholder: "בחירת משתמש…"
+ inline_title: "הזמנה מרוכזת בשורה"
+ csv_title: "הזמנה מרוכזת עם CSV"
+ upcoming_events:
+ title: "אירועים קרובים"
+ creator: "יוצר"
+ status: "מצב"
+ starts_at: "מועד התחלה"
+ upcoming_events_list:
+ title: "אירועים קרובים"
+ empty: "אין אירועים בקרוב"
+ all_day: "יום שלם"
+ error: "משיכת האירועים נכשלה"
+ try_again: "לנסות שוב"
+ view_all: "להציג הכול"
+ category:
+ sort_topics_by_event_start_date: "מיון נושאים לפי תאריך תחילת האירוע."
+ disable_topic_resorting: "השבתת מיון נושאים מחדש."
+ settings_sections:
+ event_sorting: "מיון אירועים"
+ preview:
+ more_than_one_event: "לא יכול להיות לך יותר מאירוע אחד."
+ models:
+ invitee:
+ no_users: "לא נמצאו משתמשים"
+ status:
+ unknown: "לא מעניין"
+ going: "אשתתף"
+ not_going: "לא אשתתף"
+ interested: "מעניין"
+ going_count:
+ one: "%{count} מגיע/ה"
+ two: "%{count} מגיעים/ות"
+ many: "%{count} מגיעים/ות"
+ other: "%{count} מגיעים/ות"
+ not_going_count:
+ one: "%{count} לא מגיע/ה"
+ two: "%{count} לא מגיעים/ות"
+ many: "%{count} לא מגיעים/ות"
+ other: "%{count} לא מגיעים/ות"
+ interested_count:
+ one: "%{count} מתעניין/ת"
+ two: "%{count} מתעניינים/ות"
+ many: "%{count} מתעניינים/ות"
+ other: "%{count} מתעניינים/ות"
+ invited_count:
+ one: "משתמש/ת %{count} הוזמן/ה"
+ two: "%{count} משתמשים/ות הוזמנו"
+ many: "%{count} משתמשים/ות הוזמנו"
+ other: "%{count} משתמשים/ות הוזמנו"
+ event:
+ expired: "לא בתוקף"
+ closed: "סגור"
+ status:
+ standalone:
+ title: "עצמאי"
+ description: "אי אפשר להצטרף לאירוע עצמאי."
+ public:
+ title: "ציבורי"
+ description: "לאירוע ציבורי כולם יכולים להצטרף."
+ private:
+ title: "פרטי"
+ description: "לאירוע פרטי יכולים להצטרף רק משתמשים שהוזמנו."
+ builder_modal:
+ custom_fields:
+ label: "שדות משלך"
+ placeholder: "רשות"
+ description: "השדות המותאמים אישית המורשים מוגדרים בהגדרות האתר. שדות מותאומים אישית משמשים להעביר נתונים לתוספים אחרים."
+ create_event_title: "יצירת אירוע"
+ update_event_title: "עריכת אירוע"
+ confirm_delete: "למחוק את האירוע הזה?"
+ confirm_close: "לסגור את האירוע הזה?"
+ confirm_open: "לפתוח את האירוע הזה?"
+ create: "יצירה"
+ update: "שמירה"
+ attach: "יצירת אירוע"
+ add_reminder: "הוספת תזכורת"
+ show_local_time:
+ label: "הצגת זמן מקומי"
+ description: "תאריכים ושעות יוצגו באמצעות: %{timezone}. יש להשתמש באפשרות הזאת עבור אירועים במיקום מסוים, כך שהזמנים ישקפו את אזור הזמן שבו האירוע מתרחש."
+ timezone:
+ label: אזור זמן
+ remove_timezone: אין אזור זמן (UTC)
+ reminders:
+ label: "תזכורות"
+ types:
+ bump_topic: "הקפצת נושא אוטומטית"
+ notification: "הודעה למשתתפים"
+ units:
+ minutes: "דקות"
+ hours: "שעות"
+ days: "ימים"
+ weeks: "שבועות"
+ periods:
+ before: "לפני"
+ after: "אחרי"
+ recurrence:
+ label: "חזרה"
+ none: "ללא חזרה"
+ every_day: "כל יום"
+ every_month: "כל חודש ביום הזה בשבוע"
+ every_weekday: "כל יום חול"
+ every_week: "כל שבוע ביום הזה בשבוע"
+ every_two_weeks: "כל שבועיים ביום הזה בשבוע"
+ every_four_weeks: "כל ארבעה שבועות ביום הזה בשבוע"
+ minimal:
+ label: "אירוע מזערי"
+ checkbox_label: "הסתרת כפתורי הצטרפות/העדרות ומצב מוזמנים"
+ allow_chat:
+ label: "שילוב צ׳אט"
+ checkbox_label: "יצירה וניהול של ערוץ צ׳אט ייעודי לאירוע"
+ url:
+ label: "כתובת"
+ placeholder: "רשות"
+ location:
+ label: "מיקום"
+ description:
+ label: "תיאור"
+ name:
+ label: "שם האירוע"
+ placeholder: "רשות, ברירת המחדל היא כותרת הנושא"
+ invitees:
+ label: "קבוצות שהוזמנו"
+ status:
+ label: "מצב"
+ invite_user_or_group:
+ title: "להודיע למשתמשים או לקבוצות"
+ invite: "שליחה"
diff --git a/plugins/discourse-calendar/config/locales/client.hr.yml b/plugins/discourse-calendar/config/locales/client.hr.yml
new file mode 100644
index 00000000000..856ced187de
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/client.hr.yml
@@ -0,0 +1,80 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+hr:
+ js:
+ discourse_automation:
+ triggerables:
+ event_started:
+ fields:
+ topic_id:
+ label: ID teme
+ discourse_calendar:
+ disable_holiday: "Onemogući"
+ enable_holiday: "Omogućiti"
+ date: "Datum"
+ region:
+ none: "Ništa"
+ toolbar_button:
+ today: "Danas"
+ month: "Mjesec"
+ week: "Tjedan"
+ day: "Dan"
+ group_timezones:
+ search: "Pretraži..."
+ discourse_post_event:
+ notifications:
+ invite_user_notification: "%{username} %{description}"
+ show_all: "Prikaži sve"
+ add_to_calendar: "Dodaj u kalendar"
+ created_by: "Napravio"
+ bulk_invite: "Skupna pozivnica"
+ bulk_invite_modal:
+ confirm: "potvrdi"
+ title: "Skupna pozivnica"
+ success: "Datoteka je uspješno učitana, biti ćete obaviješteni porukom kada je proces završen."
+ error: "Nažalost, datoteka bi trebala biti u CSV formatu."
+ upcoming_events:
+ status: "Statust"
+ models:
+ event:
+ expired: "Isteklo"
+ closed: "Zatvoreno"
+ status:
+ public:
+ title: "Javnost"
+ private:
+ title: "Privatno"
+ builder_modal:
+ custom_fields:
+ placeholder: "Neobvezno"
+ create: "Kreiraj"
+ update: "Spremi"
+ timezone:
+ label: Vremenska zona
+ reminders:
+ units:
+ minutes: "minute"
+ hours: "sata"
+ days: "dana"
+ periods:
+ before: "prije"
+ after: "poslije"
+ recurrence:
+ label: "Ponavljanje"
+ none: "Nema ponavljanja"
+ every_day: "Svaki dan"
+ url:
+ label: "URL"
+ placeholder: "Neobvezno"
+ location:
+ label: "Lokacija"
+ description:
+ label: "Opis"
+ status:
+ label: "Statust"
+ invite_user_or_group:
+ invite: "Pošalji"
diff --git a/plugins/discourse-calendar/config/locales/client.hu.yml b/plugins/discourse-calendar/config/locales/client.hu.yml
new file mode 100644
index 00000000000..cad1e75cf13
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/client.hu.yml
@@ -0,0 +1,418 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+hu:
+ admin_js:
+ admin:
+ site_settings:
+ categories:
+ discourse_post_event: "Discourse esemény"
+ discourse_calendar: "Discourse naptár"
+ js:
+ notifications:
+ titles:
+ event_reminder: "esemény emlékeztető"
+ popup:
+ event_reminder: Esemény emlékeztető
+ discourse_automation:
+ triggerables:
+ event_started:
+ title: Az esemény elkezdődött
+ fields:
+ topic_id:
+ label: Témaazonosító
+ discourse_calendar:
+ invite_user_notification: "%{username} meghívta erre: %{description}"
+ on_holiday: "Szabadságon"
+ disable_holiday: "Kikapcsol"
+ enable_holiday: "Engedélyez"
+ holiday: "Ünnep"
+ date: "Dátum"
+ add_to_calendar: "Hozzáadás a Google Naptárhoz"
+ region:
+ title: "Régió"
+ none: "Egyik sem"
+ use_current_region: "Jelenlegi régió használata"
+ names:
+ ar: "Argentína"
+ at: "Ausztria"
+ au_act: "Ausztrália (au_act)"
+ au_nsw: "Ausztrália (au_nsw)"
+ au_nt: "Ausztrália (au_nt)"
+ au_qld_brisbane: "Ausztrália (au_qld_brisbane)"
+ au_qld_cairns: "Ausztrália (au_qld_cairns)"
+ au_qld: "Ausztrália (au_qld)"
+ au_sa: "Ausztrália (au_sa)"
+ au_tas_north: "Ausztrália (au_tas_north)"
+ au_tas_south: "Ausztrália (au_tas_south)"
+ au_tas: "Ausztrália (au_tas)"
+ au_vic_melbourne: "Ausztrália (au_vic_melbourne)"
+ au_vic: "Ausztrália (au_vic)"
+ au_wa: "Ausztrália (au_wa)"
+ au: "Ausztrália"
+ be_fr: "Belgium (be_fr)"
+ be_nl: "Belgium (be_nl)"
+ bg_bg: "Bulgária (bg_bg)"
+ bg_en: "Bulgária (bg_hu)"
+ br: "Brazília"
+ ca_ab: "Kanada (ca_ab)"
+ ca_bc: "Kanada (ca_bc)"
+ ca_mb: "Kanada (ca_mb)"
+ ca_nb: "Kanada (ca_nb)"
+ ca_nl: "Kanada (ca_nl)"
+ ca_ns: "Kanada (ca_ns)"
+ ca_nt: "Kanada (ca_nt)"
+ ca_nu: "Kanada (ca_nu)"
+ ca_on: "Kanada (ca_on)"
+ ca_pe: "Kanada (ca_pe)"
+ ca_qc: "Kanada (ca_qc)"
+ ca_sk: "Kanada (ca_sk)"
+ ca_yt: "Kanada (ca_yt)"
+ ca: "Kanada"
+ ch_ag: "Svájc (ch_ag)"
+ ch_ai: "Svájc (ch_ai)"
+ ch_ar: "Svájc (ch_ar)"
+ ch_be: "Svájc (ch_be)"
+ ch_bl: "Svájc (ch_bl)"
+ ch_bs: "Svájc (ch_bs)"
+ ch_fr: "Svájc (ch_fr)"
+ ch_ge: "Svájc (ch_ge)"
+ ch_gl: "Svájc (ch_gl)"
+ ch_gr: "Svájc (ch_gr)"
+ ch_ju: "Svájc (ch_ju)"
+ ch_lu: "Svájc (ch_lu)"
+ ch_ne: "Svájc (ch_ne)"
+ ch_nw: "Svájc (ch_nw)"
+ ch_ow: "Svájc (ch_ow)"
+ ch_sg: "Svájc (ch_sg)"
+ ch_sh: "Svájc (ch_sh)"
+ ch_so: "Svájc (ch_so)"
+ ch_sz: "Svájc (ch_sz)"
+ ch_tg: "Svájc (ch_tg)"
+ ch_ti: "Svájc (ch_ti)"
+ ch_ur: "Svájc (ch_ur)"
+ ch_vd: "Svájc (ch_vd)"
+ ch_vs: "Svájc (ch_vs)"
+ ch_zg: "Svájc (ch_zg)"
+ ch_zh: "Svájc (ch_zh)"
+ ch: "Svájc"
+ cl: "Chile"
+ co: "Kolumbia"
+ cr: "Costa Rica"
+ cz: "Cseh Köztársaság"
+ de_bb: "Németország (de_bb)"
+ de_be: "Németország (de_be)"
+ de_bw: "Németország (de_bw)"
+ de_by_augsburg: "Németország (de_by_augsburg)"
+ de_by_cath: "Németország (de_by_cath)"
+ de_by: "Németország (de_by)"
+ de_hb: "Németország (de_hb)"
+ de_he: "Németország (de_he)"
+ de_hh: "Németország (de_hh)"
+ de_mv: "Németország (de_mv)"
+ de_ni: "Németország (de_ni)"
+ de_nw: "Németország (de_nw)"
+ de_rp: "Németország (de_rp)"
+ de_sh: "Németország (de_sh)"
+ de_sl: "Németország (de_sl)"
+ de_sn_sorbian: "Németország (de_sn_sorbian)"
+ de_sn: "Németország (de_sn)"
+ de_st: "Németország (de_st)"
+ de_th_cath: "Németország (de_th_cath)"
+ de_th: "Németország (de_th)"
+ de: "Németország"
+ dk: "Dánia"
+ ee: "Észtország"
+ el: "Görögország"
+ es_an: "Spanyolország (es_an)"
+ es_ar: "Spanyolország (es_ar)"
+ es_ce: "Spanyolország (es_ce)"
+ es_cl: "Spanyolország (es_cl)"
+ es_cm: "Spanyolország (es_cm)"
+ es_cn: "Spanyolország (es_cn)"
+ es_ct: "Spanyolország (es_ct)"
+ es_ex: "Spanyolország (es_ex)"
+ es_ga: "Spanyolország (es_ga)"
+ es_ib: "Spanyolország (es_ib)"
+ es_lo: "Spanyolország (es_lo)"
+ es_m: "Spanyolország (es_m)"
+ es_mu: "Spanyolország (es_mu)"
+ es_na: "Spanyolország (es_na)"
+ es_o: "Spanyolország (es_o)"
+ es_pv: "Spanyolország (es_pv)"
+ es_v: "Spanyolország (es_v)"
+ es_vc: "Spanyolország (es_vc)"
+ es: "Spanyolország"
+ fi: "Finnország"
+ fr_a: "Franciaország (fr_a)"
+ fr_m: "Franciaország (fr_m)"
+ fr: "Franciaország"
+ gb_con: "Egyesült Királyság (gb_con)"
+ gb_eaw: "Egyesült Királyság (gb_eaw)"
+ gb_eng: "Egyesült Királyság (gb_eng)"
+ gb_gsy: "Egyesült Királyság (gb_gsy)"
+ gb_iom: "Egyesült Királyság (gb_iom)"
+ gb_jsy: "Egyesült Királyság (gb_jsy)"
+ gb_nir: "Egyesült Királyság (gb_nir)"
+ gb_sct: "Egyesült Királyság (gb_sct)"
+ gb_wls: "Egyesült Királyság (gb_wls)"
+ gb: "Egyesült Királyság"
+ ge: "Grúzia"
+ gg: "Guernsey"
+ hk: "Hongkong"
+ hr: "Horvátország"
+ hu: "Magyarország"
+ ie: "Írország"
+ im: "Man-sziget"
+ in: "India"
+ is: "Izland"
+ it_bl: "Olaszország (it_bl)"
+ it_fi: "Olaszország (it_fi)"
+ it_ge: "Olaszország (it_ge)"
+ it_pd: "Olaszország (it_pd)"
+ it_rm: "Olaszország (it_rm)"
+ it_ro: "Olaszország (it_ro)"
+ it_to: "Olaszország (it_to)"
+ it_tv: "Olaszország (it_tv)"
+ it_ve: "Olaszország (it_ve)"
+ it_vi: "Olaszország (it_vi)"
+ it_vr: "Olaszország (it_vr)"
+ it: "Olaszország"
+ je: "Jersey"
+ jp: "Japán"
+ kr: "Dél-Korea"
+ li: "Liechtenstein"
+ lt: "Litvánia"
+ lu: "Luxemburg"
+ lv: "Lettország"
+ ma: "Marokkó"
+ mt_en: "Málta (mt_en)"
+ mt_mt: "Málta (mt_mt)"
+ mx_pue: "Mexikó (mx_pue)"
+ mx: "Mexikó"
+ my: "Malajzia"
+ ng: "Nigéria"
+ nl: "Hollandia"
+ "no": "Norvégia"
+ nz_ak: "Új-Zéland (nz_ak)"
+ nz_ca: "Új-Zéland (nz_ca)"
+ nz_ch: "Új-Zéland (nz_ch)"
+ nz_hb: "Új-Zéland (nz_hb)"
+ nz_mb: "Új-Zéland (nz_mb)"
+ nz_ne: "Új-Zéland (nz_ne)"
+ nz_nl: "Új-Zéland (nz_nl)"
+ nz_ot: "Új-Zéland (nz_ot)"
+ nz_sc: "Új-Zéland (nz_sc)"
+ nz_sl: "Új-Zéland (nz_sl)"
+ nz_ta: "Új-Zéland (nz_ta)"
+ nz_we: "Új-Zéland (nz_we)"
+ nz_wl: "Új-Zéland (nz_wl)"
+ nz: "Új-Zéland"
+ pe: "Peru"
+ ph: "Fülöp-szigetek"
+ pl: "Lengyelország"
+ pt_li: "Portugália (pt_li)"
+ pt_po: "Portugália (pt_po)"
+ pt: "Portugália"
+ ro: "Románia"
+ rs_cyrl: "Szerbia (rs_cyrl)"
+ rs_la: "Szerbia (rs_la)"
+ ru: "Orosz Föderáció"
+ se: "Svédország"
+ sa: "Szaúd-Arábia"
+ sg: "Szingapúr"
+ si: "Szlovénia"
+ sk: "Szlovákia"
+ th: "Thaiföld"
+ tn: "Tunézia"
+ tr: "Törökország"
+ ua: "Ukrajna"
+ us_ak: "Egyesült Államok (us_ak)"
+ us_al: "Egyesült Államok (us_al)"
+ us_ar: "Egyesült Államok (us_ar)"
+ us_az: "Egyesült Államok (us_az)"
+ us_ca: "Egyesült Államok (us_ca)"
+ us_co: "Egyesült Államok (us_co)"
+ us_ct: "Egyesült Államok (us_ct)"
+ us_dc: "Egyesült Államok (us_dc)"
+ us_de: "Egyesült Államok (us_de)"
+ us_fl: "Egyesült Államok (us_fl)"
+ us_ga: "Egyesült Államok (us_ga)"
+ us_gu: "Egyesült Államok (us_gu)"
+ us_hi: "Egyesült Államok (us_hi)"
+ us_ia: "Egyesült Államok (us_ia)"
+ us_id: "Egyesült Államok (us_id)"
+ us_il: "Egyesült Államok (us_il)"
+ us_in: "Egyesült Államok (us_in)"
+ us_ks: "Egyesült Államok (us_al)"
+ us_ky: "Egyesült Államok (us_ky)"
+ us_la: "Egyesült Államok (us_la)"
+ us_ma: "Egyesült Államok (us_ma)"
+ us_md: "Egyesült Államok (us_md)"
+ us_me: "Egyesült Államok (us_me)"
+ us_mi: "Egyesült Államok (us_mi)"
+ us_mn: "Egyesült Államok (us_mn)"
+ us_mo: "Egyesült Államok (us_mo)"
+ us_ms: "Egyesült Államok (us_ms)"
+ us_mt: "Egyesült Államok (us_mt)"
+ us_nc: "Egyesült Államok (us_nc)"
+ us_nd: "Egyesült Államok (us_nd)"
+ us_ne: "Egyesült Államok (us_ne)"
+ us_nh: "Egyesült Államok (us_nh)"
+ us_nj: "Egyesült Államok (us_nj)"
+ us_nm: "Egyesült Államok (us_nm)"
+ us_nv: "Egyesült Államok (us_nv)"
+ us_ny: "Egyesült Államok (us_ny)"
+ us_oh: "Egyesült Államok (us_oh)"
+ us_ok: "Egyesült Államok (us_ok)"
+ us_or: "Egyesült Államok (us_or)"
+ us_pa: "Egyesült Államok (us_pa)"
+ us_pr: "Egyesült Államok (us_pr)"
+ us_ri: "Egyesült Államok (us_ri)"
+ us_sc: "Egyesült Államok (us_sc)"
+ us_sd: "Egyesült Államok (us_sd)"
+ us_tn: "Egyesült Államok (us_tn)"
+ us_tx: "Egyesült Államok (us_tx)"
+ us_ut: "Egyesült Államok (us_ut)"
+ us_va: "Egyesült Államok (us_va)"
+ us_vi: "Egyesült Államok (us_vi)"
+ us_vt: "Egyesült Államok (us_vt)"
+ us_wa: "Egyesült Államok (us_wa)"
+ us_wi: "Egyesült Államok (us_wi)"
+ us_wv: "Egyesült Államok (us_wv)"
+ us_wy: "Egyesült Államok (us_wy)"
+ us: "Egyesült Államok"
+ ve: "Venezuela"
+ vi: "Virgin-szigetek (USA)"
+ za: "Dél-afrikai Köztársaság"
+ toolbar_button:
+ today: "Ma"
+ month: "Utolsó 30 nap"
+ week: "Utolsó 7 nap"
+ day: "Nap"
+ group_timezones:
+ search: "Keresés…"
+ group_availability: "%{group} elérhetősége"
+ discourse_post_event:
+ notifications:
+ invite_user_notification: "%{username} %{description}"
+ invite_user_predefined_attendance_notification_html: "%{username} automatikusan beállította a részvételét, és meghívta erre: %{description}"
+ before_event_reminder_html: "Egy esemény hamarosan elkezdődik: %{description}"
+ after_event_reminder_html: "Egy esemény véget ért: %{description}"
+ ongoing_event_reminder_html: "Egy esemény folyamatban van: %{description}"
+ edit_reason: "Esemény frissítve"
+ topic_title:
+ starts_at: "Az esemény kezdete: %{date}"
+ ended_at: "Az esemény véget ért: %{date}"
+ ends_in_duration: "Véget ér: %{duration}"
+ show_all: "Mutasd mindet"
+ participants:
+ one: "%{count} felhasználó vett részt."
+ other: "%{count} felhasználó vett részt."
+ invite: "Felhasználó értesítése"
+ add_to_calendar: "Hozzáadás a naptárhoz"
+ send_pm_to_creator: "PÜ küldése neki: %{username}"
+ edit_event: "Esemény szerkesztése"
+ export_event: "Esemény exportálása"
+ created_by: "Létrehozta:"
+ bulk_invite: "Csoportos meghívás"
+ close_event: "Esemény lezárása"
+ invitees_modal:
+ title_participated: "A részt vett felhasználók listája"
+ filter_placeholder: "Felhasználók szűrése"
+ bulk_invite_modal:
+ confirm: "megerősítés"
+ text: "CSV-fájl feltöltése"
+ title: "Csoportos meghívás"
+ success: "A fájl sikeresen feltöltve, értesítést fog kapni, ha a folyamat befejeződött."
+ error: "Sajnáljuk, a fájlnak CSV formátumúnak kell lennie."
+ confirmation_message: "Arra készül, hogy mindenkit értesítsen a feltöltött fájlban."
+ description_public: "A nyilvános események csak a csoportos meghíváshoz fogadnak el felhasználóneveket."
+ description_private: "A privát események csak a csoportos meghíváshoz fogadnak el csoportneveket."
+ download_sample_csv: "Minta CSV-fájl letöltése"
+ send_bulk_invites: "Meghívók küldése"
+ group_selector_placeholder: "Válasszon csoportot…"
+ user_selector_placeholder: "Válasszon felhasználót…"
+ inline_title: "Soron belüli csoportos meghívás"
+ csv_title: "Csoportos meghívás CSV-ből"
+ upcoming_events:
+ title: "Közelgő események"
+ creator: "Létrehozó"
+ status: "Állapot"
+ starts_at: "Kezdődik:"
+ upcoming_events_list:
+ title: "Közelgő események"
+ preview:
+ more_than_one_event: "Nem lehet egynél több eseménye."
+ models:
+ invitee:
+ status:
+ unknown: "Nem érdekli"
+ going: "Megy"
+ not_going: "Nem megy"
+ interested: "Érdeklődik"
+ event:
+ expired: "Lejárt"
+ closed: "Zárt"
+ status:
+ standalone:
+ title: "Önálló"
+ description: "Önálló eseményhez nem lehet csatlakozni."
+ public:
+ title: "Nyilvános"
+ description: "Nyilvános eseményhez bárki csatlakozhat."
+ private:
+ title: "Privát"
+ description: "Privát eseményhez csak meghívott felhasználók csatlakozhatnak."
+ builder_modal:
+ custom_fields:
+ label: "Egyéni mezők"
+ placeholder: "Nem kötelező"
+ create_event_title: "Esemény létrehozása"
+ update_event_title: "Esemény szerkesztése"
+ confirm_delete: "Biztos, hogy törli ezt az eseményt?"
+ confirm_close: "Biztos, hogy lezárja ezt az eseményt?"
+ create: "Létrehozás"
+ update: "Mentés"
+ attach: "Esemény létrehozása"
+ add_reminder: "Emlékeztető hozzáadása"
+ timezone:
+ label: Időzóna
+ remove_timezone: Nincs időzóna (UTC)
+ reminders:
+ label: "Emlékeztetők"
+ units:
+ minutes: "perc"
+ hours: "óra"
+ days: "nap"
+ periods:
+ before: "előtte"
+ after: "utána"
+ recurrence:
+ label: "Ismétlődés"
+ none: "Nincs ismétlődés"
+ every_day: "Naponta"
+ every_month: "Minden hónapban ezen a hétköznapon"
+ every_weekday: "Minden hétköznap"
+ every_week: "Minden héten ezen a hétköznapon"
+ every_two_weeks: "Kéthetente ezen a hétköznapon"
+ url:
+ label: "URL"
+ placeholder: "Nem kötelező"
+ location:
+ label: "Hely"
+ description:
+ label: "Leírás"
+ name:
+ label: "Esemény neve"
+ placeholder: "Nem kötelező, alapértelmezés szerint a téma címe"
+ invitees:
+ label: "Meghívott csoportok"
+ status:
+ label: "Állapot"
+ invite_user_or_group:
+ title: "Felhasználó(k) vagy csoport(ok) értesítése"
+ invite: "Küldés"
diff --git a/plugins/discourse-calendar/config/locales/client.hy.yml b/plugins/discourse-calendar/config/locales/client.hy.yml
new file mode 100644
index 00000000000..5016149ba7a
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/client.hy.yml
@@ -0,0 +1,71 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+hy:
+ js:
+ discourse_automation:
+ triggerables:
+ event_started:
+ fields:
+ topic_id:
+ label: Թեմայի ID
+ discourse_calendar:
+ disable_holiday: "Անջատել"
+ enable_holiday: "Միացնել"
+ date: "Ամսաթիվ"
+ region:
+ none: "Ոչ մի"
+ toolbar_button:
+ today: "Այսօրվա"
+ month: "Ամիս"
+ week: "Շաբաթ"
+ day: "Օր"
+ group_timezones:
+ search: "Որոնում..."
+ discourse_post_event:
+ notifications:
+ invite_user_notification: "%{username} %{description}"
+ bulk_invite: "Զանգվածային Հրավեր"
+ bulk_invite_modal:
+ success: "Ֆայլը հաջողությամբ վերբեռնվել է, Դուք կստանաք ծանուցում հաղորդագրության միջոցով, երբ գործընթացն ավարտվի:"
+ error: "Ներողություն, ֆայլը պետք է լինի CSV ձևաչափով:"
+ upcoming_events:
+ status: "Ստատուս"
+ models:
+ event:
+ closed: "Փակված"
+ status:
+ public:
+ title: "Հանրային"
+ private:
+ title: "Մասնավոր"
+ builder_modal:
+ custom_fields:
+ placeholder: "Ընտրովի"
+ create: "Ստեղծել"
+ update: "Պահպանել"
+ timezone:
+ label: Ժամային գոտի
+ reminders:
+ units:
+ days: "օր"
+ periods:
+ before: "մինչև"
+ after: "հետո"
+ recurrence:
+ label: "Կրկնություն"
+ none: "Կրկնություն չկա"
+ url:
+ label: "URL"
+ placeholder: "Ընտրովի"
+ location:
+ label: "Տեղակայություն"
+ description:
+ label: "Նկարագրությունը"
+ status:
+ label: "Ստատուս"
+ invite_user_or_group:
+ invite: "Ուղարկել"
diff --git a/plugins/discourse-calendar/config/locales/client.id.yml b/plugins/discourse-calendar/config/locales/client.id.yml
new file mode 100644
index 00000000000..f91294a95d8
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/client.id.yml
@@ -0,0 +1,102 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+id:
+ js:
+ discourse_calendar:
+ invite_user_notification: "%{username} mengundang Anda ke: %{description}"
+ on_holiday: "Saat Liburan"
+ disable_holiday: "Nonaktifkan"
+ enable_holiday: "Aktifkan"
+ holiday: "Hari Libur"
+ date: "Tanggal"
+ add_to_calendar: "Tambahkan ke Google Kalender"
+ region:
+ title: "Wilayah"
+ none: "Tidak ada"
+ use_current_region: "Gunakan Wilayah Saat Ini"
+ names:
+ ar: "Argentina"
+ at: "Austria"
+ au_act: "Australia (au_act)"
+ au_nsw: "Australia (au_nsw)"
+ au_nt: "Australia (au_nt)"
+ au_qld_brisbane: "Australia (au_qld_brisbane)"
+ au_qld_cairns: "Australia (au_qld_cairns)"
+ au_qld: "Australia (au_qld)"
+ au_sa: "Australia (au_sa)"
+ au_tas_north: "Australia (au_tas_north)"
+ au_tas_south: "Australia (au_tas_south)"
+ au_tas: "Australia (au_tas)"
+ au_vic_melbourne: "Australia (au_vic_melbourne)"
+ au_vic: "Australia (au_vic)"
+ au_wa: "Australia (au_wa)"
+ au: "Australia"
+ be_fr: "Belgia (be_fr)"
+ be_nl: "Belgia (be_nl)"
+ bg_bg: "Bulgaria (bg_bg)"
+ bg_en: "Bulgaria (bg_en)"
+ br: "Brazil"
+ ca_ab: "Kanada (ca_ab)"
+ ca_bc: "Kanada (ca_bc)"
+ ca_mb: "Kanada (ca_mb)"
+ ca_nb: "Kanada (ca_nb)"
+ ca_nl: "Kanada (ca_nl)"
+ ca_ns: "Kanada (ca_ns)"
+ ca_nt: "Kanada (ca_nt)"
+ ca_nu: "Kanada (ca_nu)"
+ ca_on: "Kanada (ca_on)"
+ ca_pe: "Kanada (ca_pe)"
+ ca_qc: "Kanada (ca_qc)"
+ ca_sk: "Kanada (ca_sk)"
+ ca_yt: "Kanada (ca_yt)"
+ ca: "Kanada"
+ ch_ag: "Swiss (ch_ag)"
+ ch_ai: "Swiss (ch_ai)"
+ ch_ar: "Swiss (ch_ar)"
+ ch_be: "Swiss (ch_be)"
+ ch_bl: "Swiss (ch_bl)"
+ toolbar_button:
+ day: "Hari"
+ group_timezones:
+ search: "Cari..."
+ discourse_post_event:
+ bulk_invite_modal:
+ confirm: "konfirmasi"
+ success: "File telah sukses diunggah, anda akan mendapat pesan pemberitahuan saat proses telah selesai."
+ error: "Maaf, file harus dalam format CSV"
+ upcoming_events:
+ status: "Status"
+ models:
+ event:
+ closed: "Tertutup"
+ status:
+ public:
+ title: "Umum"
+ private:
+ title: "Pribadi"
+ builder_modal:
+ custom_fields:
+ placeholder: "Opsional"
+ create: "Buat"
+ update: "Simpan"
+ timezone:
+ label: Zona Waktu
+ reminders:
+ units:
+ minutes: "menit"
+ hours: "jam"
+ days: "hari"
+ recurrence:
+ every_day: "Setiap hari"
+ url:
+ placeholder: "Opsional"
+ location:
+ label: "Lokasi"
+ description:
+ label: "Deskripsi"
+ status:
+ label: "Status"
diff --git a/plugins/discourse-calendar/config/locales/client.it.yml b/plugins/discourse-calendar/config/locales/client.it.yml
new file mode 100644
index 00000000000..dff4cee67fa
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/client.it.yml
@@ -0,0 +1,482 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+it:
+ admin_js:
+ admin:
+ calendar: "Calendario"
+ site_settings:
+ categories:
+ discourse_post_event: "Evento Discourse"
+ discourse_calendar: "Calendario Discourse"
+ js:
+ notifications:
+ titles:
+ event_reminder: "promemoria eventi"
+ event_invitation: "invito all'evento"
+ popup:
+ event_reminder: Promemoria eventi
+ discourse_automation:
+ triggerables:
+ event_started:
+ title: Evento iniziato
+ fields:
+ topic_id:
+ label: ID argomento
+ discourse_calendar:
+ invite_user_notification: "%{username} ti ha invitato a: %{description}"
+ on_holiday: "In vacanza"
+ disable_holiday: "Disabilita"
+ enable_holiday: "Abilita"
+ holiday: "Vacanza"
+ holidays:
+ header_title: "Festività"
+ pick_region_description: "Scegli una regione per vedere le festività per quella regione."
+ disabled_holidays_description: "Le festività disabilitate saranno escluse dal calendario delle ferie dello staff."
+ date: "Data"
+ add_to_calendar: "Aggiungi a Google Calendar"
+ toggle_timezone_offset_title: "Attiva/disattiva la differenza del fuso orario"
+ region:
+ title: "Regione"
+ none: "Nessuna"
+ use_current_region: "Usa regione corrente"
+ names:
+ ae: "Emirati Arabi Uniti"
+ ar: "Argentina"
+ at: "Austria"
+ au_act: "Australia (au_act)"
+ au_nsw: "Australia (au_nsw)"
+ au_nt: "Australia (au_nt)"
+ au_qld_brisbane: "Australia (au_qld_brisbane)"
+ au_qld_cairns: "Australia (au_qld_cairns)"
+ au_qld: "Australia (au_qld)"
+ au_sa: "Australia (au_sa)"
+ au_tas_north: "Australia (au_tas_north)"
+ au_tas_south: "Australia (au_tas_south)"
+ au_tas: "Australia (au_tas)"
+ au_vic_melbourne: "Australia (au_vic_melbourne)"
+ au_vic: "Australia (au_vic)"
+ au_wa: "Australia (au_wa)"
+ au: "Australia"
+ be_fr: "Belgio (be_fr)"
+ be_nl: "Belgio (be_nl)"
+ bg_bg: "Bulgaria (bg_bg)"
+ bg_en: "Bulgaria (bg_en)"
+ br: "Brasile"
+ br_sp: "Brasile (br_sp)"
+ br_spcapital: "Brasile (br_spcapital)"
+ ca_ab: "Canada (ca_ab)"
+ ca_bc: "Canada (ca_bc)"
+ ca_mb: "Canada (ca_mb)"
+ ca_nb: "Canada (ca_nb)"
+ ca_nl: "Canada (ca_nl)"
+ ca_ns: "Canada (ca_ns)"
+ ca_nt: "Canada (ca_nt)"
+ ca_nu: "Canada (ca_nu)"
+ ca_on: "Canada (ca_on)"
+ ca_pe: "Canada (ca_pe)"
+ ca_qc: "Canada (ca_qc)"
+ ca_sk: "Canada (ca_sk)"
+ ca_yt: "Canada (ca_yt)"
+ ca: "Canada"
+ ch_ag: "Svizzera (ch_ag)"
+ ch_ai: "Svizzera (ch_ai)"
+ ch_ar: "Svizzera (ch_ar)"
+ ch_be: "Svizzera (ch_be)"
+ ch_bl: "Svizzera (ch_bl)"
+ ch_bs: "Svizzera (ch_bs)"
+ ch_fr: "Svizzera (ch_fr)"
+ ch_ge: "Svizzera (ch_ge)"
+ ch_gl: "Svizzera (ch_gl)"
+ ch_gr: "Svizzera (ch_gr)"
+ ch_ju: "Svizzera (ch_ju)"
+ ch_lu: "Svizzera (ch_lu)"
+ ch_ne: "Svizzera (ch_ne)"
+ ch_nw: "Svizzera (ch_nw)"
+ ch_ow: "Svizzera (ch_ow)"
+ ch_sg: "Svizzera (ch_sg)"
+ ch_sh: "Svizzera (ch_sh)"
+ ch_so: "Svizzera (ch_so)"
+ ch_sz: "Svizzera (ch_sz)"
+ ch_tg: "Svizzera (ch_tg)"
+ ch_ti: "Svizzera (ch_ti)"
+ ch_ur: "Svizzera (ch_ur)"
+ ch_vd: "Svizzera (ch_vd)"
+ ch_vs: "Svizzera (ch_vs)"
+ ch_zg: "Svizzera (ch_zg)"
+ ch_zh: "Svizzera (ch_zh)"
+ ch: "Svizzera"
+ cl: "Cile"
+ co: "Colombia"
+ cr: "Costa Rica"
+ cz: "Repubblica Ceca"
+ de_bb: "Germania (de_bb)"
+ de_be: "Germania (de_be)"
+ de_bw: "Germania (de_bw)"
+ de_by_augsburg: "Germania (de_by_augsburg)"
+ de_by_cath: "Germania (de_by_cath)"
+ de_by: "Germania (de_by)"
+ de_hb: "Germania (de_hb)"
+ de_he: "Germania (de_he)"
+ de_hh: "Germania (de_hh)"
+ de_mv: "Germania (de_mv)"
+ de_ni: "Germania (de_ni)"
+ de_nw: "Germania (de_nw)"
+ de_rp: "Germania (de_rp)"
+ de_sh: "Germania (de_sh)"
+ de_sl: "Germania (de_sl)"
+ de_sn_sorbian: "Germania (de_sn_sorbian)"
+ de_sn: "Germania (de_sn)"
+ de_st: "Germania (de_st)"
+ de_th_cath: "Germania (de_th_cath)"
+ de_th: "Germania (de_th)"
+ de: "Germania"
+ dk: "Danimarca"
+ ee: "Estonia"
+ el: "Grecia"
+ es_an: "Spagna (es_an)"
+ es_ar: "Spagna (es_ar)"
+ es_ce: "Spagna (es_ce)"
+ es_cl: "Spagna (es_cl)"
+ es_cm: "Spagna (es_cm)"
+ es_cn: "Spagna (es_cn)"
+ es_ct: "Spagna (es_ct)"
+ es_ex: "Spagna (es_ex)"
+ es_ga: "Spagna (es_ga)"
+ es_ib: "Spagna (es_ib)"
+ es_lo: "Spagna (es_lo)"
+ es_m: "Spagna (es_m)"
+ es_mu: "Spagna (es_mu)"
+ es_na: "Spagna (es_na)"
+ es_o: "Spagna (es_o)"
+ es_pv: "Spagna (es_pv)"
+ es_v: "Spagna (es_v)"
+ es_vc: "Spagna (es_vc)"
+ es: "Spagna"
+ fi: "Finlandia"
+ fr_a: "Francia (fr_a)"
+ fr_m: "Francia (fr_m)"
+ fr: "Francia"
+ gb_con: "Regno Unito (gb_con)"
+ gb_eaw: "Regno Unito (gb_eaw)"
+ gb_eng: "Regno Unito (gb_eng)"
+ gb_gsy: "Regno Unito (gb_gsy)"
+ gb_iom: "Regno Unito (gb_iom)"
+ gb_jsy: "Regno Unito (gb_jsy)"
+ gb_nir: "Regno Unito (gb_nir)"
+ gb_sct: "Regno Unito (gb_sct)"
+ gb_wls: "Regno Unito (gb_wls)"
+ gb: "Regno Unito"
+ ge: "Georgia"
+ gg: "Guernsey"
+ gh: "Ghana"
+ hk: "Hong Kong"
+ hr: "Croazia"
+ hu: "Ungheria"
+ id: "Indonesia"
+ ie: "Irlanda"
+ im: "Isola di Man"
+ in: "India"
+ in_gj: "India (in_gj)"
+ in_mh: "India (in_mh)"
+ in_rj: "India (in_rj)"
+ in_tn: "India (in_tn)"
+ in_ka: "India (in_ka)"
+ is: "Islanda"
+ it_bl: "Italia (it_bl)"
+ it_fi: "Italia (it_fi)"
+ it_ge: "Italia (it_ge)"
+ it_pd: "Italia (it_pd)"
+ it_rm: "Italia (it_rm)"
+ it_ro: "Italia (it_ro)"
+ it_to: "Italia (it_to)"
+ it_tv: "Italia (it_tv)"
+ it_ve: "Italia (it_ve)"
+ it_vi: "Italia (it_vi)"
+ it_vr: "Italia (it_vr)"
+ it: "Italia"
+ je: "Jersey"
+ jp: "Giappone"
+ ke: "Kenya"
+ kr: "Corea (Repubblica di)"
+ kz: "Kazakistan (Repubblica di)"
+ li: "Liechtenstein"
+ lt: "Lituania"
+ lu: "Lussemburgo"
+ lv: "Lettonia"
+ ma: "Marocco"
+ mt_en: "Malta (mt_en)"
+ mt_mt: "Malta (mt_mt)"
+ mx_pue: "Messico (mx_pue)"
+ mx: "Messico"
+ my: "Malesia"
+ ng: "Nigeria"
+ nl: "Paesi Bassi"
+ "no": "Norvegia"
+ nz_ak: "Nuova Zelanda (nz_ak)"
+ nz_ca: "Nuova Zelanda (nz_ca)"
+ nz_ch: "Nuova Zelanda (nz_ch)"
+ nz_hb: "Nuova Zelanda (nz_hb)"
+ nz_mb: "Nuova Zelanda (nz_mb)"
+ nz_ne: "Nuova Zelanda (nz_ne)"
+ nz_nl: "Nuova Zelanda (nz_nl)"
+ nz_ot: "Nuova Zelanda (nz_ot)"
+ nz_sc: "Nuova Zelanda (nz_sc)"
+ nz_sl: "Nuova Zelanda (nz_sl)"
+ nz_ta: "Nuova Zelanda (nz_ta)"
+ nz_we: "Nuova Zelanda (nz_we)"
+ nz_wl: "Nuova Zelanda (nz_wl)"
+ nz: "Nuova Zelanda"
+ pe: "Perù"
+ ph: "Filippine"
+ pl: "Polonia"
+ pt_li: "Portogallo (pt_li)"
+ pt_po: "Portogallo (pt_po)"
+ pt: "Portogallo"
+ ro: "Romania"
+ rs_cyrl: "Serbia (rs_cyrl)"
+ rs_la: "Serbia (rs_la)"
+ ru: "Federazione Russa"
+ se: "Svezia"
+ sa: "Arabia Saudita"
+ sg: "Singapore"
+ si: "Slovenia"
+ sk: "Slovacchia"
+ th: "Tailandia"
+ tn: "Tunisia"
+ tr: "Turchia"
+ ua: "Ucraina"
+ us_ak: "Stati Uniti (us_ak)"
+ us_al: "Stati Uniti (us_al)"
+ us_ar: "Stati Uniti (us_ar)"
+ us_az: "Stati Uniti (us_az)"
+ us_ca: "Stati Uniti (us_ca)"
+ us_co: "Stati Uniti (us_co)"
+ us_ct: "Stati Uniti (us_ct)"
+ us_dc: "Stati Uniti (us_dc)"
+ us_de: "Stati Uniti (us_de)"
+ us_fl: "Stati Uniti (us_fl)"
+ us_ga: "Stati Uniti (us_ga)"
+ us_gu: "Stati Uniti (us_gu)"
+ us_hi: "Stati Uniti (us_hi)"
+ us_ia: "Stati Uniti (us_ia)"
+ us_id: "Stati Uniti (us_id)"
+ us_il: "Stati Uniti (us_il)"
+ us_in: "Stati Uniti (us_in)"
+ us_ks: "Stati Uniti (us_ks)"
+ us_ky: "Stati Uniti (us_ky)"
+ us_la: "Stati Uniti (us_la)"
+ us_ma: "Stati Uniti (us_ma)"
+ us_md: "Stati Uniti (us_md)"
+ us_me: "Stati Uniti (us_me)"
+ us_mi: "Stati Uniti (us_mi)"
+ us_mn: "Stati Uniti (us_mn)"
+ us_mo: "Stati Uniti (us_mo)"
+ us_ms: "Stati Uniti (us_ms)"
+ us_mt: "Stati Uniti (us_mt)"
+ us_nc: "Stati Uniti (us_nc)"
+ us_nd: "Stati Uniti (us_nd)"
+ us_ne: "Stati Uniti (us_ne)"
+ us_nh: "Stati Uniti (us_nh)"
+ us_nj: "Stati Uniti (us_nj)"
+ us_nm: "Stati Uniti (us_nm)"
+ us_nv: "Stati Uniti (us_nv)"
+ us_ny: "Stati Uniti (us_ny)"
+ us_oh: "Stati Uniti (us_oh)"
+ us_ok: "Stati Uniti (us_ok)"
+ us_or: "Stati Uniti (us_or)"
+ us_pa: "Stati Uniti (us_pa)"
+ us_pr: "Stati Uniti (us_pr)"
+ us_ri: "Stati Uniti (us_ri)"
+ us_sc: "Stati Uniti (us_sc)"
+ us_sd: "Stati Uniti (us_sd)"
+ us_tn: "Stati Uniti (us_tn)"
+ us_tx: "Stati Uniti (us_tx)"
+ us_ut: "Stati Uniti (us_ut)"
+ us_va: "Stati Uniti (us_va)"
+ us_vi: "Stati Uniti (us_vi)"
+ us_vt: "Stati Uniti (us_vt)"
+ us_wa: "Stati Uniti (us_wa)"
+ us_wi: "Stati Uniti (us_wi)"
+ us_wv: "Stati Uniti (us_wv)"
+ us_wy: "Stati Uniti (us_wy)"
+ us: "Stati Uniti"
+ ve: "Venezuela"
+ vi: "Isole Vergini (USA)"
+ za: "Sud Africa"
+ toolbar_button:
+ today: "Oggi"
+ month: "Mese"
+ week: "Settimana"
+ day: "Giorno"
+ list: "Elenco"
+ group_timezones:
+ search: "Ricerca..."
+ group_availability: "Disponibilità di %{group}"
+ discourse_post_event:
+ notifications:
+ before_event_reminder: "Un evento sta per iniziare"
+ after_event_reminder: "Un evento è terminato"
+ ongoing_event_reminder: "Un evento è in corso"
+ invite_user_notification: "%{username} %{description}"
+ invite_user_predefined_attendance_notification_html: "%{username} ha impostato automaticamente la tua presenza e ti ha invitato a %{description}"
+ before_event_reminder_html: "Un evento sta per iniziare: %{description}"
+ after_event_reminder_html: "Un evento è terminato: %{description}"
+ ongoing_event_reminder_html: "Un evento è in corso: %{description}"
+ edit_reason: "Evento aggiornato"
+ edit_reason_closed: "Evento chiuso"
+ edit_reason_opened: "Evento aperto"
+ topic_title:
+ starts_at: "L'evento inizierà: %{date}"
+ ended_at: "Evento terminato: %{date}"
+ ends_in_duration: "Termina tra %{duration}"
+ show_all: "Mostra tutto"
+ show_participants: "Mostra partecipanti"
+ participants:
+ one: "%{count} utente ha partecipato."
+ other: "%{count} utenti hanno partecipato."
+ invite: "Notifica l'utente"
+ add_to_calendar: "Aggiungi al calendario"
+ send_pm_to_creator: "Invia MP a %{username}"
+ leave: "Abbandona l'evento"
+ edit_event: "Modifica evento"
+ export_event: "Esporta evento"
+ created_by: "Creato da"
+ bulk_invite: "Invito collettivo"
+ close_event: "Chiudi evento"
+ open_event: "Apri evento"
+ invitees_modal:
+ title_invited: "Partecipazione all'evento"
+ title_participated: "Utenti che hanno partecipato"
+ filter_placeholder: "Filtra utenti"
+ remove_invitee: "Rimuovi invitato dall'elenco"
+ add_invitee: "Aggiungi invitato all'elenco"
+ bulk_invite_modal:
+ confirm: "conferma"
+ text: "Carica file CSV"
+ title: "Invito collettivo"
+ success: "Il file è stato caricato con successo, riceverai un messaggio di notifica quando il processo sarà completato."
+ error: "Spiacenti, il file deve essere in formato CSV."
+ confirmation_message: "Stai per inviare una notifica a tutti nel file caricato."
+ description_public: "Gli eventi pubblici accettano solo nomi utente per inviti collettivi."
+ description_private: "Gli eventi pubblici accettano solo nomi di gruppi per inviti collettivi."
+ download_sample_csv: "Scarica un file CSV di esempio"
+ send_bulk_invites: "Manda inviti"
+ group_selector_placeholder: "Scegli un gruppo..."
+ user_selector_placeholder: "Scegli utente..."
+ inline_title: "Invito collettivo inline"
+ csv_title: "Invito collettivo CSV"
+ upcoming_events:
+ title: "Prossimi eventi"
+ creator: "Creatore"
+ status: "Stato"
+ starts_at: "Inizia alle"
+ upcoming_events_list:
+ title: "Prossimi eventi"
+ empty: "Nessun evento in programma"
+ all_day: "Tutto il giorno"
+ error: "Impossibile recuperare gli eventi"
+ try_again: "Riprova"
+ view_all: "Vedi tutto"
+ category:
+ sort_topics_by_event_start_date: "Ordina gli argomenti per data di inizio dell'evento."
+ disable_topic_resorting: "Disabilita il riordinamento degli argomenti."
+ settings_sections:
+ event_sorting: "Ordinamento eventi"
+ preview:
+ more_than_one_event: "Non puoi avere più di un evento."
+ models:
+ invitee:
+ no_users: "Nessun utente trovato"
+ status:
+ unknown: "Non interessato"
+ going: "Parteciperò"
+ not_going: "Non parteciperò"
+ interested: "Interessato"
+ going_count:
+ one: "%{count} partecipa"
+ other: "%{count} partecipano"
+ not_going_count:
+ one: "%{count} non partecipa"
+ other: "%{count} non partecipano"
+ interested_count:
+ one: "%{count} interessato"
+ other: "%{count} interessati"
+ invited_count:
+ one: "%{count} utente invitato"
+ other: "%{count} utenti invitati"
+ event:
+ expired: "Scaduto"
+ closed: "Chiuso"
+ status:
+ standalone:
+ title: "Evento autonomo"
+ description: "Non è possibile partecipare a un evento autonomo."
+ public:
+ title: "Pubblico"
+ description: "Chiunque può partecipare a un evento pubblico."
+ private:
+ title: "Privato"
+ description: "Solo gli utenti invitati possono partecipare a un evento privato."
+ builder_modal:
+ custom_fields:
+ label: "Campi personalizzati"
+ placeholder: "Facoltativo"
+ description: "I campi personalizzati consentiti sono definiti nelle impostazioni del sito. I campi personalizzati sono utilizzati per trasmettere dati ad altri plugin."
+ create_event_title: "Crea Evento"
+ update_event_title: "Modifica evento"
+ confirm_delete: "Vuoi davvero eliminare questo evento?"
+ confirm_close: "Vuoi davvero chiudere questo evento?"
+ confirm_open: "Vuoi davvero aprire questo evento?"
+ create: "Crea"
+ update: "Salva"
+ attach: "Crea evento"
+ add_reminder: "Aggiungi promemoria"
+ timezone:
+ label: Fuso orario
+ remove_timezone: Nessun fuso orario (UTC)
+ reminders:
+ label: "Promemoria"
+ types:
+ bump_topic: "riproponi automaticamente l’argomento"
+ notification: "avvisa i partecipanti"
+ units:
+ minutes: "minuti"
+ hours: "ore"
+ days: "giorni"
+ weeks: "settimane"
+ periods:
+ before: "prima del"
+ after: "dopo il"
+ recurrence:
+ label: "Periodico"
+ none: "Non periodico"
+ every_day: "Ogni giorno"
+ every_month: "Ogni mese in questo giorno feriale"
+ every_weekday: "Ogni giorno feriale"
+ every_week: "Ogni settimana in questo giorno feriale"
+ every_two_weeks: "Ogni due settimane in questo giorno feriale"
+ every_four_weeks: "Ogni quattro settimane in questo giorno feriale"
+ minimal:
+ label: "Evento minimo"
+ checkbox_label: "Nascondi i pulsanti Parteciperò/Non parteciperò e lo stato degli invitati"
+ url:
+ label: "URL"
+ placeholder: "Facoltativo"
+ location:
+ label: "Località"
+ description:
+ label: "Descrizione"
+ name:
+ label: "Nome dell'evento"
+ placeholder: "Facoltativo, valore predefinito titolo dell'argomento"
+ invitees:
+ label: "Gruppi invitati"
+ status:
+ label: "Stato"
+ invite_user_or_group:
+ title: "Notifica utenti o gruppi"
+ invite: "Invia"
diff --git a/plugins/discourse-calendar/config/locales/client.ja.yml b/plugins/discourse-calendar/config/locales/client.ja.yml
new file mode 100644
index 00000000000..c7310e5ed52
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/client.ja.yml
@@ -0,0 +1,477 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+ja:
+ admin_js:
+ admin:
+ calendar: "カレンダー"
+ site_settings:
+ categories:
+ discourse_post_event: "Discourse イベント"
+ discourse_calendar: "Discourse カレンダー"
+ js:
+ notifications:
+ titles:
+ event_reminder: "イベントリマインダー"
+ event_invitation: "イベントの招待"
+ popup:
+ event_reminder: イベントリマインダー
+ discourse_automation:
+ triggerables:
+ event_started:
+ title: イベント開始
+ fields:
+ topic_id:
+ label: トピック ID
+ discourse_calendar:
+ invite_user_notification: "%{username} があなたを招待しました: %{description}"
+ on_holiday: "休暇中"
+ disable_holiday: "無効化"
+ enable_holiday: "有効化"
+ holiday: "休祝日"
+ holidays:
+ header_title: "祝祭日"
+ pick_region_description: "地域を選択すると、その地域の祝祭日が表示されます。"
+ disabled_holidays_description: "無効化された祝祭日は、スタッフの祝祭日カレンダーから除外されます。"
+ date: "日付"
+ add_to_calendar: "Google カレンダーに追加"
+ toggle_timezone_offset_title: "タイムゾーンオフセットの切り替え"
+ region:
+ title: "地域"
+ none: "なし"
+ use_current_region: "現在の地域を使用"
+ names:
+ ae: "アラブ首長国連邦"
+ ar: "アルゼンチン"
+ at: "オーストリア"
+ au_act: "オーストラリア(au_act)"
+ au_nsw: "オーストラリア(au_nsw)"
+ au_nt: "オーストラリア(au_nt)"
+ au_qld_brisbane: "オーストラリア(au_qld_brisbane)"
+ au_qld_cairns: "オーストラリア(au_qld_cairns)"
+ au_qld: "オーストラリア(au_qld)"
+ au_sa: "オーストラリア(au_sa)"
+ au_tas_north: "オーストラリア(au_tas_north)"
+ au_tas_south: "オーストラリア(au_tas_south)"
+ au_tas: "オーストラリア(au_tas)"
+ au_vic_melbourne: "オーストラリア(au_vic_melbourne)"
+ au_vic: "オーストラリア(au_vic)"
+ au_wa: "オーストラリア(au_wa)"
+ au: "オーストラリア"
+ be_fr: "ベルギー(be_fr)"
+ be_nl: "ベルギー(be_nl)"
+ bg_bg: "ブルガリア(bg_bg)"
+ bg_en: "ブルガリア(bg_en)"
+ br: "ブラジル"
+ br_sp: "ブラジル (br_sp)"
+ br_spcapital: "ブラジル (br_spcapital)"
+ ca_ab: "カナダ(ca_ab)"
+ ca_bc: "カナダ(ca_bc)"
+ ca_mb: "カナダ(ca_mb)"
+ ca_nb: "カナダ(ca_nb)"
+ ca_nl: "カナダ(ca_nl)"
+ ca_ns: "カナダ(ca_ns)"
+ ca_nt: "カナダ(ca_nt)"
+ ca_nu: "カナダ(ca_nu)"
+ ca_on: "カナダ(ca_on)"
+ ca_pe: "カナダ(ca_pe)"
+ ca_qc: "カナダ(ca_qc)"
+ ca_sk: "カナダ(ca_sk)"
+ ca_yt: "カナダ(ca_yt)"
+ ca: "カナダ"
+ ch_ag: "スイス(ch_ag)"
+ ch_ai: "スイス(ch_ai)"
+ ch_ar: "スイス(ch_ar)"
+ ch_be: "スイス(ch_be)"
+ ch_bl: "スイス(ch_bl)"
+ ch_bs: "スイス(ch_bs)"
+ ch_fr: "スイス(ch_fr)"
+ ch_ge: "スイス(ch_ge)"
+ ch_gl: "スイス(ch_gl)"
+ ch_gr: "スイス(ch_gr)"
+ ch_ju: "スイス(ch_ju)"
+ ch_lu: "スイス(ch_lu)"
+ ch_ne: "スイス(ch_ne)"
+ ch_nw: "スイス(ch_nw)"
+ ch_ow: "スイス(ch_ow)"
+ ch_sg: "スイス(ch_sg)"
+ ch_sh: "スイス(ch_sh)"
+ ch_so: "スイス(ch_so)"
+ ch_sz: "スイス(ch_sz)"
+ ch_tg: "スイス(ch_tg)"
+ ch_ti: "スイス(ch_ti)"
+ ch_ur: "スイス(ch_ur)"
+ ch_vd: "スイス(ch_vd)"
+ ch_vs: "スイス(ch_vs)"
+ ch_zg: "スイス(ch_zg)"
+ ch_zh: "スイス(ch_zh)"
+ ch: "スイス"
+ cl: "チリ"
+ co: "コロンビア"
+ cr: "コスタリカ"
+ cz: "チェコ共和国"
+ de_bb: "ドイツ(de_bb)"
+ de_be: "ドイツ(de_be)"
+ de_bw: "ドイツ(de_bw)"
+ de_by_augsburg: "ドイツ(de_by_augsburg)"
+ de_by_cath: "ドイツ(de_by_cath)"
+ de_by: "ドイツ(de_by)"
+ de_hb: "ドイツ(de_hb)"
+ de_he: "ドイツ(de_he)"
+ de_hh: "ドイツ(de_hh)"
+ de_mv: "ドイツ(de_mv)"
+ de_ni: "ドイツ(de_ni)"
+ de_nw: "ドイツ(de_nw)"
+ de_rp: "ドイツ(de_rp)"
+ de_sh: "ドイツ(de_sh)"
+ de_sl: "ドイツ(de_sl)"
+ de_sn_sorbian: "ドイツ(de_sn_sorbian)"
+ de_sn: "ドイツ(de_sn)"
+ de_st: "ドイツ(de_st)"
+ de_th_cath: "ドイツ(de_th_cath)"
+ de_th: "ドイツ(de_th)"
+ de: "ドイツ"
+ dk: "デンマーク"
+ ee: "エストニア"
+ el: "ギリシャ"
+ es_an: "スペイン(es_an)"
+ es_ar: "スペイン(es_ar)"
+ es_ce: "スペイン(es_ce)"
+ es_cl: "スペイン(es_cl)"
+ es_cm: "スペイン(es_cm)"
+ es_cn: "スペイン(es_cn)"
+ es_ct: "スペイン(es_ct)"
+ es_ex: "スペイン(es_ex)"
+ es_ga: "スペイン(es_ga)"
+ es_ib: "スペイン(es_ib)"
+ es_lo: "スペイン(es_lo)"
+ es_m: "スペイン(es_m)"
+ es_mu: "スペイン(es_mu)"
+ es_na: "スペイン(es_na)"
+ es_o: "スペイン(es_o)"
+ es_pv: "スペイン(es_pv)"
+ es_v: "スペイン(es_v)"
+ es_vc: "スペイン(es_vc)"
+ es: "スペイン"
+ fi: "フィンランド"
+ fr_a: "フランス(fr_a)"
+ fr_m: "フランス(fr_m)"
+ fr: "フランス"
+ gb_con: "英国(gb_con)"
+ gb_eaw: "英国(gb_eaw)"
+ gb_eng: "英国(gb_eng)"
+ gb_gsy: "英国(gb_gsy)"
+ gb_iom: "英国(gb_iom)"
+ gb_jsy: "英国(gb_jsy)"
+ gb_nir: "英国(gb_nir)"
+ gb_sct: "英国(gb_sct)"
+ gb_wls: "英国(gb_wls)"
+ gb: "英国"
+ ge: "ジョージア"
+ gg: "ガーンジー"
+ gh: "ガーナ"
+ hk: "香港"
+ hr: "クロアチア"
+ hu: "ハンガリー"
+ id: "インドネシア"
+ ie: "アイルランド"
+ im: "マン島"
+ in: "インド"
+ in_gj: "インド (in_gj)"
+ in_mh: "インド (in_mh)"
+ in_rj: "インド (in_rj)"
+ in_tn: "インド (in_tn)"
+ in_ka: "インド (in_ka)"
+ is: "アイスランド"
+ it_bl: "イタリア(it_bl)"
+ it_fi: "イタリア(it_fi)"
+ it_ge: "イタリア(it_ge)"
+ it_pd: "イタリア(it_pd)"
+ it_rm: "イタリア(it_rm)"
+ it_ro: "イタリア(it_ro)"
+ it_to: "イタリア(it_to)"
+ it_tv: "イタリア(it_tv)"
+ it_ve: "イタリア(it_ve)"
+ it_vi: "イタリア(it_vi)"
+ it_vr: "イタリア(it_vr)"
+ it: "イタリア"
+ je: "ジャージー"
+ jp: "日本"
+ ke: "ケニア"
+ kr: "韓国"
+ kz: "カザフスタン共和国"
+ li: "リヒテンシュタイン"
+ lt: "リトアニア"
+ lu: "ルクセンブルク"
+ lv: "ラトビア"
+ ma: "モロッコ"
+ mt_en: "マルタ(mt_en)"
+ mt_mt: "マルタ(mt_mt)"
+ mx_pue: "メキシコ(mx_pue)"
+ mx: "メキシコ"
+ my: "マレーシア"
+ ng: "ナイジェリア"
+ nl: "オランダ"
+ "no": "ノルウェー"
+ nz_ak: "ニュージーランド(nz_ak)"
+ nz_ca: "ニュージーランド(nz_ca)"
+ nz_ch: "ニュージーランド(nz_ch)"
+ nz_hb: "ニュージーランド(nz_hb)"
+ nz_mb: "ニュージーランド(nz_mb)"
+ nz_ne: "ニュージーランド(nz_ne)"
+ nz_nl: "ニュージーランド(nz_nl)"
+ nz_ot: "ニュージーランド(nz_ot)"
+ nz_sc: "ニュージーランド(nz_sc)"
+ nz_sl: "ニュージーランド(nz_sl)"
+ nz_ta: "ニュージーランド(nz_ta)"
+ nz_we: "ニュージーランド(nz_we)"
+ nz_wl: "ニュージーランド(nz_wl)"
+ nz: "ニュージーランド"
+ pe: "ペルー"
+ ph: "フィリピン"
+ pl: "ポーランド"
+ pt_li: "ポルトガル(pt_li)"
+ pt_po: "ポルトガル(pt_po)"
+ pt: "ポルトガル"
+ ro: "ルーマニア"
+ rs_cyrl: "セルビア(rs_cyrl)"
+ rs_la: "セルビア(rs_la)"
+ ru: "ロシア連邦"
+ se: "スウェーデン"
+ sa: "サウジアラビア"
+ sg: "シンガポール"
+ si: "スロベニア"
+ sk: "スロバキア"
+ th: "タイ"
+ tn: "チュニジア"
+ tr: "トルコ"
+ ua: "ウクライナ"
+ us_ak: "米国(us_ak)"
+ us_al: "米国(us_al)"
+ us_ar: "米国(us_ar)"
+ us_az: "米国(us_az)"
+ us_ca: "米国(us_ca)"
+ us_co: "米国(us_co)"
+ us_ct: "米国(us_ct)"
+ us_dc: "米国(us_dc)"
+ us_de: "米国(us_de)"
+ us_fl: "米国(us_fl)"
+ us_ga: "米国(us_ga)"
+ us_gu: "米国(us_gu)"
+ us_hi: "米国(us_hi)"
+ us_ia: "米国(us_ia)"
+ us_id: "米国(us_id)"
+ us_il: "米国(us_il)"
+ us_in: "米国(us_in)"
+ us_ks: "米国(us_ks)"
+ us_ky: "米国(us_ky)"
+ us_la: "米国(us_la)"
+ us_ma: "米国(us_ma)"
+ us_md: "米国(us_md)"
+ us_me: "米国(us_me)"
+ us_mi: "米国(us_mi)"
+ us_mn: "米国(us_mn)"
+ us_mo: "米国(us_mo)"
+ us_ms: "米国(us_ms)"
+ us_mt: "米国(us_mt)"
+ us_nc: "米国(us_nc)"
+ us_nd: "米国(us_nd)"
+ us_ne: "米国(us_ne)"
+ us_nh: "米国(us_nh)"
+ us_nj: "米国(us_nj)"
+ us_nm: "米国(us_nm)"
+ us_nv: "米国(us_nv)"
+ us_ny: "米国(us_ny)"
+ us_oh: "米国(us_oh)"
+ us_ok: "米国(us_ok)"
+ us_or: "米国(us_or)"
+ us_pa: "米国(us_pa)"
+ us_pr: "米国(us_pr)"
+ us_ri: "米国(us_ri)"
+ us_sc: "米国(us_sc)"
+ us_sd: "米国(us_sd)"
+ us_tn: "米国(us_tn)"
+ us_tx: "米国(us_tx)"
+ us_ut: "米国(us_ut)"
+ us_va: "米国(us_va)"
+ us_vi: "米国(us_vi)"
+ us_vt: "米国(us_vt)"
+ us_wa: "米国(us_wa)"
+ us_wi: "米国(us_wi)"
+ us_wv: "米国(us_wv)"
+ us_wy: "米国(us_wy)"
+ us: "米国"
+ ve: "ベネズエラ"
+ vi: "バージン諸島(米国)"
+ za: "南アフリカ"
+ toolbar_button:
+ today: "今日"
+ month: "今月"
+ week: "今週"
+ day: "日"
+ list: "リスト"
+ group_timezones:
+ search: "検索..."
+ group_availability: "%{group} の空き状況"
+ discourse_post_event:
+ notifications:
+ before_event_reminder: "イベントが間もなく開始します"
+ after_event_reminder: "イベントは終了しました"
+ ongoing_event_reminder: "イベントは進行中です"
+ invite_user_notification: "%{username} %{description}"
+ invite_user_predefined_attendance_notification_html: "%{username} はあなたの出席を自動的に設定して %{description} に招待しました"
+ before_event_reminder_html: "イベントが間もなく開始します: %{description}"
+ after_event_reminder_html: "イベントは終了しました: %{description}"
+ ongoing_event_reminder_html: "イベントは進行中です: %{description}"
+ edit_reason: "イベントが更新されました"
+ edit_reason_closed: "イベント終了"
+ edit_reason_opened: "イベント開始"
+ topic_title:
+ starts_at: "イベント開始: %{date}"
+ ended_at: "イベント終了: %{date}"
+ ends_in_duration: "終了まで %{duration}"
+ show_all: "すべて表示"
+ show_participants: "参加者を表示"
+ participants:
+ other: "%{count} 人のユーザーが参加しました。"
+ invite: "ユーザーに通知"
+ add_to_calendar: "カレンダーに追加"
+ send_pm_to_creator: "%{username} に PM を送信"
+ leave: "イベントから退出"
+ edit_event: "イベントを編集"
+ export_event: "イベントをエクスポート"
+ created_by: "作成者"
+ bulk_invite: "一括招待"
+ close_event: "イベントを終了"
+ open_event: "イベントを開く"
+ invitees_modal:
+ title_invited: "イベントへの参加"
+ title_participated: "参加したユーザーのリスト"
+ filter_placeholder: "ユーザーをフィルタ"
+ remove_invitee: "招待されたユーザーをリストから削除する"
+ add_invitee: "招待されたユーザーをリストに追加する"
+ bulk_invite_modal:
+ confirm: "確認"
+ text: "CSV ファイルをアップロード"
+ title: "一括招待"
+ success: "ファイルは正常にアップロードされました。処理が完了したら、メッセージでお知らせします。"
+ error: "ファイルは CSV 形式である必要があります。"
+ confirmation_message: "アップロードしたファイルの全員に通知しようとしています。"
+ description_public: "公開イベントの一括招待にはユーザー名のみを使用できます。"
+ description_private: "非公開イベントの一括招待にはグループ名のみを使用できます。"
+ download_sample_csv: "サンプル CSV ファイルをダウンロード"
+ send_bulk_invites: "招待を送信"
+ group_selector_placeholder: "グループを選択..."
+ user_selector_placeholder: "ユーザーを選択..."
+ inline_title: "インライン一括招待"
+ csv_title: "CSV 一括招待"
+ upcoming_events:
+ title: "今後のイベント"
+ creator: "作成者"
+ status: "ステータス"
+ starts_at: "開始時刻"
+ upcoming_events_list:
+ title: "今後のイベント"
+ empty: "今後のイベントはありません"
+ all_day: "終日"
+ error: "イベントの取得に失敗しました"
+ try_again: "やり直す"
+ view_all: "すべて表示"
+ category:
+ sort_topics_by_event_start_date: "イベント開始日順にトピックを並べ替えます。"
+ disable_topic_resorting: "トピックの並べ替え直しを無効にします。"
+ settings_sections:
+ event_sorting: "イベントの並べ替え"
+ preview:
+ more_than_one_event: "複数のイベントを指定できません。"
+ models:
+ invitee:
+ no_users: "ユーザーが見つかりません"
+ status:
+ unknown: "興味なし"
+ going: "出席"
+ not_going: "欠席"
+ interested: "興味あり"
+ going_count:
+ other: "%{count} 出席"
+ not_going_count:
+ other: "%{count} 欠席"
+ interested_count:
+ other: "%{count} 興味あり"
+ invited_count:
+ other: "%{count} 招待済みユーザー"
+ event:
+ expired: "期限切れ"
+ closed: "終了"
+ status:
+ standalone:
+ title: "単発"
+ description: "単発イベントに参加することはできません。"
+ public:
+ title: "公開"
+ description: "公開イベントは誰でも参加できます。"
+ private:
+ title: "非公開"
+ description: "非公開イベントは招待されたユーザーのみが参加できます。"
+ builder_modal:
+ custom_fields:
+ label: "カスタムフィールド"
+ placeholder: "オプション"
+ description: "許可されるカスタムフィールドは、サイト設定に定義されています。カスタムフィールドは、他のプラグインにデータを送信するために使用されます。"
+ create_event_title: "イベントを作成"
+ update_event_title: "イベントを編集"
+ confirm_delete: "このイベントを削除してもよろしいですか?"
+ confirm_close: "このイベントを終了してもよろしいですか?"
+ confirm_open: "このイベント開いてもよろしいですか?"
+ create: "作成"
+ update: "保存"
+ attach: "イベントを作成"
+ add_reminder: "リマインダーを追加"
+ timezone:
+ label: タイムゾーン
+ remove_timezone: タイムゾーンなし (UTC)
+ reminders:
+ label: "リマインダー"
+ types:
+ bump_topic: "トピックの自動バンプ"
+ notification: "参加者に通知"
+ units:
+ minutes: "分"
+ hours: "時間"
+ days: "日"
+ weeks: "週間"
+ periods:
+ before: "前"
+ after: "後"
+ recurrence:
+ label: "繰り返し"
+ none: "繰り返しなし"
+ every_day: "毎日"
+ every_month: "毎月この平日"
+ every_weekday: "すべての平日"
+ every_week: "毎週この平日"
+ every_two_weeks: "隔週この平日"
+ every_four_weeks: "4 週間ごとのこの平日"
+ minimal:
+ label: "ミニマルイベント"
+ checkbox_label: "参加/不参加ボタンと招待者のステータスを非表示にする"
+ url:
+ label: "URL"
+ placeholder: "オプション"
+ location:
+ label: "場所"
+ description:
+ label: "説明"
+ name:
+ label: "イベント名"
+ placeholder: "オプション、デフォルトはトピックタイトル"
+ invitees:
+ label: "招待されたグループ"
+ status:
+ label: "ステータス"
+ invite_user_or_group:
+ title: "ユーザーまたはグループに通知する"
+ invite: "送信"
diff --git a/plugins/discourse-calendar/config/locales/client.ko.yml b/plugins/discourse-calendar/config/locales/client.ko.yml
new file mode 100644
index 00000000000..f3de0e2fb14
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/client.ko.yml
@@ -0,0 +1,164 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+ko:
+ admin_js:
+ admin:
+ site_settings:
+ categories:
+ discourse_post_event: "Discourse 이벤트"
+ discourse_calendar: "Discourse 캘린더"
+ js:
+ notifications:
+ titles:
+ event_reminder: "이벤트 알림"
+ popup:
+ event_reminder: 이벤트 알림
+ discourse_automation:
+ triggerables:
+ event_started:
+ title: 이벤트 시작됨
+ fields:
+ topic_id:
+ label: 글 ID
+ discourse_calendar:
+ invite_user_notification: "%{username}님이 사용자님을 초대했습니다: %{description}"
+ on_holiday: "휴일"
+ disable_holiday: "비활성"
+ enable_holiday: "설정"
+ holiday: "휴일"
+ date: "날짜"
+ add_to_calendar: "구글 캘린더에 추가"
+ region:
+ title: "지역"
+ none: "없음"
+ use_current_region: "현재 지역 사용"
+ names:
+ ar: "아르헨티나"
+ at: "오스트리아"
+ au_act: "오스트레일리아 (au_act)"
+ au_nsw: "오스트레일리아 (au_nsw)"
+ au_nt: "오스트레일리아 (au_nt)"
+ au_qld_brisbane: "오스트레일리아 (au_qld_brisbane)"
+ au_qld_cairns: "오스트레일리아 (au_qld_cairns)"
+ au_qld: "오스트레일리아 (au_qld)"
+ au_sa: "오스트레일리아 (au_sa)"
+ au_tas_north: "오스트레일리아 (au_tas_north)"
+ au_tas_south: "오스트레일리아 (au_tas_south)"
+ au_tas: "오스트레일리아 (au_tas)"
+ au_vic_melbourne: "오스트레일리아 (au_vic_melbourne)"
+ au_vic: "오스트레일리아 (au_vic)"
+ au_wa: "오스트레일리아 (au_wa)"
+ au: "오스트레일리아"
+ be_fr: "벨기에 (be_fr)"
+ be_nl: "벨기에 (be_nl)"
+ bg_bg: "불가리아 (bg_bg)"
+ bg_en: "불가리아 (bg_en)"
+ br: "브라질"
+ ca_ab: "캐나다 (ca_ab)"
+ ca_bc: "캐나다 (ca_bc)"
+ ca_mb: "캐나다 (ca_mb)"
+ ca_nb: "캐나다 (ca_nb)"
+ ca_nl: "캐나다 (ca_nl)"
+ ca_ns: "캐나다 (ca_ns)"
+ ca_nt: "캐나다 (ca_nt)"
+ ca_nu: "캐나다 (ca_nu)"
+ ca_on: "캐나다 (ca_on)"
+ ca_pe: "캐나다 (ca_pe)"
+ ca_qc: "캐나다 (ca_qc)"
+ ca_sk: "캐나다 (ca_sk)"
+ ca_yt: "캐나다 (ca_yt)"
+ ca: "캐나다"
+ ch_ag: "스위스 (ch_ag)"
+ ch_ai: "스위스 (ch_ai)"
+ ch_ar: "스위스 (ch_ar)"
+ hk: "홍콩"
+ hr: "크로아티아"
+ in: "인도"
+ is: "아이슬란드"
+ kr: "한국 (대한민국)"
+ toolbar_button:
+ today: "오늘"
+ month: "월"
+ week: "주"
+ day: "일"
+ group_timezones:
+ search: "검색..."
+ discourse_post_event:
+ notifications:
+ invite_user_notification: "%{username} %{description}"
+ edit_reason: "이벤트 업데이트됨"
+ show_all: "모두 보기"
+ add_to_calendar: "캘린더에 추가"
+ edit_event: "이벤트 편집"
+ export_event: "이벤트 내보내기"
+ created_by: "작성자"
+ bulk_invite: "일괄 초대"
+ close_event: "이벤트 닫기"
+ bulk_invite_modal:
+ confirm: "확인"
+ text: "CSV 파일 업로드"
+ title: "일괄 초대"
+ success: "파일이 성공적으로 업로드되었습니다. 처리가 완료되면 메시지를 통해 알림을 받게됩니다."
+ error: "죄송합니다. 파일은 CSV 형식이어야 합니다."
+ send_bulk_invites: "초대장 보내기"
+ group_selector_placeholder: "그룹 선택..."
+ user_selector_placeholder: "사용자 선택..."
+ upcoming_events:
+ creator: "만든이"
+ status: "상태"
+ models:
+ invitee:
+ status:
+ unknown: "관심 없음"
+ interested: "관심"
+ event:
+ expired: "만료됨"
+ closed: "닫힘"
+ status:
+ public:
+ title: "공개"
+ private:
+ title: "비공개"
+ builder_modal:
+ custom_fields:
+ label: "사용자 정의 필드"
+ placeholder: "선택 사항"
+ create_event_title: "이벤트 만들기"
+ update_event_title: "이벤트 편집"
+ confirm_delete: "이 일정을 삭제하시겠습니까?"
+ create: "만들기"
+ update: "저장"
+ attach: "이벤트 만들기"
+ add_reminder: "알림 추가"
+ timezone:
+ label: 시간대
+ reminders:
+ label: "미리 알림"
+ units:
+ minutes: "분"
+ hours: "시간"
+ days: "일"
+ periods:
+ before: "이전"
+ after: "이후"
+ recurrence:
+ label: "반복"
+ none: "반복 없음"
+ every_day: "매일"
+ url:
+ label: "URL"
+ placeholder: "선택 사항"
+ location:
+ label: "위치"
+ description:
+ label: "내용"
+ name:
+ label: "이벤트 이름"
+ status:
+ label: "상태"
+ invite_user_or_group:
+ invite: "보내기"
diff --git a/plugins/discourse-calendar/config/locales/client.lt.yml b/plugins/discourse-calendar/config/locales/client.lt.yml
new file mode 100644
index 00000000000..90c94fe3694
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/client.lt.yml
@@ -0,0 +1,81 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+lt:
+ js:
+ discourse_automation:
+ triggerables:
+ event_started:
+ title: Įvykis prasidėjo
+ fields:
+ topic_id:
+ label: Temos ID
+ discourse_calendar:
+ disable_holiday: "Išjungti"
+ enable_holiday: "Įgalinti"
+ date: "Data"
+ region:
+ none: "Nieko"
+ toolbar_button:
+ today: "Šiandien"
+ month: "Per mėnesį"
+ week: "Per savaitę"
+ day: "Diena"
+ group_timezones:
+ search: "Paieška..."
+ discourse_post_event:
+ notifications:
+ invite_user_notification: "%{username} %{description}"
+ show_all: "Rodyti viską"
+ add_to_calendar: "Pridėti prie kalendoriaus"
+ created_by: "Sukurta"
+ bulk_invite: "Masinis kvietimas"
+ bulk_invite_modal:
+ confirm: "patvirtinti"
+ title: "Masinis kvietimas"
+ success: "Failas įkeltas sėkmingai, jums bus pranešta kada procesas bus baigtas."
+ error: "Atsiprašome, failas privalo buti CSV formato."
+ upcoming_events:
+ status: "Statusas"
+ models:
+ event:
+ expired: "Baigėsi galiojimo laikas"
+ closed: "Uždaryta"
+ status:
+ public:
+ title: "Vieša"
+ private:
+ title: "Privatu"
+ builder_modal:
+ custom_fields:
+ placeholder: "Pasirinktinai"
+ create: "Sukurti"
+ update: "Išsaugoti"
+ timezone:
+ label: Laiko zona
+ reminders:
+ units:
+ minutes: "minutės"
+ hours: "valandos"
+ days: "dienos"
+ periods:
+ before: "anksčiau"
+ after: "po to"
+ recurrence:
+ label: "Pasikartojimas"
+ none: "Nėra pasikartojimo"
+ every_day: "Kiekvieną dieną"
+ url:
+ label: "Nuoroda"
+ placeholder: "Pasirinktinai"
+ location:
+ label: "Vieta"
+ description:
+ label: "Aprašymas"
+ status:
+ label: "Statusas"
+ invite_user_or_group:
+ invite: "Siųsti"
diff --git a/plugins/discourse-calendar/config/locales/client.lv.yml b/plugins/discourse-calendar/config/locales/client.lv.yml
new file mode 100644
index 00000000000..3747a7571d1
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/client.lv.yml
@@ -0,0 +1,71 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+lv:
+ js:
+ discourse_automation:
+ triggerables:
+ event_started:
+ fields:
+ topic_id:
+ label: Tēmas ID
+ discourse_calendar:
+ disable_holiday: "Atslēgt"
+ enable_holiday: "Ieslēgt"
+ date: "Datums"
+ region:
+ none: "Nav"
+ toolbar_button:
+ today: "Šodien"
+ month: "Mēneša"
+ week: "Nedēļas"
+ day: "Diena"
+ group_timezones:
+ search: "Meklēt..."
+ discourse_post_event:
+ bulk_invite_modal:
+ success: "Fails veiksmīgi lejuplādēts, jums paziņos, kad process beidzies."
+ error: "Atvainojiet, failam jābūt CSV formātā."
+ upcoming_events:
+ status: "Statuss"
+ models:
+ event:
+ closed: "Slēgts"
+ status:
+ public:
+ title: "Publisks"
+ private:
+ title: "Privāts"
+ builder_modal:
+ custom_fields:
+ placeholder: "Pēc izvēles"
+ create: "Izveidot"
+ update: "Saglabāt"
+ timezone:
+ label: Laika zona
+ reminders:
+ units:
+ minutes: "minūte"
+ hours: "stunda"
+ days: "dienas"
+ periods:
+ before: "līdz"
+ after: "pēc"
+ recurrence:
+ label: "Atkārtošanās"
+ none: "Bez atkārtošanās"
+ every_day: "Katru dienu"
+ url:
+ label: "URL"
+ placeholder: "Pēc izvēles"
+ location:
+ label: "Atrašanās vieta"
+ description:
+ label: "Apraksts"
+ status:
+ label: "Statuss"
+ invite_user_or_group:
+ invite: "Sūtīt"
diff --git a/plugins/discourse-calendar/config/locales/client.nb_NO.yml b/plugins/discourse-calendar/config/locales/client.nb_NO.yml
new file mode 100644
index 00000000000..d8b347566f5
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/client.nb_NO.yml
@@ -0,0 +1,77 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+nb_NO:
+ js:
+ discourse_automation:
+ triggerables:
+ event_started:
+ fields:
+ topic_id:
+ label: Emne-ID
+ discourse_calendar:
+ disable_holiday: "Deaktiver"
+ enable_holiday: "Aktiver"
+ date: "Dato"
+ region:
+ none: "Ingen"
+ toolbar_button:
+ today: "I dag"
+ month: "Den siste måneden"
+ week: "Den siste uken"
+ day: "Dag"
+ group_timezones:
+ search: "Søk…"
+ discourse_post_event:
+ notifications:
+ invite_user_notification: "%{username} %{description}"
+ show_all: "Vis alle"
+ bulk_invite: "Bulk invitasjon"
+ bulk_invite_modal:
+ title: "Bulk invitasjon"
+ success: "Filen er lastet opp, du vil motta en melding når prosessesen er ferdig"
+ error: "Beklager, fila må være i CSV-format."
+ upcoming_events:
+ status: "Status"
+ models:
+ event:
+ expired: "Utløpt"
+ closed: "Lukket"
+ status:
+ public:
+ title: "Offentlig"
+ private:
+ title: "Privat"
+ builder_modal:
+ custom_fields:
+ placeholder: "Valgfritt"
+ create: "Opprett"
+ update: "Lagre"
+ timezone:
+ label: Tidssone
+ reminders:
+ units:
+ minutes: "minutter"
+ hours: "timer"
+ days: "dager"
+ periods:
+ before: "før"
+ after: "etter"
+ recurrence:
+ label: "Regelmessighet"
+ none: "Ingen regelmessighet"
+ every_day: "Hver dag"
+ url:
+ label: "URL"
+ placeholder: "Valgfritt"
+ location:
+ label: "Sted"
+ description:
+ label: "Beskrivelse"
+ status:
+ label: "Status"
+ invite_user_or_group:
+ invite: "Send"
diff --git a/plugins/discourse-calendar/config/locales/client.nl.yml b/plugins/discourse-calendar/config/locales/client.nl.yml
new file mode 100644
index 00000000000..336d4a8e616
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/client.nl.yml
@@ -0,0 +1,482 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+nl:
+ admin_js:
+ admin:
+ calendar: "Kalender"
+ site_settings:
+ categories:
+ discourse_post_event: "Discourse-evenement"
+ discourse_calendar: "Discourse-kalender"
+ js:
+ notifications:
+ titles:
+ event_reminder: "evenementherinnering"
+ event_invitation: "evenementuitnodiging"
+ popup:
+ event_reminder: Evenementherinnering
+ discourse_automation:
+ triggerables:
+ event_started:
+ title: Evenement begonnen
+ fields:
+ topic_id:
+ label: Topic-ID
+ discourse_calendar:
+ invite_user_notification: "%{username} heeft je uitgenodigd voor: %{description}"
+ on_holiday: "Op feestdag"
+ disable_holiday: "Uitschakelen"
+ enable_holiday: "Inschakelen"
+ holiday: "Feestdag"
+ holidays:
+ header_title: "Feestdag"
+ pick_region_description: "Kies een regio om de feestdagen voor die regio te zien."
+ disabled_holidays_description: "Uitgeschakelde feestdagen worden uitgesloten van de feestdagenkalender voor medewerkers."
+ date: "Datum"
+ add_to_calendar: "Toevoegen aan Google-agenda"
+ toggle_timezone_offset_title: "Tijdzoneoffset schakelen"
+ region:
+ title: "Regio"
+ none: "Geen"
+ use_current_region: "Huidige regio gebruiken"
+ names:
+ ae: "Verenigde Arabische Emiraten"
+ ar: "Argentinië"
+ at: "Oostenrijk"
+ au_act: "Australië (au_act)"
+ au_nsw: "Australië (au_nsw)"
+ au_nt: "Australië (au_nt)"
+ au_qld_brisbane: "Australië (au_qld_brisbane)"
+ au_qld_cairns: "Australië (au_qld_cairns)"
+ au_qld: "Australië (au_qld)"
+ au_sa: "Australië (au_sa)"
+ au_tas_north: "Australië (au_tas_north)"
+ au_tas_south: "Australië (au_tas_south)"
+ au_tas: "Australië (au_tas)"
+ au_vic_melbourne: "Australië (au_vic_melbourne)"
+ au_vic: "Australië (au_vic)"
+ au_wa: "Australië (au_wa)"
+ au: "Australië"
+ be_fr: "België (be_fr)"
+ be_nl: "België (be_nl)"
+ bg_bg: "Bulgarije (bg_bg)"
+ bg_en: "Bulgarije (bg_en)"
+ br: "Brazilië"
+ br_sp: "Brazilië (br_sp)"
+ br_spcapital: "Brazilië (br_spcapital)"
+ ca_ab: "Canada (ca_ab)"
+ ca_bc: "Canada (ca_bc)"
+ ca_mb: "Canada (ca_mb)"
+ ca_nb: "Canada (ca_nb)"
+ ca_nl: "Canada (ca_nl)"
+ ca_ns: "Canada (ca_ns)"
+ ca_nt: "Canada (ca_nt)"
+ ca_nu: "Canada (ca_nu)"
+ ca_on: "Canada (ca_on)"
+ ca_pe: "Canada (ca_pe)"
+ ca_qc: "Canada (ca_qc)"
+ ca_sk: "Canada (ca_sk)"
+ ca_yt: "Canada (ca_yt)"
+ ca: "Canada"
+ ch_ag: "Zwitserland (ch_ag)"
+ ch_ai: "Zwitserland (ch_ai)"
+ ch_ar: "Zwitserland (ch_ar)"
+ ch_be: "Zwitserland (ch_be)"
+ ch_bl: "Zwitserland (ch_bl)"
+ ch_bs: "Zwitserland (ch_bs)"
+ ch_fr: "Zwitserland (ch_fr)"
+ ch_ge: "Zwitserland (ch_ge)"
+ ch_gl: "Zwitserland (ch_gl)"
+ ch_gr: "Zwitserland (ch_gr)"
+ ch_ju: "Zwitserland (ch_ju)"
+ ch_lu: "Zwitserland (ch_lu)"
+ ch_ne: "Zwitserland (ch_ne)"
+ ch_nw: "Zwitserland (ch_nw)"
+ ch_ow: "Zwitserland (ch_ow)"
+ ch_sg: "Zwitserland (ch_sg)"
+ ch_sh: "Zwitserland (ch_sh)"
+ ch_so: "Zwitserland (ch_so)"
+ ch_sz: "Zwitserland (ch_sz)"
+ ch_tg: "Zwitserland (ch_tg)"
+ ch_ti: "Zwitserland (ch_ti)"
+ ch_ur: "Zwitserland (ch_ur)"
+ ch_vd: "Zwitserland (ch_vd)"
+ ch_vs: "Zwitserland (ch_vs)"
+ ch_zg: "Zwitserland (ch_zg)"
+ ch_zh: "Zwitserland (ch_zh)"
+ ch: "Zwitserland"
+ cl: "Chili"
+ co: "Colombia"
+ cr: "Costa Rica"
+ cz: "Tsjechië"
+ de_bb: "Duitsland (de_bb)"
+ de_be: "Duitsland (de_be)"
+ de_bw: "Duitsland (de_bw)"
+ de_by_augsburg: "Duitsland (de_by_augsburg)"
+ de_by_cath: "Duitsland (de_by_cath)"
+ de_by: "Duitsland (de_door)"
+ de_hb: "Duitsland (de_hb)"
+ de_he: "Duitsland (de_he)"
+ de_hh: "Duitsland (de_hh)"
+ de_mv: "Duitsland (de_mv)"
+ de_ni: "Duitsland (de_ni)"
+ de_nw: "Duitsland (de_nw)"
+ de_rp: "Duitsland (de_rp)"
+ de_sh: "Duitsland (de_sh)"
+ de_sl: "Duitsland (de_sl)"
+ de_sn_sorbian: "Duitsland (de_sn_sorbian)"
+ de_sn: "Duitsland (de_sn)"
+ de_st: "Duitsland (de_st)"
+ de_th_cath: "Duitsland (de_th_cath)"
+ de_th: "Duitsland (de_th)"
+ de: "Duitsland"
+ dk: "Denemarken"
+ ee: "Estland"
+ el: "Griekenland"
+ es_an: "Spanje (es_an)"
+ es_ar: "Spanje (es_ar)"
+ es_ce: "Spanje (es_ce)"
+ es_cl: "Spanje (es_cl)"
+ es_cm: "Spanje (es_cm)"
+ es_cn: "Spanje (es_cn)"
+ es_ct: "Spanje (es_ct)"
+ es_ex: "Spanje (es_ex)"
+ es_ga: "Spanje (es_ga)"
+ es_ib: "Spanje (es_ib)"
+ es_lo: "Spanje (es_lo)"
+ es_m: "Spanje (es_m)"
+ es_mu: "Spanje (es_mu)"
+ es_na: "Spanje (es_na)"
+ es_o: "Spanje (es_o)"
+ es_pv: "Spanje (es_pv)"
+ es_v: "Spanje (es_v)"
+ es_vc: "Spanje (es_vc)"
+ es: "Spanje"
+ fi: "Finland"
+ fr_a: "Frankrijk (fr_a)"
+ fr_m: "Frankrijk (fr_m)"
+ fr: "Frankrijk"
+ gb_con: "Verenigd Koninkrijk (gb_con)"
+ gb_eaw: "Verenigd Koninkrijk (gb_eaw)"
+ gb_eng: "Verenigd Koninkrijk (gb_eng)"
+ gb_gsy: "Verenigd Koninkrijk (gb_gsy)"
+ gb_iom: "Verenigd Koninkrijk (gb_iom)"
+ gb_jsy: "Verenigd Koninkrijk (gb_jsy)"
+ gb_nir: "Verenigd Koninkrijk (gb_nir)"
+ gb_sct: "Verenigd Koninkrijk (gb_sct)"
+ gb_wls: "Verenigd Koninkrijk (gb_wls)"
+ gb: "Verenigd Koninkrijk"
+ ge: "Georgië"
+ gg: "Guernsey"
+ gh: "Ghana"
+ hk: "Hongkong"
+ hr: "Kroatië"
+ hu: "Hongarije"
+ id: "Indonesië"
+ ie: "Ierland"
+ im: "Eiland Man"
+ in: "India"
+ in_gj: "India (in_gj)"
+ in_mh: "India (in_mh)"
+ in_rj: "India (in_rj)"
+ in_tn: "India (in_tn)"
+ in_ka: "India (in_ka)"
+ is: "IJsland"
+ it_bl: "Italië (it_bl)"
+ it_fi: "Italië (it_fi)"
+ it_ge: "Italië (it_ge)"
+ it_pd: "Italië (it_pd)"
+ it_rm: "Italië (it_rm)"
+ it_ro: "Italië (it_ro)"
+ it_to: "Italië (it_to)"
+ it_tv: "Italië (it_tv)"
+ it_ve: "Italië (it_ve)"
+ it_vi: "Italië (it_vi)"
+ it_vr: "Italië (it_vr)"
+ it: "Italië"
+ je: "Jersey"
+ jp: "Japan"
+ ke: "Kenia"
+ kr: "Zuid-Korea"
+ kz: "Kazachstan"
+ li: "Liechtenstein"
+ lt: "Litouwen"
+ lu: "Luxemburg"
+ lv: "Letland"
+ ma: "Marokko"
+ mt_en: "Malta (mt_en)"
+ mt_mt: "Malta (mt_mt)"
+ mx_pue: "Mexico (mx_pue)"
+ mx: "Mexico"
+ my: "Maleisië"
+ ng: "Nigeria"
+ nl: "Nederland"
+ "no": "Noorwegen"
+ nz_ak: "Nieuw-Zeeland (nz_ak)"
+ nz_ca: "Nieuw-Zeeland (nz_ca)"
+ nz_ch: "Nieuw-Zeeland (nz_ch)"
+ nz_hb: "Nieuw-Zeeland (nz_hb)"
+ nz_mb: "Nieuw-Zeeland (nz_mb)"
+ nz_ne: "Nieuw-Zeeland (nz_ne)"
+ nz_nl: "Nieuw-Zeeland (nz_nl)"
+ nz_ot: "Nieuw-Zeeland (nz_ot)"
+ nz_sc: "Nieuw-Zeeland (nz_sc)"
+ nz_sl: "Nieuw-Zeeland (nz_sl)"
+ nz_ta: "Nieuw-Zeeland (nz_ta)"
+ nz_we: "Nieuw-Zeeland (nz_we)"
+ nz_wl: "Nieuw-Zeeland (nz_wl)"
+ nz: "Nieuw-Zeeland"
+ pe: "Peru"
+ ph: "Filippijnen"
+ pl: "Polen"
+ pt_li: "Portugal (pt_li)"
+ pt_po: "Portugal (pt_po)"
+ pt: "Portugal"
+ ro: "Roemenië"
+ rs_cyrl: "Servië (rs_cyrl)"
+ rs_la: "Servië (rs_la)"
+ ru: "Rusland"
+ se: "Zweden"
+ sa: "Saoedi-Arabië"
+ sg: "Singapore"
+ si: "Slovenië"
+ sk: "Slowakije"
+ th: "Thailand"
+ tn: "Tunesië"
+ tr: "Turkije"
+ ua: "Oekraïne"
+ us_ak: "Verenigde Staten (us_ak)"
+ us_al: "Verenigde Staten (us_al)"
+ us_ar: "Verenigde Staten (us_ar)"
+ us_az: "Verenigde Staten (us_az)"
+ us_ca: "Verenigde Staten (us_ca)"
+ us_co: "Verenigde Staten (us_co)"
+ us_ct: "Verenigde Staten (us_ct)"
+ us_dc: "Verenigde Staten (us_dc)"
+ us_de: "Verenigde Staten (us_de)"
+ us_fl: "Verenigde Staten (us_fl)"
+ us_ga: "Verenigde Staten (us_ga)"
+ us_gu: "Verenigde Staten (us_gu)"
+ us_hi: "Verenigde Staten (us_hi)"
+ us_ia: "Verenigde Staten (us_ia)"
+ us_id: "Verenigde Staten (us_id)"
+ us_il: "Verenigde Staten (us_il)"
+ us_in: "Verenigde Staten (us_in)"
+ us_ks: "Verenigde Staten (us_ks)"
+ us_ky: "Verenigde Staten (us_ky)"
+ us_la: "Verenigde Staten (us_la)"
+ us_ma: "Verenigde Staten (us_ma)"
+ us_md: "Verenigde Staten (us_md)"
+ us_me: "Verenigde Staten (us_me)"
+ us_mi: "Verenigde Staten (us_mi)"
+ us_mn: "Verenigde Staten (us_mn)"
+ us_mo: "Verenigde Staten (us_mo)"
+ us_ms: "Verenigde Staten (us_ms)"
+ us_mt: "Verenigde Staten (us_mt)"
+ us_nc: "Verenigde Staten (us_nc)"
+ us_nd: "Verenigde Staten (us_nd)"
+ us_ne: "Verenigde Staten (us_ne)"
+ us_nh: "Verenigde Staten (us_nh)"
+ us_nj: "Verenigde Staten (us_nj)"
+ us_nm: "Verenigde Staten (us_nm)"
+ us_nv: "Verenigde Staten (us_nv)"
+ us_ny: "Verenigde Staten (us_ny)"
+ us_oh: "Verenigde Staten (us_oh)"
+ us_ok: "Verenigde Staten (us_ok)"
+ us_or: "Verenigde Staten (us_or)"
+ us_pa: "Verenigde Staten (us_pa)"
+ us_pr: "Verenigde Staten (us_pr)"
+ us_ri: "Verenigde Staten (us_ri)"
+ us_sc: "Verenigde Staten (us_sc)"
+ us_sd: "Verenigde Staten (us_sd)"
+ us_tn: "Verenigde Staten (us_tn)"
+ us_tx: "Verenigde Staten (us_tx)"
+ us_ut: "Verenigde Staten (us_ut)"
+ us_va: "Verenigde Staten (us_va)"
+ us_vi: "Verenigde Staten (us_vi)"
+ us_vt: "Verenigde Staten (us_vt)"
+ us_wa: "Verenigde Staten (us_wa)"
+ us_wi: "Verenigde Staten (us_wi)"
+ us_wv: "Verenigde Staten (us_wv)"
+ us_wy: "Verenigde Staten (us_wy)"
+ us: "Verenigde Staten"
+ ve: "Venezuela"
+ vi: "Maagdeneilanden (VS)"
+ za: "Zuid-Afrika"
+ toolbar_button:
+ today: "Vandaag"
+ month: "Maand"
+ week: "Week"
+ day: "Dag"
+ list: "Lijst"
+ group_timezones:
+ search: "Zoeken..."
+ group_availability: "Beschikbaarheid %{groep}"
+ discourse_post_event:
+ notifications:
+ before_event_reminder: "Een evenement gaat beginnen"
+ after_event_reminder: "Een evenement is afgelopen"
+ ongoing_event_reminder: "Er is een evenement gaande"
+ invite_user_notification: "%{username} %{description}"
+ invite_user_predefined_attendance_notification_html: "%{username} heeft automatisch je aanwezigheid ingesteld en je uitgenodigd voor %{description}"
+ before_event_reminder_html: "Een evenement gaat beginnen %{description}"
+ after_event_reminder_html: "Een evenement is afgelopen %{description}"
+ ongoing_event_reminder_html: "Er is een evenement gaande %{description}"
+ edit_reason: "Evenement bijgewerkt"
+ edit_reason_closed: "Evenement gesloten"
+ edit_reason_opened: "Evenement geopend"
+ topic_title:
+ starts_at: "Evenement begint: %{datum}"
+ ended_at: "Evenement eindigt: %{datum}"
+ ends_in_duration: "Eindigt %{duur}"
+ show_all: "Alles weergeven"
+ show_participants: "Deelnemers weergeven"
+ participants:
+ one: "%{count} gebruiker heeft deelgenomen."
+ other: "%{count} gebruikers hebben deelgenomen."
+ invite: "Gebruiker informeren"
+ add_to_calendar: "Toevoegen aan agenda"
+ send_pm_to_creator: "PB sturen naar %{username}"
+ leave: "Evenement verlaten"
+ edit_event: "Evenement bewerken"
+ export_event: "Evenement exporteren"
+ created_by: "Gemaakt door"
+ bulk_invite: "Bulkuitnodiging"
+ close_event: "Evenement sluiten"
+ open_event: "Evenement openen"
+ invitees_modal:
+ title_invited: "Evenementdeelname"
+ title_participated: "Lijst van deelnemende gebruikers"
+ filter_placeholder: "Gebruikers filteren"
+ remove_invitee: "Uitgenodigde verwijderen uit lijst"
+ add_invitee: "Uitgenodigde toevoegen aan lijst"
+ bulk_invite_modal:
+ confirm: "bevestigen"
+ text: "CSV-bestand uploaden"
+ title: "Bulkuitnodiging"
+ success: "Bestand geüpload, je ontvangt een bericht wanneer het proces voltooid is."
+ error: "Sorry, het bestand moet in CSV-indeling zijn."
+ confirmation_message: "Je staat op het punt om iedereen in het geüploade bestand te informeren."
+ description_public: "Openbare evenementen accepteren alleen gebruikersnamen voor bulkuitnodigingen."
+ description_private: "Privé-evenementen accepteren alleen groepsnamen voor bulkuitnodigingen."
+ download_sample_csv: "Voorbeeld-CSV-bestand downloaden"
+ send_bulk_invites: "Uitnodigingen sturen"
+ group_selector_placeholder: "Kies een groep..."
+ user_selector_placeholder: "Kies gebruiker..."
+ inline_title: "Inline bulkuitnodiging"
+ csv_title: "CSV-bulkuitnodiging"
+ upcoming_events:
+ title: "Aankomende evenementen"
+ creator: "Maker"
+ status: "Status"
+ starts_at: "Begint om"
+ upcoming_events_list:
+ title: "Aankomende evenementen"
+ empty: "Geen aankomende evenementen"
+ all_day: "Hele dag"
+ error: "Ophalen van evenementen mislukt"
+ try_again: "Opnieuw proberen"
+ view_all: "Alles weergeven"
+ category:
+ sort_topics_by_event_start_date: "Sorteer topics op de begindatum van evenementen."
+ disable_topic_resorting: "Schakel sorteren van topics uit."
+ settings_sections:
+ event_sorting: "Evenementen sorteren"
+ preview:
+ more_than_one_event: "Je kunt niet meer dan één evenement hebben."
+ models:
+ invitee:
+ no_users: "Geen gebruikers gevonden"
+ status:
+ unknown: "Niet geïnteresseerd"
+ going: "Gaan"
+ not_going: "Niet gaan"
+ interested: "Geïnteresseerd"
+ going_count:
+ one: "%{count} gaat"
+ other: "%{count} gaan"
+ not_going_count:
+ one: "%{count} gaat niet"
+ other: "%{count} gaan niet"
+ interested_count:
+ one: "%{count} is geïnteresseerd"
+ other: "%{count} zijn geïnteresseerd"
+ invited_count:
+ one: "%{count} gebruiker uitgenodigd"
+ other: "%{count} gebruikers uitgenodigd"
+ event:
+ expired: "Verlopen"
+ closed: "Gesloten"
+ status:
+ standalone:
+ title: "Losstaand"
+ description: "Er kan niet worden deelgenomen aan een losstaand evenement."
+ public:
+ title: "Openbaar"
+ description: "Iedereen kan deelnemen aan een openbaar evenement."
+ private:
+ title: "Privé"
+ description: "Alleen uitgenodigde gebruikers kunnen deelnemen aan een privé-evenement."
+ builder_modal:
+ custom_fields:
+ label: "Aangepaste velden"
+ placeholder: "Optioneel"
+ description: "Toegestane aangepaste velden worden gedefinieerd in de site-instellingen. Aangepaste velden worden gebruikt om gegevens door te geven aan andere plug-ins."
+ create_event_title: "Evenement maken"
+ update_event_title: "Evenement bewerken"
+ confirm_delete: "Weet je zeker dat je dit evenement wilt verwijderen?"
+ confirm_close: "Weet je zeker dat je dit evenement wilt sluiten?"
+ confirm_open: "Weet je zeker dat je dit evenement wilt openen?"
+ create: "Maken"
+ update: "Opslaan"
+ attach: "Evenement maken"
+ add_reminder: "Herinnering toevoegen"
+ timezone:
+ label: Tijdzone
+ remove_timezone: Geen tijdzone (UTC)
+ reminders:
+ label: "Herinneringen"
+ types:
+ bump_topic: "topic automatisch omhoog plaatsen"
+ notification: "deelnemers informeren"
+ units:
+ minutes: "minuten"
+ hours: "uur"
+ days: "dagen"
+ weeks: "weken"
+ periods:
+ before: "voor"
+ after: "na"
+ recurrence:
+ label: "Herhaling"
+ none: "Geen herhaling"
+ every_day: "Elke dag"
+ every_month: "Elke maand op deze weekdag"
+ every_weekday: "Elke weekdag"
+ every_week: "Elke week op deze weekdag"
+ every_two_weeks: "Elke twee weken op deze weekdag"
+ every_four_weeks: "Elke vier weken op deze weekdag"
+ minimal:
+ label: "Minimaal evenement"
+ checkbox_label: "Knoppen Gaan/Niet gaan en genodigdenstatus verbergen"
+ url:
+ label: "URL"
+ placeholder: "Optioneel"
+ location:
+ label: "Locatie"
+ description:
+ label: "Beschrijving"
+ name:
+ label: "Evenementnaam"
+ placeholder: "Optioneel, standaard ingesteld op topictitel"
+ invitees:
+ label: "Uitgenodigde groepen"
+ status:
+ label: "Status"
+ invite_user_or_group:
+ title: "Gebruiker(s) of groep(en) informeren"
+ invite: "Sturen"
diff --git a/plugins/discourse-calendar/config/locales/client.pl_PL.yml b/plugins/discourse-calendar/config/locales/client.pl_PL.yml
new file mode 100644
index 00000000000..459e8a46aea
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/client.pl_PL.yml
@@ -0,0 +1,459 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+pl_PL:
+ admin_js:
+ admin:
+ calendar: "Kalendarz"
+ site_settings:
+ categories:
+ discourse_post_event: "Wydarzenie Discourse"
+ discourse_calendar: "Kalendarz Discourse"
+ js:
+ notifications:
+ titles:
+ event_reminder: "przypomnienie o wydarzeniu"
+ event_invitation: "zaproszenie na wydarzenie"
+ popup:
+ event_reminder: Przypomnienie o wydarzeniu
+ discourse_automation:
+ triggerables:
+ event_started:
+ title: Wydarzenie rozpoczęte
+ fields:
+ topic_id:
+ label: ID tematu
+ discourse_calendar:
+ invite_user_notification: "%{username} zaprosił Cię do: %{description}"
+ on_holiday: "Na świętach"
+ disable_holiday: "Wyłącz"
+ enable_holiday: "Włącz"
+ holiday: "Świeto"
+ holidays:
+ header_title: "Święta"
+ pick_region_description: "Wybierz region, aby zobaczyć święta dla tego regionu."
+ disabled_holidays_description: "Wyłączone wakacje zostaną wyłączone z kalendarza wakacyjnego personelu."
+ date: "Data"
+ add_to_calendar: "Dodaj do Kalendarza Google"
+ toggle_timezone_offset_title: "Przełącz przesunięcie strefy czasowej"
+ region:
+ title: "Region"
+ none: "Brak"
+ use_current_region: "Użyj bieżącego regionu"
+ names:
+ ar: "Argentyna"
+ at: "Austria"
+ au_act: "Australia (au_act)"
+ au_nsw: "Australia (au_nsw)"
+ au_nt: "Australia (au_nt)"
+ au_qld_brisbane: "Australia (au_qld_brisbane)"
+ au_qld_cairns: "Australia (au_qld_cairns)"
+ au_qld: "Australia (au_qld)"
+ au_sa: "Australia (au_sa)"
+ au_tas_north: "Australia (au_tas_north)"
+ au_tas_south: "Australia (au_tas_south)"
+ au_tas: "Australia (au_tas)"
+ au_vic_melbourne: "Australia (au_vic_melbourne)"
+ au_vic: "Australia (au_vic)"
+ au_wa: "Australia (au_wa)"
+ au: "Australia"
+ be_fr: "Belgia (be_fr)"
+ be_nl: "Belgia (be_nl)"
+ bg_bg: "Bułgaria (bg_bg)"
+ bg_en: "Bułgaria (bg_en)"
+ br: "Brazylia"
+ br_sp: "Brazylia (br_sp)"
+ br_spcapital: "Brazylia (br_spcapital)"
+ ca_ab: "Kanada (ca_ab)"
+ ca_bc: "Kanada (ca_bc)"
+ ca_mb: "Kanada (ca_mb)"
+ ca_nb: "Kanada (ca_nb)"
+ ca_nl: "Kanada (ca_nl)"
+ ca_ns: "Kanada (ca_ns)"
+ ca_nt: "Kanada (ca_nt)"
+ ca_nu: "Kanada (ca_nu)"
+ ca_on: "Kanada (ca_on)"
+ ca_pe: "Kanada (ca_pe)"
+ ca_qc: "Kanada (ca_qc)"
+ ca_sk: "Kanada (ca_sk)"
+ ca_yt: "Kanada (ca_yt)"
+ ca: "Kanada"
+ ch_ag: "Szwajcaria (ch_ag)"
+ ch_ai: "Szwajcaria (ch_ai)"
+ ch_ar: "Szwajcaria (ch_ar)"
+ ch_be: "Szwajcaria (ch_be)"
+ ch_bl: "Szwajcaria (ch_bl)"
+ ch_bs: "Szwajcaria (ch_bs)"
+ ch_fr: "Szwajcaria (ch_fr)"
+ ch_ge: "Szwajcaria (ch_ge)"
+ ch_gl: "Szwajcaria (ch_gl)"
+ ch_gr: "Szwajcaria (ch_gr)"
+ ch_ju: "Szwajcaria (ch_ju)"
+ ch_lu: "Szwajcaria (ch_lu)"
+ ch_ne: "Szwajcaria (ch_ne)"
+ ch_nw: "Szwajcaria (ch_nw)"
+ ch_ow: "Szwajcaria (ch_ow)"
+ ch_sg: "Szwajcaria (ch_sg)"
+ ch_sh: "Szwajcaria (ch_sh)"
+ ch_so: "Szwajcaria (ch_so)"
+ ch_sz: "Szwajcaria (ch_sz)"
+ ch_tg: "Szwajcaria (ch_tg)"
+ ch_ti: "Szwajcaria (ch_ti)"
+ ch_ur: "Szwajcaria (ch_ur)"
+ ch_vd: "Szwajcaria (ch_vd)"
+ ch_vs: "Szwajcaria (ch_vs)"
+ ch_zg: "Szwajcaria (ch_zg)"
+ ch_zh: "Szwajcaria (ch_zh)"
+ ch: "Szwajcaria"
+ cl: "Chile"
+ co: "Kolumbia"
+ cr: "Kostaryka"
+ cz: "Republika Czeska"
+ de_bb: "Niemcy (de_bb)"
+ de_be: "Niemcy (de_be)"
+ de_bw: "Niemcy (de_bw)"
+ de_by_augsburg: "Niemcy (de_by_augsburg)"
+ de_by_cath: "Niemcy (de_by_cath)"
+ de_by: "Niemcy (de_by)"
+ de_hb: "Niemcy (de_hb)"
+ de_he: "Niemcy (de_he)"
+ de_hh: "Niemcy (de_hh)"
+ de_mv: "Niemcy (de_mv)"
+ de_ni: "Niemcy (de_ni)"
+ de_nw: "Niemcy (de_nw)"
+ de_rp: "Niemcy (de_rp)"
+ de_sh: "Niemcy (de_sh)"
+ de_sl: "Niemcy (de_sl)"
+ de_sn_sorbian: "Niemcy (de_sn_sorbian)"
+ de_sn: "Niemcy (de_sn)"
+ de_st: "Niemcy (de_st)"
+ de_th_cath: "Niemcy (de_th_cath)"
+ de_th: "Niemcy (de_th)"
+ de: "Niemcy"
+ dk: "Dania"
+ ee: "Estonia"
+ el: "Grecja"
+ es_an: "Hiszpania (es_an)"
+ es_ar: "Hiszpania (es_ar)"
+ es_ce: "Hiszpania (es_ce)"
+ es_cl: "Hiszpania (es_cl)"
+ es_cm: "Hiszpania (es_cm)"
+ es_cn: "Hiszpania (es_cn)"
+ es_ct: "Hiszpania (es_ct)"
+ es_ex: "Hiszpania (es_ex)"
+ es_ga: "Hiszpania (es_ga)"
+ es_ib: "Hiszpania (es_ib)"
+ es_lo: "Hiszpania (es_lo)"
+ es_m: "Hiszpania (es_m)"
+ es_mu: "Hiszpania (es_mu)"
+ es_na: "Hiszpania (es_na)"
+ es_o: "Hiszpania (es_o)"
+ es_pv: "Hiszpania (es_pv)"
+ es_v: "Hiszpania (es_v)"
+ es_vc: "Hiszpania (es_vc)"
+ es: "Hiszpania"
+ fi: "Finlandia"
+ fr_a: "Francja (fr_a)"
+ fr_m: "Francja (fr_m)"
+ fr: "Francja"
+ gb_con: "Wielka Brytania (gb_con)"
+ gb_eaw: "Wielka Brytania (gb_eaw)"
+ gb_eng: "Wielka Brytania (gb_eng)"
+ gb_gsy: "Wielka Brytania (gb_gsy)"
+ gb_iom: "Wielka Brytania (gb_iom)"
+ gb_jsy: "Wielka Brytania (gb_jsy)"
+ gb_nir: "Wielka Brytania (gb_nir)"
+ gb_sct: "Wielka Brytania (gb_sct)"
+ gb_wls: "Wielka Brytania (gb_wls)"
+ gb: "Wielka Brytania"
+ ge: "Gruzja"
+ gg: "Guernsey"
+ gh: "Ghana"
+ hk: "Hongkong"
+ hr: "Chorwacja"
+ hu: "Węgry"
+ id: "Indonezja"
+ ie: "Irlandia"
+ im: "Wyspa Man"
+ in: "Indie"
+ in_gj: "Indie (in_gj)"
+ in_mh: "Indie (w_mh)"
+ in_rj: "Indie (in_rj)"
+ in_tn: "Indie (w_tn)"
+ is: "Islandia"
+ it_bl: "Włochy (it_bl)"
+ it_fi: "Włochy (it_fi)"
+ it_ge: "Włochy (it_ge)"
+ it_pd: "Włochy (it_pd)"
+ it_rm: "Włochy (it_rm)"
+ it_ro: "Włochy (it_ro)"
+ it_to: "Włochy (it_to)"
+ it_tv: "Włochy (it_tv)"
+ it_ve: "Włochy (it_ve)"
+ it_vi: "Włochy (it_vi)"
+ it_vr: "Włochy (it_vr)"
+ it: "Włochy"
+ je: "Jersey"
+ jp: "Japonia"
+ ke: "Kenia"
+ kr: "Korea (Republika)"
+ kz: "Kazachstan (Republika)"
+ li: "Liechtenstein"
+ lt: "Litwa"
+ lu: "Luksemburg"
+ lv: "Łotwa"
+ ma: "Maroko"
+ mt_en: "Malta (mt_en)"
+ mt_mt: "Malta (mt_mt)"
+ mx_pue: "Meksyk (mx_pue)"
+ mx: "Meksyk"
+ my: "Malezja"
+ ng: "Nigeria"
+ nl: "Holandia"
+ "no": "Norwegia"
+ nz_ak: "Nowa Zelandia (nz_ak)"
+ nz_ca: "Nowa Zelandia (nz_ca)"
+ nz_ch: "Nowa Zelandia (nz_ch)"
+ nz_hb: "Nowa Zelandia (nz_hb)"
+ nz_mb: "Nowa Zelandia (nz_mb)"
+ nz_ne: "Nowa Zelandia (nz_ne)"
+ nz_nl: "Nowa Zelandia (nz_nl)"
+ nz_ot: "Nowa Zelandia (nz_ot)"
+ nz_sc: "Nowa Zelandia (nz_sc)"
+ nz_sl: "Nowa Zelandia (nz_sl)"
+ nz_ta: "Nowa Zelandia (nz_ta)"
+ nz_we: "Nowa Zelandia (nz_we)"
+ nz_wl: "Nowa Zelandia (nz_wl)"
+ nz: "Nowa Zelandia"
+ pe: "Peru"
+ ph: "Filipiny"
+ pl: "Polska"
+ pt_li: "Portugalia (pt_li)"
+ pt_po: "Portugalia (pt_po)"
+ pt: "Portugalia"
+ ro: "Rumunia"
+ rs_cyrl: "Serbia (rs_cyrl)"
+ rs_la: "Serbia (rs_la)"
+ ru: "Federacja Rosyjska"
+ se: "Szwecja"
+ sa: "Arabia Saudyjska"
+ sg: "Singapur"
+ si: "Słowenia"
+ sk: "Słowacja"
+ th: "Tajlandia"
+ tn: "Tunezja"
+ tr: "Turcja"
+ ua: "Ukraina"
+ us_ak: "Stany Zjednoczone (us_ak)"
+ us_al: "Stany Zjednoczone (us_al)"
+ us_ar: "Stany Zjednoczone (us_ar)"
+ us_az: "Stany Zjednoczone (us_az)"
+ us_ca: "Stany Zjednoczone (us_ca)"
+ us_co: "Stany Zjednoczone (us_co)"
+ us_ct: "Stany Zjednoczone (us_ct)"
+ us_dc: "Stany Zjednoczone (us_dc)"
+ us_de: "Stany Zjednoczone (us_de)"
+ us_fl: "Stany Zjednoczone (us_fl)"
+ us_ga: "Stany Zjednoczone (us_ga)"
+ us_gu: "Stany Zjednoczone (us_gu)"
+ us_hi: "Stany Zjednoczone (us_hi)"
+ us_ia: "Stany Zjednoczone (us_ia)"
+ us_id: "Stany Zjednoczone (us_id)"
+ us_il: "Stany Zjednoczone (us_il)"
+ us_in: "Stany Zjednoczone (us_in)"
+ us_ks: "Stany Zjednoczone (us_ks)"
+ us_ky: "Stany Zjednoczone (us_ky)"
+ us_la: "Stany Zjednoczone (us_la)"
+ us_ma: "Stany Zjednoczone (us_ma)"
+ us_md: "Stany Zjednoczone (us_md)"
+ us_me: "Stany Zjednoczone (us_me)"
+ us_mi: "Stany Zjednoczone (us_mi)"
+ us_mn: "Stany Zjednoczone (us_mn)"
+ us_mo: "Stany Zjednoczone (us_mo)"
+ us_ms: "Stany Zjednoczone (us_ms)"
+ us_mt: "Stany Zjednoczone (us_mt)"
+ us_nc: "Stany Zjednoczone (us_nc)"
+ us_nd: "Stany Zjednoczone (us_nd)"
+ us_ne: "Stany Zjednoczone (us_ne)"
+ us_nh: "Stany Zjednoczone (us_nh)"
+ us_nj: "Stany Zjednoczone (us_nj)"
+ us_nm: "Stany Zjednoczone (us_nm)"
+ us_nv: "Stany Zjednoczone (us_nv)"
+ us_ny: "Stany Zjednoczone (us_ny)"
+ us_oh: "Stany Zjednoczone (us_oh)"
+ us_ok: "Stany Zjednoczone (us_ok)"
+ us_or: "Stany Zjednoczone (us_or)"
+ us_pa: "Stany Zjednoczone (us_pa)"
+ us_pr: "Stany Zjednoczone (us_pr)"
+ us_ri: "Stany Zjednoczone (us_ri)"
+ us_sc: "Stany Zjednoczone (us_sc)"
+ us_sd: "Stany Zjednoczone (us_sd)"
+ us_tn: "Stany Zjednoczone (us_tn)"
+ us_tx: "Stany Zjednoczone (us_tx)"
+ us_ut: "Stany Zjednoczone (us_ut)"
+ us_va: "Stany Zjednoczone (us_va)"
+ us_vi: "Stany Zjednoczone (us_vi)"
+ us_vt: "Stany Zjednoczone (us_vt)"
+ us_wa: "Stany Zjednoczone (us_wa)"
+ us_wi: "Stany Zjednoczone (us_wi)"
+ us_wv: "Stany Zjednoczone (us_wv)"
+ us_wy: "Stany Zjednoczone (us_wy)"
+ us: "Stany Zjednoczone"
+ ve: "Wenezuela"
+ vi: "Wyspy Dziewicze (USA)"
+ za: "Afryka Południowa"
+ toolbar_button:
+ today: "Dzisiaj"
+ month: "Miesiąc"
+ week: "Tydzień"
+ day: "Dzień"
+ list: "Lista"
+ group_timezones:
+ search: "Wyszukiwanie..."
+ group_availability: "Dostępność %{group}"
+ discourse_post_event:
+ notifications:
+ before_event_reminder: "Wydarzenie wkrótce się rozpocznie"
+ after_event_reminder: "Wydarzenie zakończyło się"
+ ongoing_event_reminder: "Wydarzenie trwa"
+ invite_user_notification: "%{username} %{description}"
+ invite_user_predefined_attendance_notification_html: "%{username} automatycznie ustawił Twoją obecność i zaprosił Cię do %{description}"
+ before_event_reminder_html: "Wydarzenie wkrótce się rozpocznie %{description}"
+ after_event_reminder_html: "Wydarzenie zakończyło się %{description}"
+ ongoing_event_reminder_html: "Wydarzenie trwa %{description}"
+ edit_reason: "Wydarzenie zaktualizowane"
+ topic_title:
+ starts_at: "Wydarzenie rozpocznie się: %{date}"
+ ended_at: "Wydarzenie zakończyło się: %{date}"
+ ends_in_duration: "Kończy się %{duration}"
+ show_all: "Pokaż wszystkie"
+ participants:
+ one: "%{count} użytkownik wziął udział"
+ few: "%{count} użytkowników wzięło udział."
+ many: "%{count} użytkowników wzięło udział."
+ other: "%{count} użytkowników wzięło udział."
+ invite: "Powiadom użytkownika"
+ add_to_calendar: "Dodaj do kalendarza"
+ send_pm_to_creator: "Wyślij PW do %{username}"
+ leave: "Opuść wydarzenie"
+ edit_event: "Edytuj wydarzenie"
+ export_event: "Eksportuj wydarzenie"
+ created_by: "Utworzony przez"
+ bulk_invite: "Zaproszenie zbiorcze"
+ close_event: "Zamknij wydarzenie"
+ invitees_modal:
+ title_participated: "Lista użytkowników, którzy wzięli udział"
+ filter_placeholder: "Filtruj użytkowników"
+ bulk_invite_modal:
+ confirm: "potwierdź"
+ text: "Prześlij plik CSV"
+ title: "Zaproszenie zbiorcze"
+ success: "Plik został przesłany pomyślnie: otrzymasz prywatną wiadomość, gdy proces zostanie zakończony."
+ error: "Przykro nam, ale wymagany format pliku to CSV."
+ confirmation_message: "Zamierzasz powiadomić wszystkich w przesłanym pliku."
+ description_public: "W przypadku wydarzeń publicznych akceptowane są tylko nazwy użytkowników w przypadku zaproszeń zbiorczych."
+ description_private: "W przypadku wydarzeń prywatnych akceptowane są tylko nazwy grup w przypadku zaproszeń zbiorczych."
+ download_sample_csv: "Pobierz przykładowy plik CSV"
+ send_bulk_invites: "Wyślij zaproszenia"
+ group_selector_placeholder: "Wybierz grupę..."
+ user_selector_placeholder: "Wybierz użytkownika..."
+ csv_title: "Zaproszenie zbiorcze CSV"
+ upcoming_events:
+ title: "Nadchodzące wydarzenia"
+ creator: "Twórca"
+ status: "Status"
+ starts_at: "Zaczyna się o"
+ upcoming_events_list:
+ title: "Nadchodzące wydarzenia"
+ empty: "Brak nadchodzących wydarzeń"
+ all_day: "Cały dzień"
+ error: "Nie udało się pobrać wydarzeń"
+ try_again: "Spróbuj ponownie"
+ view_all: "Zobacz wszystkie"
+ category:
+ sort_topics_by_event_start_date: "Sortuj tematy według daty rozpoczęcia wydarzenia."
+ settings_sections:
+ event_sorting: "Sortowanie wydarzeń"
+ preview:
+ more_than_one_event: "Nie możesz mieć więcej niż jednego wydarzenia."
+ models:
+ invitee:
+ status:
+ unknown: "Nie zainteresowany"
+ going: "Wezmę udział"
+ not_going: "Nie biorę udziału"
+ interested: "Zainteresowany"
+ event:
+ expired: "Wygasły"
+ closed: "Zamknięte"
+ status:
+ standalone:
+ title: "Samodzielne"
+ description: "Nie można dołączyć do samodzielnego wydarzenia."
+ public:
+ title: "Publiczne"
+ description: "Do wydarzenia publicznego może dołączyć każdy."
+ private:
+ title: "Prywatne"
+ description: "Do wydarzenia prywatnego mogą dołączyć wyłącznie zaproszeni użytkownicy."
+ builder_modal:
+ custom_fields:
+ label: "Pola niestandardowe"
+ placeholder: "Opcjonalnie"
+ description: "Dozwolone pola niestandardowe są zdefiniowane w ustawieniach witryny. Pola niestandardowe służą do przesyłania danych do innych wtyczek."
+ create_event_title: "Utwórz wydarzenie"
+ update_event_title: "Edytuj wydarzenie"
+ confirm_delete: "Czy na pewno chcesz usunąć to wydarzenie?"
+ confirm_close: "Czy na pewno chcesz zamknąć to wydarzenie?"
+ create: "Utwórz"
+ update: "Zapisz"
+ attach: "Utwórz wydarzenie"
+ add_reminder: "Dodaj przypomnienie"
+ timezone:
+ label: Strefa czasowa
+ remove_timezone: Brak strefy czasowej (UTC)
+ reminders:
+ label: "Przypomnienia"
+ types:
+ bump_topic: "automatycznie podbij temat"
+ notification: "powiadom uczestników"
+ units:
+ minutes: "minut"
+ hours: "godzin"
+ days: "dni"
+ weeks: "tygodnie"
+ periods:
+ before: "przed"
+ after: "po"
+ recurrence:
+ label: "Powtarzanie"
+ none: "Brak powtarzania"
+ every_day: "Codziennie"
+ every_month: "Co miesiąc w ten dzień tygodnia"
+ every_weekday: "Każdego dnia roboczego"
+ every_week: "Co tydzień w ten dzień tygodnia"
+ every_two_weeks: "Co dwa tygodnie w ten dzień tygodnia"
+ every_four_weeks: "Co cztery tygodnie w ten dzień tygodnia"
+ minimal:
+ label: "Minimalne wydarzenie"
+ checkbox_label: "Ukryj przyciski Wezmę udział/Nie biorę udziału oraz status zaproszeń"
+ url:
+ label: "URL"
+ placeholder: "Opcjonalnie"
+ location:
+ label: "Lokalizacja"
+ description:
+ label: "Opis"
+ name:
+ label: "Nazwa wydarzenia"
+ placeholder: "Opcjonalne, domyślnie jest to tytuł tematu"
+ invitees:
+ label: "Zaproszone grupy"
+ status:
+ label: "Status"
+ invite_user_or_group:
+ title: "Powiadom użytkowników lub grupy"
+ invite: "Wyślij"
diff --git a/plugins/discourse-calendar/config/locales/client.pt.yml b/plugins/discourse-calendar/config/locales/client.pt.yml
new file mode 100644
index 00000000000..933ddab56e2
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/client.pt.yml
@@ -0,0 +1,80 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+pt:
+ js:
+ discourse_automation:
+ triggerables:
+ event_started:
+ title: Evento iniciado
+ fields:
+ topic_id:
+ label: ID do Tópico
+ discourse_calendar:
+ disable_holiday: "Desativar"
+ enable_holiday: "Ativar"
+ date: "Data"
+ region:
+ none: "Nenhuma"
+ toolbar_button:
+ today: "Hoje"
+ month: "Mês"
+ week: "Semana"
+ day: "Dia"
+ group_timezones:
+ search: "Pesquisar..."
+ discourse_post_event:
+ notifications:
+ invite_user_notification: "%{username} %{description}"
+ show_all: "Mostrar tudo"
+ add_to_calendar: "Adicionar ao calendário"
+ bulk_invite: "Convite em massa"
+ bulk_invite_modal:
+ confirm: "confirmar"
+ title: "Convite em massa"
+ success: "Ficheiro enviado corretamente, será notificado via mensagem quando o processo estiver concluído."
+ error: "Desculpe, o ficheiro deverá estar no formato CSV."
+ upcoming_events:
+ status: "Estado"
+ models:
+ event:
+ expired: "Expirado"
+ closed: "Fechado"
+ status:
+ public:
+ title: "Público"
+ private:
+ title: "Privado"
+ builder_modal:
+ custom_fields:
+ placeholder: "Opcional"
+ create: "Criar"
+ update: "Guardar"
+ timezone:
+ label: Zona Horária
+ reminders:
+ units:
+ minutes: "minutos"
+ hours: "horas"
+ days: "dias"
+ periods:
+ before: "antes"
+ after: "depois"
+ recurrence:
+ label: "Recorrência"
+ none: "Nenhuma recorrência"
+ every_day: "Diariamente"
+ url:
+ label: "URL"
+ placeholder: "Opcional"
+ location:
+ label: "Localização"
+ description:
+ label: "Descrição"
+ status:
+ label: "Estado"
+ invite_user_or_group:
+ invite: "Enviar"
diff --git a/plugins/discourse-calendar/config/locales/client.pt_BR.yml b/plugins/discourse-calendar/config/locales/client.pt_BR.yml
new file mode 100644
index 00000000000..9761dd367c4
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/client.pt_BR.yml
@@ -0,0 +1,482 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+pt_BR:
+ admin_js:
+ admin:
+ calendar: "Calendário"
+ site_settings:
+ categories:
+ discourse_post_event: "Evento do Discourse"
+ discourse_calendar: "Calendário do Discourse"
+ js:
+ notifications:
+ titles:
+ event_reminder: "lembrete de evento"
+ event_invitation: "convite do evento"
+ popup:
+ event_reminder: Lembrete de evento
+ discourse_automation:
+ triggerables:
+ event_started:
+ title: Evento iniciado
+ fields:
+ topic_id:
+ label: ID do tópico
+ discourse_calendar:
+ invite_user_notification: "%{username} convidou para participar de %{description}"
+ on_holiday: "No feriado"
+ disable_holiday: "Desativar"
+ enable_holiday: "Ativar"
+ holiday: "Feriado"
+ holidays:
+ header_title: "Feriados"
+ pick_region_description: "Escolha uma região para ver os feriados dessa região."
+ disabled_holidays_description: "Feriados desativados serão excluídos do calendário de férias da equipe."
+ date: "Data"
+ add_to_calendar: "Adicionar ao Google Calendar"
+ toggle_timezone_offset_title: "Alternar deslocamento de fuso horário"
+ region:
+ title: "Região"
+ none: "Nenhum"
+ use_current_region: "Usar região atual"
+ names:
+ ae: "Emirados Árabes Unidos"
+ ar: "Argentina"
+ at: "Áustria"
+ au_act: "Austrália (au_act)"
+ au_nsw: "Austrália (au_nsw)"
+ au_nt: "Austrália (au_nt)"
+ au_qld_brisbane: "Austrália (au_qld_brisbane)"
+ au_qld_cairns: "Austrália (au_qld_cairns)"
+ au_qld: "Austrália (au_qld)"
+ au_sa: "Austrália (au_sa)"
+ au_tas_north: "Austrália (au_tas_north)"
+ au_tas_south: "Austrália (au_tas_south)"
+ au_tas: "Austrália (au_tas)"
+ au_vic_melbourne: "Austrália (au_vic_melbourne)"
+ au_vic: "Austrália (au_vic)"
+ au_wa: "Austrália (au_wa)"
+ au: "Austrália"
+ be_fr: "Bélgica (be_fr)"
+ be_nl: "Bélgica (be_nl)"
+ bg_bg: "Bulgária (bg_bg)"
+ bg_en: "Bulgária (bg_en)"
+ br: "Brasil"
+ br_sp: "Brasil (br_sp)"
+ br_spcapital: "Brasil (br_spcapital)"
+ ca_ab: "Canadá (ca_ab)"
+ ca_bc: "Canadá (ca_bc)"
+ ca_mb: "Canadá (ca_mb)"
+ ca_nb: "Canadá (ca_nb)"
+ ca_nl: "Canadá (ca_nl)"
+ ca_ns: "Canadá (ca_ns)"
+ ca_nt: "Canadá (ca_nt)"
+ ca_nu: "Canadá (ca_nu)"
+ ca_on: "Canadá (ca_on)"
+ ca_pe: "Canadá (ca_pe)"
+ ca_qc: "Canadá (ca_qc)"
+ ca_sk: "Canadá (ca_sk)"
+ ca_yt: "Canadá (ca_yt)"
+ ca: "Canadá"
+ ch_ag: "Suíça (ch_ag)"
+ ch_ai: "Suíça (ch_ai)"
+ ch_ar: "Suíça (ch_ar)"
+ ch_be: "Suíça (ch_be)"
+ ch_bl: "Suíça (ch_bl)"
+ ch_bs: "Suíça (ch_bs)"
+ ch_fr: "Suíça (ch_fr)"
+ ch_ge: "Suíça (ch_ge)"
+ ch_gl: "Suíça (ch_gl)"
+ ch_gr: "Suíça (ch_gr)"
+ ch_ju: "Suíça (ch_ju)"
+ ch_lu: "Suíça (ch_lu)"
+ ch_ne: "Suíça (ch_ne)"
+ ch_nw: "Suíça (ch_nw)"
+ ch_ow: "Suíça (ch_ow)"
+ ch_sg: "Suíça (ch_sg)"
+ ch_sh: "Suíça (ch_sh)"
+ ch_so: "Suíça (ch_so)"
+ ch_sz: "Suíça (ch_sz)"
+ ch_tg: "Suíça (ch_tg)"
+ ch_ti: "Suíça (ch_ti)"
+ ch_ur: "Suíça (ch_ur)"
+ ch_vd: "Suíça (ch_vd)"
+ ch_vs: "Suíça (ch_vs)"
+ ch_zg: "Suíça (ch_zg)"
+ ch_zh: "Suíça (ch_zh)"
+ ch: "Suíça"
+ cl: "Chile"
+ co: "Colômbia"
+ cr: "Costa Rica"
+ cz: "República Tcheca"
+ de_bb: "Alemanha (de_bb)"
+ de_be: "Alemanha (de_be)"
+ de_bw: "Alemanha (de_bw)"
+ de_by_augsburg: "Alemanha (de_by_augsburg)"
+ de_by_cath: "Alemanha (de_by_cath)"
+ de_by: "Alemanha (de_by)"
+ de_hb: "Alemanha (de_hb)"
+ de_he: "Alemanha (de_he)"
+ de_hh: "Alemanha (de_hh)"
+ de_mv: "Alemanha (de_mv)"
+ de_ni: "Alemanha (de_ni)"
+ de_nw: "Alemanha (de_nw)"
+ de_rp: "Alemanha (de_rp)"
+ de_sh: "Alemanha (de_sh)"
+ de_sl: "Alemanha (de_sl)"
+ de_sn_sorbian: "Alemanha (de_sn_sorbian)"
+ de_sn: "Alemanha (de_sn)"
+ de_st: "Alemanha (de_st)"
+ de_th_cath: "Alemanha (de_th_cath)"
+ de_th: "Alemanha (de_th)"
+ de: "Alemanha"
+ dk: "Dinamarca"
+ ee: "Estônia"
+ el: "Grécia"
+ es_an: "Espanha (es_an)"
+ es_ar: "Espanha (es_ar)"
+ es_ce: "Espanha (es_ce)"
+ es_cl: "Espanha (es_cl)"
+ es_cm: "Espanha (es_cm)"
+ es_cn: "Espanha (es_cn)"
+ es_ct: "Espanha (es_ct)"
+ es_ex: "Espanha (es_ex)"
+ es_ga: "Espanha (es_ga)"
+ es_ib: "Espanha (es_ib)"
+ es_lo: "Espanha (es_lo)"
+ es_m: "Espanha (es_m)"
+ es_mu: "Espanha (es_mu)"
+ es_na: "Espanha (es_na)"
+ es_o: "Espanha (es_o)"
+ es_pv: "Espanha (es_pv)"
+ es_v: "Espanha (es_v)"
+ es_vc: "Espanha (es_vc)"
+ es: "Espanha"
+ fi: "Finlândia"
+ fr_a: "França (fr_a)"
+ fr_m: "França (fr_m)"
+ fr: "França"
+ gb_con: "Reino Unido (gb_con)"
+ gb_eaw: "Reino Unido (gb_eaw)"
+ gb_eng: "Reino Unido (gb_eng)"
+ gb_gsy: "Reino Unido (gb_gsy)"
+ gb_iom: "Reino Unido (gb_iom)"
+ gb_jsy: "Reino Unido (gb_jsy)"
+ gb_nir: "Reino Unido (gb_nir)"
+ gb_sct: "Reino Unido (gb_sct)"
+ gb_wls: "Reino Unido (gb_wls)"
+ gb: "Reino Unido"
+ ge: "Geórgia"
+ gg: "Guernsey"
+ gh: "Gana"
+ hk: "Hong Kong"
+ hr: "Croácia"
+ hu: "Hungria"
+ id: "Indonésia"
+ ie: "Irlanda"
+ im: "Ilha de Man"
+ in: "Índia"
+ in_gj: "Índia (in_gj)"
+ in_mh: "Índia (in_mh)"
+ in_rj: "Índia (in_rj)"
+ in_tn: "Índia (in_tn)"
+ in_ka: "Índia (in_ka)"
+ is: "Islândia"
+ it_bl: "Itália (it_bl)"
+ it_fi: "Itália (it_fi)"
+ it_ge: "Itália (it_ge)"
+ it_pd: "Itália (it_pd)"
+ it_rm: "Itália (it_rm)"
+ it_ro: "Itália (it_ro)"
+ it_to: "Itália (it_to)"
+ it_tv: "Itália (it_tv)"
+ it_ve: "Itália (it_ve)"
+ it_vi: "Itália (it_vi)"
+ it_vr: "Itália (it_vr)"
+ it: "Itália"
+ je: "Jersey"
+ jp: "Japão"
+ ke: "Quênia"
+ kr: "Coreia do Norte"
+ kz: "Cazaquistão (República do)"
+ li: "Liechtenstein"
+ lt: "Lituânia"
+ lu: "Luxemburgo"
+ lv: "Letônia"
+ ma: "Marrocos"
+ mt_en: "Malta (mt_en)"
+ mt_mt: "Malta (mt_mt)"
+ mx_pue: "México (mx_pue)"
+ mx: "México"
+ my: "Malásia"
+ ng: "Nigéria"
+ nl: "Países Baixos"
+ "no": "Noruega"
+ nz_ak: "Nova Zelândia (nz_ak)"
+ nz_ca: "Nova Zelândia (nz_ca)"
+ nz_ch: "Nova Zelândia (nz_ch)"
+ nz_hb: "Nova Zelândia (nz_hb)"
+ nz_mb: "Nova Zelândia (nz_mb)"
+ nz_ne: "Nova Zelândia (nz_ne)"
+ nz_nl: "Nova Zelândia (nz_nl)"
+ nz_ot: "Nova Zelândia (nz_ot)"
+ nz_sc: "Nova Zelândia (nz_sc)"
+ nz_sl: "Nova Zelândia (nz_sl)"
+ nz_ta: "Nova Zelândia (nz_ta)"
+ nz_we: "Nova Zelândia (nz_we)"
+ nz_wl: "Nova Zelândia (nz_wl)"
+ nz: "Nova Zelândia"
+ pe: "Peru"
+ ph: "Filipinas"
+ pl: "Polônia"
+ pt_li: "Portugal (pt_li)"
+ pt_po: "Portugal (pt_po)"
+ pt: "Portugal"
+ ro: "Romênia"
+ rs_cyrl: "Sérvia (rs_cyrl)"
+ rs_la: "Sérvia (rs_la)"
+ ru: "Federação Russa"
+ se: "Suécia"
+ sa: "Arábia Saudita"
+ sg: "Cingapura"
+ si: "Eslovênia"
+ sk: "Eslováquia"
+ th: "Tailândia"
+ tn: "Tunísia"
+ tr: "Turquia"
+ ua: "Ucrânia"
+ us_ak: "Estados Unidos (us_ak)"
+ us_al: "Estados Unidos (us_al)"
+ us_ar: "Estados Unidos (us_ar)"
+ us_az: "Estados Unidos (us_az)"
+ us_ca: "Estados Unidos (us_ca)"
+ us_co: "Estados Unidos (us_co)"
+ us_ct: "Estados Unidos (us_ct)"
+ us_dc: "Estados Unidos (us_dc)"
+ us_de: "Estados Unidos (us_de)"
+ us_fl: "Estados Unidos (us_fl)"
+ us_ga: "Estados Unidos (us_ga)"
+ us_gu: "Estados Unidos (us_gu)"
+ us_hi: "Estados Unidos (us_hi)"
+ us_ia: "Estados Unidos (us_ia)"
+ us_id: "Estados Unidos (us_id)"
+ us_il: "Estados Unidos (us_il)"
+ us_in: "Estados Unidos (us_in)"
+ us_ks: "Estados Unidos (us_ks)"
+ us_ky: "Estados Unidos (us_ky)"
+ us_la: "Estados Unidos (us_la)"
+ us_ma: "Estados Unidos (us_ma)"
+ us_md: "Estados Unidos (us_md)"
+ us_me: "Estados Unidos (us_me)"
+ us_mi: "Estados Unidos (us_mi)"
+ us_mn: "Estados Unidos (us_mn)"
+ us_mo: "Estados Unidos (us_mo)"
+ us_ms: "Estados Unidos (us_ms)"
+ us_mt: "Estados Unidos (us_mt)"
+ us_nc: "Estados Unidos (us_nc)"
+ us_nd: "Estados Unidos (us_nd)"
+ us_ne: "Estados Unidos (us_ne)"
+ us_nh: "Estados Unidos (us_nh)"
+ us_nj: "Estados Unidos (us_nj)"
+ us_nm: "Estados Unidos (us_nm)"
+ us_nv: "Estados Unidos (us_nv)"
+ us_ny: "Estados Unidos (us_ny)"
+ us_oh: "Estados Unidos (us_oh)"
+ us_ok: "Estados Unidos (us_ok)"
+ us_or: "Estados Unidos (us_or)"
+ us_pa: "Estados Unidos (us_pa)"
+ us_pr: "Estados Unidos (us_pr)"
+ us_ri: "Estados Unidos (us_ri)"
+ us_sc: "Estados Unidos (us_sc)"
+ us_sd: "Estados Unidos (us_sd)"
+ us_tn: "Estados Unidos (us_tn)"
+ us_tx: "Estados Unidos (us_tx)"
+ us_ut: "Estados Unidos (us_ut)"
+ us_va: "Estados Unidos (us_va)"
+ us_vi: "Estados Unidos (us_vi)"
+ us_vt: "Estados Unidos (us_vt)"
+ us_wa: "Estados Unidos (us_wa)"
+ us_wi: "Estados Unidos (us_wi)"
+ us_wv: "Estados Unidos (us_wv)"
+ us_wy: "Estados Unidos (us_wy)"
+ us: "Estados Unidos"
+ ve: "Venezuela"
+ vi: "Ilhas Virgens (E.U.A)"
+ za: "África do Sul"
+ toolbar_button:
+ today: "Hoje"
+ month: "Mês"
+ week: "Semana"
+ day: "Dia"
+ list: "Lista"
+ group_timezones:
+ search: "Pesquisar..."
+ group_availability: "disponibilidade de %{group}"
+ discourse_post_event:
+ notifications:
+ before_event_reminder: "Um evento está prestes a começar"
+ after_event_reminder: "Um evento foi encerrado"
+ ongoing_event_reminder: "Um evento está em andamento"
+ invite_user_notification: "%{username} %{description}"
+ invite_user_predefined_attendance_notification_html: "%{username} definiu automaticamente sua presença e te convidou para %{description}"
+ before_event_reminder_html: "Um evento está prestes a começar %{description}"
+ after_event_reminder_html: "Um evento terminou %{description}"
+ ongoing_event_reminder_html: "Um evento está em andamento %{description}"
+ edit_reason: "Evento atualizado"
+ edit_reason_closed: "Evento fechado"
+ edit_reason_opened: "Evento aberto"
+ topic_title:
+ starts_at: "O evento terá início: %{date}"
+ ended_at: "O evento terminou: %{date}"
+ ends_in_duration: "Termina em %{duration}"
+ show_all: "Exibir tudo"
+ show_participants: "Mostrar participantes"
+ participants:
+ one: "%{count} usuário(a) participou."
+ other: "%{count} usuários(as) participaram."
+ invite: "Notificar usuário(a)"
+ add_to_calendar: "Adicionar ao calendário"
+ send_pm_to_creator: "Enviar MP para %{username}"
+ leave: "Sair do evento"
+ edit_event: "Editar evento"
+ export_event: "Exportar evento"
+ created_by: "Criador(a):"
+ bulk_invite: "Convite em massa"
+ close_event: "Encerrar evento"
+ open_event: "Evento aberto"
+ invitees_modal:
+ title_invited: "Participação no evento"
+ title_participated: "Lista de usuários(as) que participaram"
+ filter_placeholder: "Filtrar usuários(as)"
+ remove_invitee: "Remover convidado(a) da lista"
+ add_invitee: "Adicionar convidado(a) à lista"
+ bulk_invite_modal:
+ confirm: "confirmar"
+ text: "Carregar arquivo CSV"
+ title: "Convite em massa"
+ success: "Arquivo enviado com sucesso, você será notificado(a) por mensagem quando o processo estiver completo."
+ error: "Desculpe, o arquivo deve estar no formato CSV."
+ confirmation_message: "Você está prestes a enviar convites por e-mail para todos no arquivo enviado."
+ description_public: "Eventos públicos só aceitam nomes de usuários(as) para convites em massa."
+ description_private: "Eventos públicos só aceitam nomes de usuários(as) para convites em massa."
+ download_sample_csv: "Faça o download de um arquivo CSV de exemplo"
+ send_bulk_invites: "Enviar convites"
+ group_selector_placeholder: "Fechar grupo..."
+ user_selector_placeholder: "Escolha o(a) usuário(a)..."
+ inline_title: "Convite em massa embutido"
+ csv_title: "Convite em massa CSV"
+ upcoming_events:
+ title: "Próximos eventos"
+ creator: "Criador"
+ status: "Status"
+ starts_at: "Começa"
+ upcoming_events_list:
+ title: "Próximos eventos"
+ empty: "Nenhum evento próximo"
+ all_day: "O dia todo"
+ error: "Falha ao recuperar eventos"
+ try_again: "Tentar novamente"
+ view_all: "Visualizar tudo"
+ category:
+ sort_topics_by_event_start_date: "Classifique os tópicos por data de início do evento."
+ disable_topic_resorting: "Desativar reclassificação de tópico."
+ settings_sections:
+ event_sorting: "Classificação de eventos"
+ preview:
+ more_than_one_event: "Você não pode ter mais de um evento."
+ models:
+ invitee:
+ no_users: "Nenhum usuário(a) encontrado(a)"
+ status:
+ unknown: "Não tenho interesse"
+ going: "Eu vou"
+ not_going: "Não vou"
+ interested: "Interessado"
+ going_count:
+ one: "%{count} vai"
+ other: "%{count} vão"
+ not_going_count:
+ one: "%{count} não vai"
+ other: "%{count} não vão"
+ interested_count:
+ one: "%{count} tem interesse"
+ other: "%{count} têm interesse"
+ invited_count:
+ one: "%{count} usuário(a) convidado(a)"
+ other: "%{count} usuários(as) convidados(as)"
+ event:
+ expired: "Expirou"
+ closed: "Fechados"
+ status:
+ standalone:
+ title: "Independente"
+ description: "Não é possível entrar em um evento independente."
+ public:
+ title: "Público"
+ description: "Qualquer pessoa pode participar de um evento público."
+ private:
+ title: "Privado"
+ description: "Um evento privado só pode ter a participação de usuários(as) convidados(as)."
+ builder_modal:
+ custom_fields:
+ label: "Campos personalizados"
+ placeholder: "Opcional"
+ description: "Os campos personalizados permitidos são definidos nas configurações do site. Os campos personalizados são usados para transmitir dados para outros plugins."
+ create_event_title: "Criar evento"
+ update_event_title: "Editar evento"
+ confirm_delete: "Você tem certeza de que quer excluir este evento?"
+ confirm_close: "Você tem certeza de que deseja fechar este evento?"
+ confirm_open: "Você tem certeza de que deseja abrir este evento?"
+ create: "Criar"
+ update: "Salvar"
+ attach: "Criar evento"
+ add_reminder: "Adicionar lembrete"
+ timezone:
+ label: Fuso horário
+ remove_timezone: Sem fuso horário (UTC)
+ reminders:
+ label: "Lembretes"
+ types:
+ bump_topic: "impulsionar tópico automaticamente"
+ notification: "notificar participantes"
+ units:
+ minutes: "minutos"
+ hours: "horas"
+ days: "dias"
+ weeks: "semanas"
+ periods:
+ before: "antes"
+ after: "depois"
+ recurrence:
+ label: "Recorrência"
+ none: "Sem recorrência"
+ every_day: "Todos os dias"
+ every_month: "Todo mês, neste dia da semana"
+ every_weekday: "Todos os dias da semana"
+ every_week: "Toda semana, neste dia da semana"
+ every_two_weeks: "A cada duas semanas, neste dia da semana"
+ every_four_weeks: "A cada quatro semanas, neste dia da semana"
+ minimal:
+ label: "evento mínimo"
+ checkbox_label: "Ocultar botões Vou/Não vou e status dos(as) convidados(as)"
+ url:
+ label: "URL"
+ placeholder: "Opcional"
+ location:
+ label: "Localização"
+ description:
+ label: "Descrição"
+ name:
+ label: "Nome do evento"
+ placeholder: "Opcional, o padrão é o título do tópico"
+ invitees:
+ label: "Grupos convidados"
+ status:
+ label: "Status"
+ invite_user_or_group:
+ title: "Notificar usuário(s) ou grupo(s)"
+ invite: "Enviar"
diff --git a/plugins/discourse-calendar/config/locales/client.ro.yml b/plugins/discourse-calendar/config/locales/client.ro.yml
new file mode 100644
index 00000000000..a6324a8f956
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/client.ro.yml
@@ -0,0 +1,100 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+ro:
+ js:
+ discourse_automation:
+ triggerables:
+ event_started:
+ fields:
+ topic_id:
+ label: ID discuție
+ discourse_calendar:
+ disable_holiday: "Dezactivează"
+ enable_holiday: "Activează"
+ date: "Dată"
+ add_to_calendar: "Adaugă în Google Calendar"
+ region:
+ none: "Nimeni"
+ toolbar_button:
+ today: "Astăzi"
+ month: "Lună"
+ week: "Săptămână"
+ day: "Zi"
+ group_timezones:
+ search: "Caută..."
+ discourse_post_event:
+ notifications:
+ invite_user_notification: "%{username} %{description}"
+ invite: "Notifică un utilizator"
+ add_to_calendar: "Adaugă în calendar"
+ send_pm_to_creator: "Trimite PM către %{username}"
+ export_event: "Exportă evenimentul"
+ created_by: "Creat de"
+ bulk_invite: "Invitație în masă"
+ bulk_invite_modal:
+ title: "Invitație în masă"
+ success: "Fișier încărcat cu succes, vei fi înștiințat printr-un mesaj când procesarea este completă."
+ error: "Scuze, fișierul trebuie să fie în format CSV."
+ confirmation_message: "Ești pe cale să anunți pe toată lumea din fișierul încărcat."
+ upcoming_events:
+ title: "Evenimente viitoare"
+ creator: "Creator"
+ status: "Stare"
+ starts_at: "Începe la"
+ upcoming_events_list:
+ title: "Evenimente viitoare"
+ empty: "Fără evenimente viitoare"
+ all_day: "Toată ziua"
+ error: "Eroare la preluarea evenimentelor"
+ try_again: "Încearcă din nou"
+ view_all: "Vezi tot"
+ models:
+ invitee:
+ status:
+ unknown: "Nu sunt interesat"
+ going: "Vin"
+ not_going: "Nu Vin"
+ interested: "Interesat"
+ event:
+ expired: "Expirate"
+ closed: "Închis"
+ status:
+ public:
+ title: "Public"
+ private:
+ title: "Privat"
+ builder_modal:
+ custom_fields:
+ placeholder: "Opțional"
+ create: "Creează"
+ update: "Salvare"
+ timezone:
+ label: Fus orar
+ reminders:
+ units:
+ minutes: "minute"
+ hours: "ore"
+ days: "zile"
+ periods:
+ before: "înainte"
+ after: "după"
+ recurrence:
+ label: "Recurență"
+ none: "Nicio recurență"
+ every_day: "În fiecare zi"
+ url:
+ label: "URL"
+ placeholder: "Opțional"
+ location:
+ label: "Locație"
+ description:
+ label: "Descriere"
+ status:
+ label: "Status"
+ invite_user_or_group:
+ title: "Notifică utilizatorul (utilizatorii) sau grupul (grupurile)"
+ invite: "Trimite"
diff --git a/plugins/discourse-calendar/config/locales/client.ru.yml b/plugins/discourse-calendar/config/locales/client.ru.yml
new file mode 100644
index 00000000000..3df0711138c
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/client.ru.yml
@@ -0,0 +1,492 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+ru:
+ admin_js:
+ admin:
+ calendar: "Календарь"
+ site_settings:
+ categories:
+ discourse_post_event: "Мероприятие Discourse"
+ discourse_calendar: "Календарь Discourse"
+ js:
+ notifications:
+ titles:
+ event_reminder: "напоминание о мероприятии"
+ event_invitation: "приглашение на мероприятие"
+ popup:
+ event_reminder: Напоминание о мероприятии
+ discourse_automation:
+ triggerables:
+ event_started:
+ title: Мероприятие началось
+ fields:
+ topic_id:
+ label: ID темы
+ discourse_calendar:
+ invite_user_notification: "Пользователь %{username} приглашает вас: %{description}"
+ on_holiday: "Выходные"
+ disable_holiday: "Отключить"
+ enable_holiday: "Включить"
+ holiday: "Праздник"
+ holidays:
+ header_title: "Праздники"
+ pick_region_description: "Выберите регион, чтобы увидеть праздники, которые там отмечают."
+ disabled_holidays_description: "При отключении праздников они будут исключены из рабочего календаря."
+ date: "Дата"
+ add_to_calendar: "Добавить в Календарь Google"
+ toggle_timezone_offset_title: "Переключить смещение часового пояса"
+ region:
+ title: "Регион"
+ none: "Не выбран"
+ use_current_region: "Использовать текущий регион"
+ names:
+ ae: "Объединенные Арабские Эмираты"
+ ar: "Аргентина"
+ at: "Австрия"
+ au_act: "Австралия (au_act)"
+ au_nsw: "Австралия (au_nsw)"
+ au_nt: "Австралия (au_nt)"
+ au_qld_brisbane: "Австралия (au_qld_brisbane)"
+ au_qld_cairns: "Австралия (au_qld_cairns)"
+ au_qld: "Австралия (au_qld)"
+ au_sa: "Австралия (au_sa)"
+ au_tas_north: "Австралия (au_tas_north)"
+ au_tas_south: "Австралия (au_tas_south)"
+ au_tas: "Австралия (au_tas)"
+ au_vic_melbourne: "Австралия (au_vic_melbourne)"
+ au_vic: "Австралия (au_vic)"
+ au_wa: "Австралия (au_wa)"
+ au: "Австралия"
+ be_fr: "Бельгия (be_fr)"
+ be_nl: "Бельгия (be_nl)"
+ bg_bg: "Болгария (bg_bg)"
+ bg_en: "Болгария (bg_en)"
+ br: "Бразилия"
+ br_sp: "Бразилия (br_sp)"
+ br_spcapital: "Бразилия (br_spcapital)"
+ ca_ab: "Канада (ca_ab)"
+ ca_bc: "Канада (ca_bc)"
+ ca_mb: "Канада (ca_mb)"
+ ca_nb: "Канада (ca_nb)"
+ ca_nl: "Канада (ca_nl)"
+ ca_ns: "Канада (ca_ns)"
+ ca_nt: "Канада (ca_nt)"
+ ca_nu: "Канада (ca_nu)"
+ ca_on: "Канада (ca_on)"
+ ca_pe: "Канада (ca_pe)"
+ ca_qc: "Канада (ca_qc)"
+ ca_sk: "Канада (ca_sk)"
+ ca_yt: "Канада (ca_yt)"
+ ca: "Канада"
+ ch_ag: "Швейцария (ch_ag)"
+ ch_ai: "Швейцария (ch_ai)"
+ ch_ar: "Швейцария (ch_ar)"
+ ch_be: "Швейцария (ch_be)"
+ ch_bl: "Швейцария (ch_bl)"
+ ch_bs: "Швейцария (ch_bs)"
+ ch_fr: "Швейцария (ch_fr)"
+ ch_ge: "Швейцария (ch_ge)"
+ ch_gl: "Швейцария (ch_gl)"
+ ch_gr: "Швейцария (ch_gr)"
+ ch_ju: "Швейцария (ch_ju)"
+ ch_lu: "Швейцария (ch_lu)"
+ ch_ne: "Швейцария (ch_ne)"
+ ch_nw: "Швейцария (ch_nw)"
+ ch_ow: "Швейцария (ch_ow)"
+ ch_sg: "Швейцария (ch_sg)"
+ ch_sh: "Швейцария (ch_sh)"
+ ch_so: "Швейцария (ch_so)"
+ ch_sz: "Швейцария (ch_sz)"
+ ch_tg: "Швейцария (ch_tg)"
+ ch_ti: "Швейцария (ch_ti)"
+ ch_ur: "Швейцария (ch_ur)"
+ ch_vd: "Швейцария (ch_vd)"
+ ch_vs: "Швейцария (ch_vs)"
+ ch_zg: "Швейцария (ch_zg)"
+ ch_zh: "Швейцария (ch_zh)"
+ ch: "Швейцария"
+ cl: "Чили"
+ co: "Колумбия"
+ cr: "Коста-Рика"
+ cz: "Чешская Республика"
+ de_bb: "Германия (de_bb)"
+ de_be: "Германия (de_be)"
+ de_bw: "Германия (de_bw)"
+ de_by_augsburg: "Германия (de_by_augsburg)"
+ de_by_cath: "Германия (de_by_cath)"
+ de_by: "Германия (de_by)"
+ de_hb: "Германия (de_hb)"
+ de_he: "Германия (de_he)"
+ de_hh: "Германия (de_hh)"
+ de_mv: "Германия (de_mv)"
+ de_ni: "Германия (de_ni)"
+ de_nw: "Германия (de_nw)"
+ de_rp: "Германия (de_rp)"
+ de_sh: "Германия (de_sh)"
+ de_sl: "Германия (de_sl)"
+ de_sn_sorbian: "Германия (de_sn_sorbian)"
+ de_sn: "Германия (de_sn)"
+ de_st: "Германия (de_st)"
+ de_th_cath: "Германия (de_th_cath)"
+ de_th: "Германия (de_th)"
+ de: "Германия"
+ dk: "Дания"
+ ee: "Эстония"
+ el: "Греция"
+ es_an: "Испания (es_an)"
+ es_ar: "Испания (es_ar)"
+ es_ce: "Испания (es_ce)"
+ es_cl: "Испания (es_cl)"
+ es_cm: "Испания (es_cm)"
+ es_cn: "Испания (es_cn)"
+ es_ct: "Испания (es_ct)"
+ es_ex: "Испания (es_ex)"
+ es_ga: "Испания (es_ga)"
+ es_ib: "Испания (es_ib)"
+ es_lo: "Испания (es_lo)"
+ es_m: "Испания (es_m)"
+ es_mu: "Испания (es_mu)"
+ es_na: "Испания (es_na)"
+ es_o: "Испания (es_o)"
+ es_pv: "Испания (es_pv)"
+ es_v: "Испания (es_v)"
+ es_vc: "Испания (es_vc)"
+ es: "Испания"
+ fi: "Финляндия"
+ fr_a: "Франция (fr_a)"
+ fr_m: "Франция (fr_m)"
+ fr: "Франция"
+ gb_con: "Великобритания (gb_con)"
+ gb_eaw: "Великобритания (gb_eaw)"
+ gb_eng: "Великобритания (gb_eng)"
+ gb_gsy: "Великобритания (gb_gsy)"
+ gb_iom: "Великобритания (gb_iom)"
+ gb_jsy: "Великобритания (gb_jsy)"
+ gb_nir: "Великобритания (gb_nir)"
+ gb_sct: "Великобритания (gb_sct)"
+ gb_wls: "Великобритания (gb_wls)"
+ gb: "Великобритания"
+ ge: "Грузия"
+ gg: "Гернси"
+ gh: "Гана"
+ hk: "Гонконг"
+ hr: "Хорватия"
+ hu: "Венгрия"
+ id: "Индонезия"
+ ie: "Ирландия"
+ im: "Остров Мэн"
+ in: "Индия"
+ in_gj: "Индия (in_gj)"
+ in_mh: "Индия (in_mh)"
+ in_rj: "Индия (in_rj)"
+ in_tn: "Индия (in_tn)"
+ in_ka: "Индия (in_ka)"
+ is: "Исландия"
+ it_bl: "Италия (it_bl)"
+ it_fi: "Италия (it_fi)"
+ it_ge: "Италия (it_ge)"
+ it_pd: "Италия (it_pd)"
+ it_rm: "Италия (it_rm)"
+ it_ro: "Италия (it_ro)"
+ it_to: "Италия (it_to)"
+ it_tv: "Италия (it_tv)"
+ it_ve: "Италия (it_ve)"
+ it_vi: "Италия (it_vi)"
+ it_vr: "Италия (it_vr)"
+ it: "Италия"
+ je: "Джерси"
+ jp: "Япония"
+ ke: "Кения"
+ kr: "Республика Корея"
+ kz: "Республика Казахстан"
+ li: "Лихтенштейн"
+ lt: "Литва"
+ lu: "Люксембург"
+ lv: "Латвия"
+ ma: "Марокко"
+ mt_en: "Мальта (mt_en)"
+ mt_mt: "Мальта (mt_mt)"
+ mx_pue: "Мексика (mx_pue)"
+ mx: "Мексика"
+ my: "Малайзия"
+ ng: "Нигерия"
+ nl: "Нидерланды"
+ "no": "Норвегия"
+ nz_ak: "Новая Зеландия (nz_ak)"
+ nz_ca: "Новая Зеландия (nz_ca)"
+ nz_ch: "Новая Зеландия (nz_ch)"
+ nz_hb: "Новая Зеландия (nz_hb)"
+ nz_mb: "Новая Зеландия (nz_mb)"
+ nz_ne: "Новая Зеландия (nz_ne)"
+ nz_nl: "Новая Зеландия (nz_nl)"
+ nz_ot: "Новая Зеландия (nz_ot)"
+ nz_sc: "Новая Зеландия (nz_sc)"
+ nz_sl: "Новая Зеландия (nz_sl)"
+ nz_ta: "Новая Зеландия (nz_ta)"
+ nz_we: "Новая Зеландия (nz_we)"
+ nz_wl: "Новая Зеландия (nz_wl)"
+ nz: "Новая Зеландия"
+ pe: "Перу"
+ ph: "Филиппины"
+ pl: "Польша"
+ pt_li: "Португалия (pt_li)"
+ pt_po: "Португалия (pt_po)"
+ pt: "Португалия"
+ ro: "Румыния"
+ rs_cyrl: "Сербия (rs_cyrl)"
+ rs_la: "Сербия (rs_la)"
+ ru: "Российская Федерация"
+ se: "Швеция"
+ sa: "Саудовская Аравия"
+ sg: "Сингапур"
+ si: "Словения"
+ sk: "Словакия"
+ th: "Таиланд"
+ tn: "Тунис"
+ tr: "Турция"
+ ua: "Украина"
+ us_ak: "США (us_ak)"
+ us_al: "США (us_al)"
+ us_ar: "США (us_ar)"
+ us_az: "США (us_az)"
+ us_ca: "США (us_ca)"
+ us_co: "США (us_co)"
+ us_ct: "США (us_ct)"
+ us_dc: "США (us_dc)"
+ us_de: "США (us_de)"
+ us_fl: "США (us_fl)"
+ us_ga: "США (us_ga)"
+ us_gu: "США (us_gu)"
+ us_hi: "США (us_hi)"
+ us_ia: "США (us_ia)"
+ us_id: "США (us_id)"
+ us_il: "США (us_il)"
+ us_in: "США (us_in)"
+ us_ks: "США (us_ks)"
+ us_ky: "США (us_ky)"
+ us_la: "США (us_la)"
+ us_ma: "США (us_ma)"
+ us_md: "США (us_md)"
+ us_me: "США (us_me)"
+ us_mi: "США (us_mi)"
+ us_mn: "США (us_mn)"
+ us_mo: "США (us_mo)"
+ us_ms: "США (us_ms)"
+ us_mt: "США (us_mt)"
+ us_nc: "США (us_nc)"
+ us_nd: "США (us_nd)"
+ us_ne: "США (us_ne)"
+ us_nh: "США (us_nh)"
+ us_nj: "США (us_nj)"
+ us_nm: "США (us_nm)"
+ us_nv: "США (us_nv)"
+ us_ny: "США (us_ny)"
+ us_oh: "США (us_oh)"
+ us_ok: "США (us_ok)"
+ us_or: "США (us_or)"
+ us_pa: "США (us_pa)"
+ us_pr: "США (us_pr)"
+ us_ri: "США (us_ri)"
+ us_sc: "США (us_sc)"
+ us_sd: "США (us_sd)"
+ us_tn: "США (us_tn)"
+ us_tx: "США (us_tx)"
+ us_ut: "США (us_ut)"
+ us_va: "США (us_va)"
+ us_vi: "США (us_vi)"
+ us_vt: "США (us_vt)"
+ us_wa: "США (us_wa)"
+ us_wi: "США (us_wi)"
+ us_wv: "США (us_wv)"
+ us_wy: "США (us_wy)"
+ us: "США"
+ ve: "Венесуэла"
+ vi: "Виргинские острова (США)"
+ za: "Южная Африка"
+ toolbar_button:
+ today: "За сегодня"
+ month: "За месяц"
+ week: "За неделю"
+ day: "Дата"
+ list: "Список"
+ group_timezones:
+ search: "Поиск..."
+ group_availability: "доступность %{group}"
+ discourse_post_event:
+ notifications:
+ before_event_reminder: "Мероприятие скоро начнется"
+ after_event_reminder: "Мероприятие закончилось"
+ ongoing_event_reminder: "Мероприятие идет"
+ invite_user_notification: "%{username} %{description}"
+ invite_user_predefined_attendance_notification_html: "%{username} автоматически определил(а) ваше присутствие и пригласил(а) вас на мероприятие «%{description}»"
+ before_event_reminder_html: "Мероприятие скоро начнется: %{description}"
+ after_event_reminder_html: "Мероприятие закончилось: %{description}"
+ ongoing_event_reminder_html: "Мероприятие идет: %{description}"
+ edit_reason: "Мероприятие обновлено"
+ edit_reason_closed: "Мероприятие закрыто"
+ edit_reason_opened: "Мероприятие открыто"
+ topic_title:
+ starts_at: "Мероприятие начнется %{date}"
+ ended_at: "Мероприятие завершится %{date}"
+ ends_in_duration: "Окончание %{duration}"
+ show_all: "Показать все"
+ show_participants: "Показать участников"
+ participants:
+ one: "Участвовал %{count} пользователь."
+ few: "Участвовало %{count} пользователя."
+ many: "Участвовало %{count} пользователей."
+ other: "Участвовало пользователей: %{count}."
+ invite: "Уведомлять пользователя"
+ add_to_calendar: "Добавить в календарь"
+ send_pm_to_creator: "Отправить ЛС пользователю %{username}"
+ leave: "Покинуть мероприятие"
+ edit_event: "Изменить мероприятие"
+ export_event: "Экспорт мероприятия"
+ created_by: "Создано"
+ bulk_invite: "Массовое приглашение"
+ close_event: "Закрыть мероприятие"
+ open_event: "Открыть мероприятие"
+ invitees_modal:
+ title_invited: "Участие в мероприятии"
+ title_participated: "Список участвовавших пользователей"
+ filter_placeholder: "Фильтр пользователей"
+ remove_invitee: "Удалить приглашенного из списка"
+ add_invitee: "Добавить приглашенного в список"
+ bulk_invite_modal:
+ confirm: "подтвердить"
+ text: "Загрузить CSV-файл"
+ title: "Массовое приглашение"
+ success: "Файл успешно загружен, вы получите сообщение, когда процесс будет завершён."
+ error: "Извините, но файл должен быть в формате CSV."
+ confirmation_message: "Вы собираетесь уведомить всех адресатов, указанных в загруженном файле."
+ description_public: "Публичные мероприятия принимают только имена пользователей для массовых приглашений."
+ description_private: "Закрытые мероприятия принимают только имена групп для массовых приглашений."
+ download_sample_csv: "Загрузить пример CSV-файла"
+ send_bulk_invites: "Отправить приглашения"
+ group_selector_placeholder: "Выберите группу..."
+ user_selector_placeholder: "Выберите пользователя..."
+ inline_title: "Массовое приглашение"
+ csv_title: "Массовое приглашение через CSV-файл"
+ upcoming_events:
+ title: "Предстоящие мероприятия"
+ creator: "Создатель"
+ status: "Статус"
+ starts_at: "Начинается в"
+ upcoming_events_list:
+ title: "Предстоящие мероприятия"
+ empty: "Нет предстоящих мероприятий"
+ all_day: "Весь день"
+ error: "Не удалось получить мероприятия"
+ try_again: "Попробуйте снова"
+ view_all: "Просмотреть все"
+ category:
+ sort_topics_by_event_start_date: "Сортировать темы по дате начала мероприятия."
+ disable_topic_resorting: "Отключить сортировку тем."
+ settings_sections:
+ event_sorting: "Сортировка мероприятий"
+ preview:
+ more_than_one_event: "У вас не может быть более одного мероприятия."
+ models:
+ invitee:
+ no_users: "Пользователи не найдены"
+ status:
+ unknown: "Не интересует"
+ going: "Участвую"
+ not_going: "Не участвую"
+ interested: "Заинтересован"
+ going_count:
+ one: "%{count} участвует"
+ few: "%{count} участвуют"
+ many: "%{count} участвуют"
+ other: "%{count} участвует"
+ not_going_count:
+ one: "%{count} не участвует"
+ few: "%{count} не участвуют"
+ many: "%{count} не участвуют"
+ other: "%{count} не участвует"
+ interested_count:
+ one: "%{count} интересуется"
+ few: "%{count} интересуются"
+ many: "%{count} интересуются"
+ other: "%{count} интересуется"
+ invited_count:
+ one: "%{count} пользователь приглашен"
+ few: "%{count} пользователя приглашены"
+ many: "%{count} пользователей приглашены"
+ other: "%{count} пользователя приглашены"
+ event:
+ expired: "Истекшие"
+ closed: "Закрытое"
+ status:
+ standalone:
+ title: "Автономное"
+ description: "К автономному мероприятию нельзя присоединиться."
+ public:
+ title: "Публичное"
+ description: "К публичному мероприятию может присоединиться любой желающий."
+ private:
+ title: "Закрытое"
+ description: "К закрытому мероприятию могут присоединиться только приглашенные пользователи."
+ builder_modal:
+ custom_fields:
+ label: "Настраиваемые поля"
+ placeholder: "Необязательно"
+ description: "Настраиваемые поля разрешены в настройках сайта. Поля используются для передачи данных в другие плагины."
+ create_event_title: "Создать мероприятие"
+ update_event_title: "Изменить мероприятие"
+ confirm_delete: "Действительно удалить это мероприятие ?"
+ confirm_close: "Действительно закрыть это мероприятие ?"
+ confirm_open: "Действительно открыть это мероприятие ?"
+ create: "Создать"
+ update: "Сохранить"
+ attach: "Создать событие"
+ add_reminder: "Добавить напоминание"
+ timezone:
+ label: Часовой пояс
+ remove_timezone: Без часового пояса (UTC)
+ reminders:
+ label: "Напоминания"
+ types:
+ bump_topic: "автоматическое поднятие темы"
+ notification: "уведомление участников"
+ units:
+ minutes: "мин"
+ hours: "ч"
+ days: "сут."
+ weeks: "нед."
+ periods:
+ before: "до"
+ after: "после"
+ recurrence:
+ label: "Повторение"
+ none: "Без повторения"
+ every_day: "Каждый день"
+ every_month: "Каждый месяц в этот будний день"
+ every_weekday: "Каждый будний день"
+ every_week: "Каждую неделю в этот будний день"
+ every_two_weeks: "Каждые две недели в этот будний день"
+ every_four_weeks: "Каждые четыре недели в этот будний день"
+ minimal:
+ label: "Минимальное событие"
+ checkbox_label: "Скрыть кнопки «Участвую», «Не участвую» и статус приглашенных"
+ url:
+ label: "URL"
+ placeholder: "Необязательно"
+ location:
+ label: "Расположение"
+ description:
+ label: "Описание"
+ name:
+ label: "Название события"
+ placeholder: "Необязательно, по умолчанию используется заголовок темы"
+ invitees:
+ label: "Приглашённые группы"
+ status:
+ label: "Статус"
+ invite_user_or_group:
+ title: "Уведомить пользователей или группы"
+ invite: "Отправить"
diff --git a/plugins/discourse-calendar/config/locales/client.sk.yml b/plugins/discourse-calendar/config/locales/client.sk.yml
new file mode 100644
index 00000000000..758bbbd2311
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/client.sk.yml
@@ -0,0 +1,81 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+sk:
+ js:
+ discourse_automation:
+ triggerables:
+ event_started:
+ fields:
+ topic_id:
+ label: ID témy
+ discourse_calendar:
+ disable_holiday: "Zakázať"
+ enable_holiday: "Povoliť"
+ date: "Dátum"
+ region:
+ none: "Žiadny"
+ toolbar_button:
+ today: "Dnes"
+ month: "Mesiac"
+ week: "Týždeň"
+ day: "Deň"
+ group_timezones:
+ search: "Hľadať"
+ discourse_post_event:
+ notifications:
+ invite_user_notification: "%{username} %{description}"
+ show_all: "Zobraziť všetko"
+ add_to_calendar: "Pridať do kalendára"
+ created_by: "Vytvoril"
+ bulk_invite: "Hromadné pozvanie"
+ bulk_invite_modal:
+ confirm: "potvrďte"
+ title: "Hromadné pozvanie"
+ success: "Súbor bol úspešne odoslaný. Keď sa nahrávanie dokončí, budete na to upozornený cez správu."
+ error: "Prepáčte, súbor musí byť v CSV formáte."
+ upcoming_events:
+ creator: "Tvorca"
+ status: "Stav"
+ models:
+ event:
+ expired: "Vypršala platnosť"
+ closed: "Zatvorené"
+ status:
+ public:
+ title: "Verejné"
+ private:
+ title: "Súkromné"
+ builder_modal:
+ custom_fields:
+ placeholder: "Nepovinné"
+ create: "Vytvoriť"
+ update: "Uložiť"
+ timezone:
+ label: Časové pásmo
+ reminders:
+ units:
+ minutes: "minút"
+ hours: "hodiny"
+ days: "dní"
+ periods:
+ before: "pred"
+ after: "po"
+ recurrence:
+ label: "Opakovanie"
+ none: "Žiadna opakovaná udalosť"
+ every_day: "Každý deň"
+ url:
+ label: "URL"
+ placeholder: "Nepovinné"
+ location:
+ label: "Poloha"
+ description:
+ label: "Popis"
+ status:
+ label: "Stav"
+ invite_user_or_group:
+ invite: "Odoslať"
diff --git a/plugins/discourse-calendar/config/locales/client.sl.yml b/plugins/discourse-calendar/config/locales/client.sl.yml
new file mode 100644
index 00000000000..9a244b832fc
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/client.sl.yml
@@ -0,0 +1,410 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+sl:
+ admin_js:
+ admin:
+ site_settings:
+ categories:
+ discourse_post_event: "Dogodek Discourse"
+ discourse_calendar: "Koledar Discourse"
+ js:
+ discourse_automation:
+ triggerables:
+ event_started:
+ fields:
+ topic_id:
+ label: ID teme
+ discourse_calendar:
+ invite_user_notification: "%{username} vas vabi na: %{description}"
+ on_holiday: "Na počitnicah"
+ disable_holiday: "Onemogoči"
+ enable_holiday: "Omogoči"
+ holiday: "Praznik"
+ date: "Datum"
+ add_to_calendar: "Dodaj v Googlov koledar"
+ region:
+ title: "Regija"
+ none: "Brez"
+ use_current_region: "Uporabi trenutno regijo"
+ names:
+ ar: "Argentina"
+ at: "Avstrija"
+ au_act: "Avstralija (au_act)"
+ au_nsw: "Avstralija (au_nsw)"
+ au_nt: "Avstralija (au_nt)"
+ au_qld_brisbane: "Avstralija (au_qld_brisbane)"
+ au_qld_cairns: "Avstralija (au_qld_cairns)"
+ au_qld: "Avstralija (au_qld)"
+ au_sa: "Avstralija (au_sa)"
+ au_tas_north: "Avstralija (au_tas_north)"
+ au_tas_south: "Avstralija (au_tas_south)"
+ au_tas: "Avstralija (au_tas)"
+ au_vic_melbourne: "Avstralija (au_vic_melbourne)"
+ au_vic: "Avstralija (au_vic)"
+ au_wa: "Avstralija (au_wa)"
+ au: "Avstralija"
+ be_fr: "Belgija (be_fr)"
+ be_nl: "Belgija (be_nl)"
+ bg_bg: "Bolgarija (bg_bg)"
+ bg_en: "Bolgarija (bg_en)"
+ br: "Brazilija"
+ ca_ab: "Kanada (ca_ab)"
+ ca_bc: "Kanada (ca_bc)"
+ ca_mb: "Kanada (ca_mb)"
+ ca_nb: "Kanada (ca_nb)"
+ ca_nl: "Kanada (ca_nl)"
+ ca_ns: "Kanada (ca_ns)"
+ ca_nt: "Kanada (ca_nt)"
+ ca_nu: "Kanada (ca_nu)"
+ ca_on: "Kanada (ca_on)"
+ ca_pe: "Kanada (ca_pe)"
+ ca_qc: "Kanada (ca_qc)"
+ ca_sk: "Kanada (ca_sk)"
+ ca_yt: "Kanada (ca_yt)"
+ ca: "Kanada"
+ ch_ag: "Švica (ch_ag)"
+ ch_ai: "Švica (ch_ai)"
+ ch_ar: "Švica (ch_ar)"
+ ch_be: "Švica (ch_be)"
+ ch_bl: "Švica (ch_bl)"
+ ch_bs: "Švica (ch_bs)"
+ ch_fr: "Švica (ch_fr)"
+ ch_ge: "Švica (ch_ge)"
+ ch_gl: "Švica (ch_gl)"
+ ch_gr: "Švica (ch_gr)"
+ ch_ju: "Švica (ch_ju)"
+ ch_lu: "Švica (ch_lu)"
+ ch_ne: "Švica (ch_ne)"
+ ch_nw: "Švica (ch_nw)"
+ ch_ow: "Švica (ch_ow)"
+ ch_sg: "Švica (ch_sg)"
+ ch_sh: "Švica (ch_sh)"
+ ch_so: "Švica (ch_so)"
+ ch_sz: "Švica (ch_sz)"
+ ch_tg: "Švica (ch_tg)"
+ ch_ti: "Švica (ch_ti)"
+ ch_ur: "Švica (ch_ur)"
+ ch_vd: "Švica (ch_vd)"
+ ch_vs: "Švica (ch_vs)"
+ ch_zg: "Švica (ch_zg)"
+ ch_zh: "Švica (ch_zh)"
+ ch: "Švica"
+ cl: "Čile"
+ co: "Kolumbija"
+ cr: "Kostarika"
+ cz: "Češka"
+ de_bb: "Nemčija (de_bb)"
+ de_be: "Nemčija (de_be)"
+ de_bw: "Nemčija (de_bw)"
+ de_by_augsburg: "Nemčija (de_by_augsburg)"
+ de_by_cath: "Nemčija (de_by_cath)"
+ de_by: "Nemčija (de_by)"
+ de_hb: "Nemčija (de_hb)"
+ de_he: "Nemčija (de_he)"
+ de_hh: "Nemčija (de_hh)"
+ de_mv: "Nemčija (de_mv)"
+ de_ni: "Nemčija (de_ni)"
+ de_nw: "Nemčija (de_nw)"
+ de_rp: "Nemčija (de_rp)"
+ de_sh: "Nemčija (de_sh)"
+ de_sl: "Nemčija (de_sl)"
+ de_sn_sorbian: "Nemčija (de_sn_sorbian)"
+ de_sn: "Nemčija (de_sn)"
+ de_st: "Nemčija (de_st)"
+ de_th_cath: "Nemčija (de_th_cath)"
+ de_th: "Nemčija (de_th)"
+ de: "Nemčija"
+ dk: "Danska"
+ ee: "Estonija"
+ el: "Grčija"
+ es_an: "Španija (es_an)"
+ es_ar: "Španija (es_ar)"
+ es_ce: "Španija (es_ce)"
+ es_cl: "Španija (es_cl)"
+ es_cm: "Španija (es_cm)"
+ es_cn: "Španija (es_cn)"
+ es_ct: "Španija (es_ct)"
+ es_ex: "Španija (es_ex)"
+ es_ga: "Španija (es_ga)"
+ es_ib: "Španija (es_ib)"
+ es_lo: "Španija (es_lo)"
+ es_m: "Španija (es_m)"
+ es_mu: "Španija (es_mu)"
+ es_na: "Španija (es_na)"
+ es_o: "Španija (es_o)"
+ es_pv: "Španija (es_pv)"
+ es_v: "Španija (es_v)"
+ es_vc: "Španija (es_vc)"
+ es: "Španija"
+ fi: "Finska"
+ fr_a: "Francija (fr_a)"
+ fr_m: "Francija (fr_m)"
+ fr: "Francija"
+ gb_con: "Združeno kraljestvo (gb_con)"
+ gb_eaw: "Združeno kraljestvo (gb_eaw)"
+ gb_eng: "Združeno kraljestvo (gb_eng)"
+ gb_gsy: "Združeno kraljestvo (gb_gsy)"
+ gb_iom: "Združeno kraljestvo (gb_iom)"
+ gb_jsy: "Združeno kraljestvo (gb_jsy)"
+ gb_nir: "Združeno kraljestvo (gb_nir)"
+ gb_sct: "Združeno kraljestvo (gb_sct)"
+ gb_wls: "Združeno kraljestvo (gb_wls)"
+ gb: "Združeno kraljestvo"
+ ge: "Gruzija"
+ gg: "Guernsey"
+ hk: "Hong Kong"
+ hr: "Hrvaška"
+ hu: "Madžarska"
+ ie: "Irska"
+ im: "Otok Man"
+ is: "Islandija"
+ it_bl: "Italija (it_bl)"
+ it_fi: "Italija (it_fi)"
+ it_ge: "Italija (it_ge)"
+ it_pd: "Italija (it_pd)"
+ it_rm: "Italija (it_rm)"
+ it_ro: "Italija (it_ro)"
+ it_to: "Italija (it_to)"
+ it_tv: "Italija (it_tv)"
+ it_ve: "Italija (it_ve)"
+ it_vi: "Italija (it_vi)"
+ it_vr: "Italija (it_vr)"
+ it: "Italija"
+ je: "Jersey"
+ jp: "Japonska"
+ kr: "Južna Koreja"
+ li: "Lihtenštajn"
+ lt: "Litva"
+ lu: "Luksemburg"
+ lv: "Latvija"
+ ma: "Maroko"
+ mt_en: "Malta (mt_en)"
+ mt_mt: "Malta (mt_mt)"
+ mx_pue: "Mehika (mx_pue)"
+ mx: "Mehika"
+ my: "Malezija"
+ ng: "Nigerija"
+ nl: "Nizozemska"
+ "no": "Norveška"
+ nz_ak: "Nova Zelandija (nz_ak)"
+ nz_ca: "Nova Zelandija (nz_ca)"
+ nz_ch: "Nova Zelandija (nz_ch)"
+ nz_hb: "Nova Zelandija (nz_hb)"
+ nz_mb: "Nova Zelandija (nz_mb)"
+ nz_ne: "Nova Zelandija (nz_ne)"
+ nz_nl: "Nova Zelandija (nz_nl)"
+ nz_ot: "Nova Zelandija (nz_ot)"
+ nz_sc: "Nova Zelandija (nz_sc)"
+ nz_sl: "Nova Zelandija (nz_sl)"
+ nz_ta: "Nova Zelandija (nz_ta)"
+ nz_we: "Nova Zelandija (nz_we)"
+ nz_wl: "Nova Zelandija (nz_wl)"
+ nz: "Nova Zelandija"
+ pe: "Peru"
+ ph: "Filipini"
+ pl: "Poljska"
+ pt_li: "Portugalska (pt_li)"
+ pt_po: "Portugalska (pt_po)"
+ pt: "Portugalska"
+ ro: "Romunija"
+ rs_cyrl: "Srbija (rs_cyrl)"
+ rs_la: "Srbija (rs_la)"
+ ru: "Rusija"
+ se: "Švedska"
+ sg: "Singapur"
+ si: "Slovenija"
+ sk: "Slovaška"
+ th: "Tajska"
+ tn: "Tunizija"
+ tr: "Turčija"
+ ua: "Ukrajina"
+ us_ak: "ZDA (us_ak)"
+ us_al: "ZDA (us_al)"
+ us_ar: "ZDA (us_ar)"
+ us_az: "ZDA (us_az)"
+ us_ca: "ZDA (us_ca)"
+ us_co: "ZDA (us_co)"
+ us_ct: "ZDA (us_ct)"
+ us_dc: "ZDA (us_dc)"
+ us_de: "ZDA (us_de)"
+ us_fl: "ZDA (us_fl)"
+ us_ga: "ZDA (us_ga)"
+ us_gu: "ZDA (us_gu)"
+ us_hi: "ZDA (us_hi)"
+ us_ia: "ZDA (us_ia)"
+ us_id: "ZDA (us_id)"
+ us_il: "ZDA (us_il)"
+ us_in: "ZDA (us_in)"
+ us_ks: "ZDA (us_ks)"
+ us_ky: "ZDA (us_ky)"
+ us_la: "ZDA (us_la)"
+ us_ma: "ZDA (us_ma)"
+ us_md: "ZDA (us_md)"
+ us_me: "ZDA (us_me)"
+ us_mi: "ZDA (us_mi)"
+ us_mn: "ZDA (us_mn)"
+ us_mo: "ZDA (us_mo)"
+ us_ms: "ZDA (us_ms)"
+ us_mt: "ZDA (us_mt)"
+ us_nc: "ZDA (us_nc)"
+ us_nd: "ZDA (us_nd)"
+ us_ne: "ZDA (us_ne)"
+ us_nh: "ZDA (us_nh)"
+ us_nj: "ZDA (us_nj)"
+ us_nm: "ZDA (us_nm)"
+ us_nv: "ZDA (us_nv)"
+ us_ny: "ZDA (us_ny)"
+ us_oh: "ZDA (us_oh)"
+ us_ok: "ZDA (us_ok)"
+ us_or: "ZDA (us_or)"
+ us_pa: "ZDA (us_pa)"
+ us_pr: "ZDA (us_pr)"
+ us_ri: "ZDA (us_ri)"
+ us_sc: "ZDA (us_sc)"
+ us_sd: "ZDA (us_sd)"
+ us_tn: "ZDA (us_tn)"
+ us_tx: "ZDA (us_tx)"
+ us_ut: "ZDA (us_ut)"
+ us_va: "ZDA (us_va)"
+ us_vi: "ZDA (us_vi)"
+ us_vt: "ZDA (us_vt)"
+ us_wa: "ZDA (us_wa)"
+ us_wi: "ZDA (us_wi)"
+ us_wv: "ZDA (us_wv)"
+ us_wy: "ZDA (us_wy)"
+ us: "ZDA"
+ ve: "Venezuela"
+ vi: "Deviški otoki (ZDA)"
+ za: "Južna Afrika"
+ toolbar_button:
+ today: "Danes"
+ month: "Mesec"
+ week: "Teden"
+ group_timezones:
+ search: "Išči..."
+ group_availability: "Razpoložljivost %{group}"
+ discourse_post_event:
+ notifications:
+ invite_user_notification: "%{username} %{description}"
+ invite_user_predefined_attendance_notification_html: "%{username} vas je povabil na %{description} in vam določil udeležbo"
+ before_event_reminder_html: "Dogodek se bo kmalu pričel %{description}"
+ after_event_reminder_html: "Dogodek je zaključen %{description}"
+ ongoing_event_reminder_html: "Dogodek je v teku %{description}"
+ edit_reason: "Dogodek posodobljen"
+ topic_title:
+ starts_at: "Dogodek se bo pričel: %{date}"
+ ended_at: "Dogodek se je zaključil: %{date}"
+ ends_in_duration: "Bo zaključen %{duration}"
+ show_all: "Pokaži vse"
+ participants:
+ one: "%{count} udeleženec."
+ two: "%{count} udeleženca."
+ few: "%{count} udeleženci."
+ other: "%{count} udeležencev."
+ invite: "Obvesti uporabnika"
+ add_to_calendar: "Dodaj v koledar"
+ send_pm_to_creator: "Pošlji ZS %{username}"
+ edit_event: "Uredi dogodek"
+ export_event: "Izvozi dogodek"
+ created_by: "Ustvaril"
+ bulk_invite: "Skupinsko vabilo"
+ close_event: "Zapri dogodek"
+ invitees_modal:
+ title_participated: "Seznam udeležencev"
+ filter_placeholder: "Filtriraj uporabnike"
+ bulk_invite_modal:
+ confirm: "potrdi"
+ text: "Uvozi iz CSV"
+ title: "Skupinsko vabilo"
+ success: "Datoteka je uspešno naložena. Obveščeni boste, ko bo postopek zaključen."
+ error: "Datoteka mora biti v CSV obliki."
+ confirmation_message: "Poslali boste obvestilo vsem osebam iz datoteke."
+ description_public: "Skupinska vabila na javne dogodke lahko vsebujejo samo imena uporabnikov."
+ description_private: "Skupinska vabila na zasebne dogodke lahko vsebujejo samo imena grup."
+ download_sample_csv: "Prenesi vzorčno CSV datoteko"
+ send_bulk_invites: "Pošlji vabila"
+ group_selector_placeholder: "Izberi grupo..."
+ user_selector_placeholder: "Izberi uporabnika..."
+ inline_title: "Ročno dodajanje vabil"
+ csv_title: "Dodajanje vabil iz CSV"
+ upcoming_events:
+ title: "Prihajajoči dogodki"
+ creator: "Organizator"
+ status: "Status"
+ starts_at: "Prične se ob"
+ upcoming_events_list:
+ title: "Prihajajoči dogodki"
+ preview:
+ more_than_one_event: "Dodaš lahko največ en dogodek."
+ models:
+ invitee:
+ status:
+ unknown: "Neodločen"
+ going: "Pridem"
+ not_going: "Ne pridem"
+ interested: "Mogoče pridem"
+ event:
+ expired: "Končan"
+ closed: "Zaprto"
+ status:
+ standalone:
+ title: "Samostojni"
+ description: "Na samostojni dogodek prijava ni možna."
+ public:
+ title: "Javni"
+ description: "Na javni dogodek se lahko prijavi kdorkoli."
+ private:
+ title: "Zasebni"
+ description: "Na zasebni dogodek se lahko prijavijo samo povabljenci."
+ builder_modal:
+ custom_fields:
+ label: "Polja po meri"
+ placeholder: "Neobvezno"
+ description: "Polja po meri so določena v sistemskih nastavitvah. Z njihovo pomočjo lahko izmenjujete podatke z drugimi vtičniki."
+ create_event_title: "Ustvari dogodek"
+ update_event_title: "Uredi dogodek"
+ confirm_delete: "Res želiš izbrisati ta dogodek?"
+ confirm_close: "Res želiš zapreti ta dogodek?"
+ create: "Ustvari"
+ update: "Shrani"
+ attach: "Ustvari dogodek"
+ add_reminder: "Dodaj opomnik"
+ timezone:
+ label: Časovni pas
+ reminders:
+ label: "Opomniki"
+ units:
+ minutes: "minut"
+ hours: "ur"
+ days: "dnevi"
+ periods:
+ before: "pred"
+ after: "po"
+ recurrence:
+ label: "Ponavljanje"
+ none: "Brez"
+ every_day: "Vsak dan"
+ every_month: "Mesečno na ta dan"
+ every_weekday: "Vsak delavnik"
+ every_week: "Tedensko na ta dan"
+ url:
+ label: "URL"
+ placeholder: "Neobvezno"
+ location:
+ label: "Lokacija"
+ description:
+ label: "Opis"
+ name:
+ label: "Ime dogodka"
+ placeholder: "Neobvezno, privzeto enako imenu dogodka"
+ invitees:
+ label: "Povabljene grupe"
+ status:
+ label: "Status"
+ invite_user_or_group:
+ title: "Obvesti uporabnike oz. grupe"
+ invite: "Pošlji"
diff --git a/plugins/discourse-calendar/config/locales/client.sq.yml b/plugins/discourse-calendar/config/locales/client.sq.yml
new file mode 100644
index 00000000000..09f29af7ecc
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/client.sq.yml
@@ -0,0 +1,39 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+sq:
+ js:
+ discourse_automation:
+ triggerables:
+ event_started:
+ fields:
+ topic_id:
+ label: ID e temës
+ discourse_calendar:
+ disable_holiday: "Çaktivizo"
+ enable_holiday: "Aktivizo"
+ region:
+ none: "Asnjë"
+ toolbar_button:
+ today: "Sot"
+ month: "Këtë muaj"
+ week: "Këtë javë"
+ day: "Dit"
+ discourse_post_event:
+ bulk_invite_modal:
+ success: "Skedari u ngarkua, do njoftoheni me mesazh kur procesi të mbarojë. "
+ error: "Na vjen keq, skedari duhet të jete i formatit CSV."
+ builder_modal:
+ update: "Ruaj"
+ reminders:
+ units:
+ days: "ditë"
+ url:
+ label: "URL"
+ location:
+ label: "Vendndodhja"
+ description:
+ label: "Përshkrimi"
diff --git a/plugins/discourse-calendar/config/locales/client.sr.yml b/plugins/discourse-calendar/config/locales/client.sr.yml
new file mode 100644
index 00000000000..197a54ff0c4
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/client.sr.yml
@@ -0,0 +1,37 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+sr:
+ js:
+ discourse_automation:
+ triggerables:
+ event_started:
+ fields:
+ topic_id:
+ label: ID Teme
+ discourse_calendar:
+ disable_holiday: "Onemogući"
+ enable_holiday: "Omogući"
+ region:
+ none: "Ništa"
+ toolbar_button:
+ today: "Danas"
+ month: "Mesec"
+ week: "Nedelja"
+ discourse_post_event:
+ builder_modal:
+ update: "Sačuvaj"
+ reminders:
+ units:
+ days: "dana"
+ periods:
+ before: "pre"
+ url:
+ label: "URL"
+ location:
+ label: "Lokacija"
+ description:
+ label: "Opis"
diff --git a/plugins/discourse-calendar/config/locales/client.sv.yml b/plugins/discourse-calendar/config/locales/client.sv.yml
new file mode 100644
index 00000000000..e99c8883414
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/client.sv.yml
@@ -0,0 +1,428 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+sv:
+ admin_js:
+ admin:
+ calendar: "Kalender"
+ site_settings:
+ categories:
+ discourse_post_event: "Discourse-evenemang"
+ discourse_calendar: "Discourse-kalender"
+ js:
+ notifications:
+ titles:
+ event_reminder: "händelsepåminnelse"
+ popup:
+ event_reminder: Händelsepåminnelse
+ discourse_automation:
+ triggerables:
+ event_started:
+ title: Händelsen startad
+ fields:
+ topic_id:
+ label: Ämnes-ID
+ discourse_calendar:
+ invite_user_notification: "%{username} har bjudit in dig till: %{description}"
+ on_holiday: "På semester"
+ disable_holiday: "Inaktivera"
+ enable_holiday: "Aktivera"
+ holiday: "Semester"
+ holidays:
+ header_title: "Helgdagar"
+ pick_region_description: "Välj en region för att se helgdagarna för den regionen."
+ disabled_holidays_description: "Inaktiverade helgdagar kommer att exkluderas från personalens helgdagskalender."
+ date: "Datum"
+ add_to_calendar: "Lägg till i Google Kalender"
+ region:
+ title: "Region"
+ none: "Ingen"
+ use_current_region: "Använd nuvarande region"
+ names:
+ ar: "Argentina"
+ at: "Österrike"
+ au_act: "Australien (au_act)"
+ au_nsw: "Australien (au_nsw)"
+ au_nt: "Australien (au_nt)"
+ au_qld_brisbane: "Australien (au_qld_brisbane)"
+ au_qld_cairns: "Australien (au_qld_cairns)"
+ au_qld: "Australien (au_qld)"
+ au_sa: "Australien (au_sa)"
+ au_tas_north: "Australien (au_tas_north)"
+ au_tas_south: "Australien (au_tas_south)"
+ au_tas: "Australien (au_tas)"
+ au_vic_melbourne: "Australien (au_vic_melbourne)"
+ au_vic: "Australien (au_vic)"
+ au_wa: "Australien (au_wa)"
+ au: "Australien"
+ be_fr: "Belgien (be_fr)"
+ be_nl: "Belgien (be_nl)"
+ bg_bg: "Bulgarien (bg_bg)"
+ bg_en: "Bulgarien (bg_en)"
+ br: "Brasilien"
+ br_sp: "Brasilien (br_sp)"
+ br_spcapital: "Brasilien (br_spcapital)"
+ ca_ab: "Kanada (ca_ab)"
+ ca_bc: "Kanada (ca_bc)"
+ ca_mb: "Kanada (ca_mb)"
+ ca_nb: "Kanada (ca_nb)"
+ ca_nl: "Kanada (ca_nl)"
+ ca_ns: "Kanada (ca_ns)"
+ ca_nt: "Kanada (ca_nt)"
+ ca_nu: "Kanada (ca_nu)"
+ ca_on: "Kanada (ca_on)"
+ ca_pe: "Kanada (ca_pe)"
+ ca_qc: "Kanada (ca_qc)"
+ ca_sk: "Kanada (ca_sk)"
+ ca_yt: "Kanada (ca_yt)"
+ ca: "Kanada"
+ ch_ag: "Schweiz (ch_ag)"
+ ch_ai: "Schweiz (ch_ai)"
+ ch_ar: "Schweiz (ch_ar)"
+ ch_be: "Schweiz (ch_be)"
+ ch_bl: "Schweiz (ch_bl)"
+ ch_bs: "Schweiz (ch_bs)"
+ ch_fr: "Schweiz (ch_fr)"
+ ch_ge: "Schweiz (ch_ge)"
+ ch_gl: "Schweiz (ch_gl)"
+ ch_gr: "Schweiz (ch_gr)"
+ ch_ju: "Schweiz (ch_ju)"
+ ch_lu: "Schweiz (ch_lu)"
+ ch_ne: "Schweiz (ch_ne)"
+ ch_nw: "Schweiz (ch_nw)"
+ ch_ow: "Schweiz (ch_ow)"
+ ch_sg: "Schweiz (ch_sg)"
+ ch_sh: "Schweiz (ch_sh)"
+ ch_so: "Schweiz (ch_so)"
+ ch_sz: "Schweiz (ch_sz)"
+ ch_tg: "Schweiz (ch_tg)"
+ ch_ti: "Schweiz (ch_ti)"
+ ch_ur: "Schweiz (ch_ur)"
+ ch_vd: "Schweiz (ch_vd)"
+ ch_vs: "Schweiz (ch_vs)"
+ ch_zg: "Schweiz (ch_zg)"
+ ch_zh: "Schweiz (ch_zh)"
+ ch: "Schweiz"
+ cl: "Chile"
+ co: "Colombia"
+ cr: "Costa Rica"
+ cz: "Tjeckien"
+ de_bb: "Tyskland (de_bb)"
+ de_be: "Tyskland (de_be)"
+ de_bw: "Tyskland (de_bw)"
+ de_by_augsburg: "Tyskland (de_by_augsburg)"
+ de_by_cath: "Tyskland (de_by_cath)"
+ de_by: "Tyskland (de_by)"
+ de_hb: "Tyskland (de_hb)"
+ de_he: "Tyskland (de_he)"
+ de_hh: "Tyskland (de_hh)"
+ de_mv: "Tyskland (de_mv)"
+ de_ni: "Tyskland (de_ni)"
+ de_nw: "Tyskland (de_nw)"
+ de_rp: "Tyskland (de_rp)"
+ de_sh: "Tyskland (de_sh)"
+ de_sl: "Tyskland (de_sl)"
+ de_sn_sorbian: "Tyskland (de_sn_sorbian)"
+ de_sn: "Tyskland (de_sn)"
+ de_st: "Tyskland (de_st)"
+ de_th_cath: "Tyskland (de_th_cath)"
+ de_th: "Tyskland (de_th)"
+ de: "Tyskland"
+ dk: "Danmark"
+ ee: "Estland"
+ el: "Grekland"
+ es_an: "Spanien (es_an)"
+ es_ar: "Spanien (es_ar)"
+ es_ce: "Spanien (es_ce)"
+ es_cl: "Spanien (es_cl)"
+ es_cm: "Spanien (es_cm)"
+ es_cn: "Spanien (es_cn)"
+ es_ct: "Spanien (es_ct)"
+ es_ex: "Spanien (es_ex)"
+ es_ga: "Spanien (es_ga)"
+ es_ib: "Spanien (es_ib)"
+ es_lo: "Spanien (es_lo)"
+ es_m: "Spanien (es_m)"
+ es_mu: "Spanien (es_mu)"
+ es_na: "Spanien (es_na)"
+ es_o: "Spanien (es_o)"
+ es_pv: "Spanien (es_pv)"
+ es_v: "Spanien (es_v)"
+ es_vc: "Spanien (es_vc)"
+ es: "Spanien"
+ fi: "Finland"
+ fr_a: "Frankrike (fr_a)"
+ fr_m: "Frankrike (fr_m)"
+ fr: "Frankrike"
+ gb_con: "Storbritannien (gb_con)"
+ gb_eaw: "Storbritannien (gb_eaw)"
+ gb_eng: "Storbritannien (gb_eng)"
+ gb_gsy: "Storbritannien (gb_gsy)"
+ gb_iom: "Storbritannien (gb_iom)"
+ gb_jsy: "Storbritannien (gb_jsy)"
+ gb_nir: "Storbritannien (gb_nir)"
+ gb_sct: "Storbritannien (gb_sct)"
+ gb_wls: "Storbritannien (gb_wls)"
+ gb: "Storbritannien"
+ ge: "Georgien"
+ gg: "Guernsey"
+ gh: "Ghana"
+ hk: "Hongkong"
+ hr: "Kroatien"
+ hu: "Ungern"
+ ie: "Irland"
+ im: "Isle of Man"
+ in: "Indien"
+ is: "Island"
+ it_bl: "Italien (it_bl)"
+ it_fi: "Italien (it_fi)"
+ it_ge: "Italien (it_ge)"
+ it_pd: "Italien (it_pd)"
+ it_rm: "Italien (it_rm)"
+ it_ro: "Italien (it_ro)"
+ it_to: "Italien (it_to)"
+ it_tv: "Italien (it_tv)"
+ it_ve: "Italien (it_ve)"
+ it_vi: "Italien (it_vi)"
+ it_vr: "Italien (it_vr)"
+ it: "Italien"
+ je: "Jersey"
+ jp: "Japan"
+ kr: "Korea (Republiken)"
+ kz: "Kazakstan (Republiken Kazakstan)"
+ li: "Liechtenstein"
+ lt: "Litauen"
+ lu: "Luxemburg"
+ lv: "Lettland"
+ ma: "Marocko"
+ mt_en: "Malta (mt_en)"
+ mt_mt: "Malta (mt_mt)"
+ mx_pue: "Mexiko (mx_pue)"
+ mx: "Mexiko"
+ my: "Malaysia"
+ ng: "Nigeria"
+ nl: "Nederländerna"
+ "no": "Norge"
+ nz_ak: "Nya Zeeland (nz_ak)"
+ nz_ca: "Nya Zeeland (nz_ca)"
+ nz_ch: "Nya Zeeland (nz_ch)"
+ nz_hb: "Nya Zeeland (nz_hb)"
+ nz_mb: "Nya Zeeland (nz_mb)"
+ nz_ne: "Nya Zeeland (nz_ne)"
+ nz_nl: "Nya Zeeland (nz_nl)"
+ nz_ot: "Nya Zeeland (nz_ot)"
+ nz_sc: "Nya Zeeland (nz_sc)"
+ nz_sl: "Nya Zeeland (nz_sl)"
+ nz_ta: "Nya Zeeland (nz_ta)"
+ nz_we: "Nya Zeeland (nz_we)"
+ nz_wl: "Nya Zeeland (nz_wl)"
+ nz: "Nya Zeeland"
+ pe: "Peru"
+ ph: "Filippinerna"
+ pl: "Polen"
+ pt_li: "Portugal (pt_li)"
+ pt_po: "Portugal (pt_po)"
+ pt: "Portugal"
+ ro: "Rumänien"
+ rs_cyrl: "Serbien (rs_cyrl)"
+ rs_la: "Serbien (rs_la)"
+ ru: "Ryska federationen"
+ se: "Sverige"
+ sa: "Saudiarabien"
+ sg: "Singapore"
+ si: "Slovenien"
+ sk: "Slovakien"
+ th: "Thailand"
+ tn: "Tunisien"
+ tr: "Turkiet"
+ ua: "Ukraina"
+ us_ak: "USA (us_ak)"
+ us_al: "USA (us_al)"
+ us_ar: "USA (us_ar)"
+ us_az: "USA (us_az)"
+ us_ca: "USA (us_ca)"
+ us_co: "USA (us_co)"
+ us_ct: "USA (us_ct)"
+ us_dc: "USA (us_dc)"
+ us_de: "USA (us_de)"
+ us_fl: "USA (us_fl)"
+ us_ga: "USA (us_ga)"
+ us_gu: "USA (us_gu)"
+ us_hi: "USA (us_hi)"
+ us_ia: "USA (us_ia)"
+ us_id: "USA (us_id)"
+ us_il: "USA (us_il)"
+ us_in: "USA (us_in)"
+ us_ks: "USA (us_ks)"
+ us_ky: "USA (us_ky)"
+ us_la: "USA (us_la)"
+ us_ma: "USA (us_ma)"
+ us_md: "USA (us_md)"
+ us_me: "USA (us_me)"
+ us_mi: "USA (us_mi)"
+ us_mn: "USA (us_mn)"
+ us_mo: "USA (us_mo)"
+ us_ms: "USA (us_ms)"
+ us_mt: "USA (us_mt)"
+ us_nc: "USA (us_nc)"
+ us_nd: "USA (us_nd)"
+ us_ne: "USA (us_ne)"
+ us_nh: "USA (us_nh)"
+ us_nj: "USA (us_nj)"
+ us_nm: "USA (us_nm)"
+ us_nv: "USA (us_nv)"
+ us_ny: "USA (us_ny)"
+ us_oh: "USA (us_oh)"
+ us_ok: "USA (us_ok)"
+ us_or: "USA (us_or)"
+ us_pa: "USA (us_pa)"
+ us_pr: "USA (us_pr)"
+ us_ri: "USA (us_ri)"
+ us_sc: "USA (us_sc)"
+ us_sd: "USA (us_sd)"
+ us_tn: "USA (us_tn)"
+ us_tx: "USA (us_tx)"
+ us_ut: "USA (us_ut)"
+ us_va: "USA (us_va)"
+ us_vi: "USA (us_vi)"
+ us_vt: "USA (us_vt)"
+ us_wa: "USA (us_wa)"
+ us_wi: "USA (us_wi)"
+ us_wv: "USA (us_wv)"
+ us_wy: "USA (us_wy)"
+ us: "USA"
+ ve: "Venezuela"
+ vi: "Jungfruöarna (USA)"
+ za: "Sydafrika"
+ toolbar_button:
+ today: "Idag"
+ month: "Månad"
+ week: "Vecka"
+ day: "Dag"
+ group_timezones:
+ search: "Sök..."
+ group_availability: "%{group}-tillgänglighet"
+ discourse_post_event:
+ notifications:
+ invite_user_notification: "%{username} %{description}"
+ invite_user_predefined_attendance_notification_html: "%{username} har automatiskt ställt in din närvaro och bjudit in dig till %{description}"
+ before_event_reminder_html: "Ett evenemang börjar snart %{description}"
+ after_event_reminder_html: "Ett evenemang har avslutats %{description}"
+ ongoing_event_reminder_html: "Ett evenemang pågår %{description}"
+ edit_reason: "Evenemang uppdaterat"
+ topic_title:
+ starts_at: "Evenemanget börjar: %{date}"
+ ended_at: "Evenemanget slutade: %{date}"
+ ends_in_duration: "Slutar %{duration}"
+ show_all: "Visa alla"
+ participants:
+ one: "%{count} användare deltog."
+ other: "%{count} användare deltog."
+ invite: "Meddela användare"
+ add_to_calendar: "Lägg till i kalender"
+ send_pm_to_creator: "Skicka PM till %{username}"
+ edit_event: "Redigera evenemang"
+ export_event: "Exportera evenemang"
+ created_by: "Skapat av"
+ bulk_invite: "Massinbjudan"
+ close_event: "Stäng evenemang"
+ invitees_modal:
+ title_participated: "Lista över användare som deltog"
+ filter_placeholder: "Filtrera användare"
+ bulk_invite_modal:
+ confirm: "bekräfta"
+ text: "Ladda upp CSV-fil"
+ title: "Massinbjudan"
+ success: "Filen laddades upp och du underrättas via meddelande när processen är klar"
+ error: "Tyvärr bör filen vara i CSV-format."
+ confirmation_message: "Du är på väg att meddela alla i den uppladdade filen."
+ description_public: "För offentliga evenemang godkänns endast användarnamn för massinbjudningar."
+ description_private: "För privata evenemang godkänns endast gruppnamn för massinbjudningar."
+ download_sample_csv: "Ladda ner ett CSV-exempel"
+ send_bulk_invites: "Skicka inbjudningar"
+ group_selector_placeholder: "Välj en grupp ..."
+ user_selector_placeholder: "Välj användare ..."
+ inline_title: "Inbäddad massinbjudan"
+ csv_title: "CSV-massinbjudan"
+ upcoming_events:
+ title: "Kommande evenemang"
+ creator: "Skapare"
+ status: "Status"
+ starts_at: "Börjar"
+ upcoming_events_list:
+ title: "Kommande evenemang"
+ preview:
+ more_than_one_event: "Du kan inte ha mer än ett evenemang."
+ models:
+ invitee:
+ status:
+ unknown: "Inte intresserad"
+ going: "Kommer"
+ not_going: "Kommer inte"
+ interested: "Intresserad"
+ event:
+ expired: "Förfallen"
+ closed: "Stängd"
+ status:
+ standalone:
+ title: "Fristående"
+ description: "Ett fristående evenemang kan inte anslutas."
+ public:
+ title: "Offentligt"
+ description: "Ett offentligt evenemang kan besökas av vem som helst."
+ private:
+ title: "Privat"
+ description: "Endast inbjudna användare kan besöka ett privat evenemang."
+ builder_modal:
+ custom_fields:
+ label: "Anpassade fält"
+ placeholder: "Valfritt"
+ description: "Tillåtna anpassade fält definieras i platsinställningar. Anpassade fält används för att överföra data till andra plugins."
+ create_event_title: "Skapa evenemang"
+ update_event_title: "Redigera evenemang"
+ confirm_delete: "Är du säker på att du vill ta bort detta evenemang?"
+ confirm_close: "Är du säker på att du vill stänga detta evenemang?"
+ create: "Skapa"
+ update: "Spara"
+ attach: "Skapa händelse"
+ add_reminder: "Lägg till påminnelse"
+ timezone:
+ label: Tidszon
+ remove_timezone: Ingen tidszon (UTC)
+ reminders:
+ label: "Påminnelser"
+ units:
+ minutes: "minuter"
+ hours: "timmar"
+ days: "dagar"
+ periods:
+ before: "innan"
+ after: "efter"
+ recurrence:
+ label: "Upprepning"
+ none: "Ingen upprepning"
+ every_day: "Varje dag"
+ every_month: "Varje månad på denna veckodag"
+ every_weekday: "Varje veckodag"
+ every_week: "Varje vecka på denna veckodag"
+ every_two_weeks: "Varannan vecka på denna veckodag"
+ url:
+ label: "URL"
+ placeholder: "Valfritt"
+ location:
+ label: "Plats"
+ description:
+ label: "Beskrivning"
+ name:
+ label: "Evenemangsnamn"
+ placeholder: "Valfritt, som standard används ämnesrubriken"
+ invitees:
+ label: "Inbjudna grupper"
+ status:
+ label: "Status"
+ invite_user_or_group:
+ title: "Meddela användare eller grupp(er)"
+ invite: "Skicka"
diff --git a/plugins/discourse-calendar/config/locales/client.sw.yml b/plugins/discourse-calendar/config/locales/client.sw.yml
new file mode 100644
index 00000000000..056be954aea
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/client.sw.yml
@@ -0,0 +1,62 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+sw:
+ js:
+ discourse_automation:
+ triggerables:
+ event_started:
+ fields:
+ topic_id:
+ label: Utambulisho wa Mada
+ discourse_calendar:
+ disable_holiday: "Sitisha"
+ enable_holiday: "Wezesha"
+ date: "Tarehe"
+ region:
+ none: "Hakuna"
+ toolbar_button:
+ today: "Leo"
+ month: "Mwezi"
+ week: "Wiki"
+ day: "Siku"
+ group_timezones:
+ search: "Tafuta"
+ discourse_post_event:
+ bulk_invite_modal:
+ success: "Faili limepakiwa kwa mafanikio, utapewa taarifa kwa kupitia Meseji mchakato utakapo kamilika"
+ error: "Samahani, faili hili inabidi liwe na umbizo faili la CSV"
+ models:
+ event:
+ closed: "Imefungwa"
+ status:
+ public:
+ title: "Umma"
+ private:
+ title: "Binafsi"
+ builder_modal:
+ custom_fields:
+ placeholder: "Sio muhimu"
+ create: "Tengeneza"
+ update: "Hifadhi"
+ reminders:
+ units:
+ days: "siku"
+ periods:
+ before: "kabla"
+ after: "baada"
+ recurrence:
+ label: "Kurudiarudia"
+ none: "Hakuna kurudia"
+ url:
+ label: "Anwani ya mtandao"
+ placeholder: "Sio muhimu"
+ location:
+ label: "Sehemu"
+ description:
+ label: "Elezo"
+ invite_user_or_group:
+ invite: "Tuma"
diff --git a/plugins/discourse-calendar/config/locales/client.te.yml b/plugins/discourse-calendar/config/locales/client.te.yml
new file mode 100644
index 00000000000..a68267c6bc8
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/client.te.yml
@@ -0,0 +1,73 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+te:
+ js:
+ discourse_automation:
+ triggerables:
+ event_started:
+ fields:
+ topic_id:
+ label: విషయపు ఐడీ
+ discourse_calendar:
+ disable_holiday: "అచేతనం"
+ enable_holiday: "చేతనం"
+ date: "తేదీ"
+ region:
+ none: "ఏదీ లేదు"
+ toolbar_button:
+ today: "ఈరోజు"
+ month: "ఈ నెల"
+ week: "వారం"
+ day: "రోజు"
+ discourse_post_event:
+ notifications:
+ invite_user_notification: "%{username} %{description}"
+ show_all: "అన్నీ చూపండి"
+ add_to_calendar: "క్యాలెండర్కు జోడించండి"
+ created_by: "సృష్టికర్త"
+ bulk_invite: "చాలా మొత్తం ఆహ్వానాలు"
+ bulk_invite_modal:
+ confirm: "నిర్ధారించండి"
+ title: "చాలా మొత్తం ఆహ్వానాలు"
+ error: "క్షమించండి, ఫైల్ CSV ఆకృతిలో ఉండాలి."
+ upcoming_events:
+ status: "స్థితి"
+ models:
+ event:
+ expired: "గడువు ముగిసింది"
+ closed: "మూసివేయబడినవి"
+ status:
+ public:
+ title: "బహిరంగం"
+ private:
+ title: "అంతరంగికం"
+ builder_modal:
+ custom_fields:
+ placeholder: "ఐచ్ఛికం"
+ create: "సృష్టించండి"
+ update: "భద్రపరుచు"
+ timezone:
+ label: సమయమండలం
+ reminders:
+ units:
+ minutes: "నిమిషాలు"
+ hours: "గంటలు"
+ days: "రోజులు"
+ periods:
+ before: "ముందు"
+ after: "తర్వాత"
+ url:
+ label: "యూఆర్ యల్"
+ placeholder: "ఐచ్ఛికం"
+ location:
+ label: "ప్రాంతం"
+ description:
+ label: "వివరణ"
+ status:
+ label: "స్థితి"
+ invite_user_or_group:
+ invite: "పంపండి"
diff --git a/plugins/discourse-calendar/config/locales/client.th.yml b/plugins/discourse-calendar/config/locales/client.th.yml
new file mode 100644
index 00000000000..241a69a5163
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/client.th.yml
@@ -0,0 +1,70 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+th:
+ js:
+ discourse_automation:
+ triggerables:
+ event_started:
+ fields:
+ topic_id:
+ label: หมายเลขกระทู้
+ discourse_calendar:
+ disable_holiday: "ปิดใช้งาน"
+ enable_holiday: "เปิดใช้งาน"
+ date: "วันที่"
+ region:
+ none: "ไม่มี"
+ toolbar_button:
+ today: "วันนี้"
+ month: "เดือน"
+ week: "สัปดาห์"
+ group_timezones:
+ search: "ค้นหา..."
+ discourse_post_event:
+ notifications:
+ invite_user_notification: "%{username}%{description}"
+ created_by: "สร้างโดย"
+ bulk_invite_modal:
+ success: "ไฟล์ถูกอัปโหลดเรียบร้อยแล้ว คุณจะได้รับการแจ้งเตือนทางข้อความเมื่อขั้นตอนเสร็จสิ้น"
+ upcoming_events:
+ status: "สถานะ"
+ models:
+ event:
+ closed: "ปิด"
+ status:
+ public:
+ title: "สาธารณะ"
+ private:
+ title: "ส่วนตัว"
+ builder_modal:
+ custom_fields:
+ placeholder: "ทางเลือก"
+ create: "สร้าง"
+ update: "บันทึก"
+ timezone:
+ label: เขตเวลา
+ reminders:
+ units:
+ days: "วัน"
+ periods:
+ before: "ก่อน"
+ after: "หลังจาก"
+ recurrence:
+ label: "มีการเกิดขึ้นซ้ำ"
+ none: "ไม่มีการเกิดขึ้นซ้ำ"
+ every_day: "ทุกวัน"
+ url:
+ label: "URL"
+ placeholder: "ทางเลือก"
+ location:
+ label: "ที่อยู่"
+ description:
+ label: "รายละเอียด"
+ status:
+ label: "สถานะ"
+ invite_user_or_group:
+ invite: "ส่ง"
diff --git a/plugins/discourse-calendar/config/locales/client.tr_TR.yml b/plugins/discourse-calendar/config/locales/client.tr_TR.yml
new file mode 100644
index 00000000000..f42bab6c244
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/client.tr_TR.yml
@@ -0,0 +1,482 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+tr_TR:
+ admin_js:
+ admin:
+ calendar: "Takvim"
+ site_settings:
+ categories:
+ discourse_post_event: "Discourse Etkinliği"
+ discourse_calendar: "Discourse Takvimi"
+ js:
+ notifications:
+ titles:
+ event_reminder: "etkinlik hatırlatıcısı"
+ event_invitation: "etkinlik daveti"
+ popup:
+ event_reminder: Etkinlik hatırlatıcısı
+ discourse_automation:
+ triggerables:
+ event_started:
+ title: Etkinlik başladı
+ fields:
+ topic_id:
+ label: Konu kimliği
+ discourse_calendar:
+ invite_user_notification: "%{username} sizi şuraya davet etti: %{description}"
+ on_holiday: "Tatilde"
+ disable_holiday: "Devre dışı bırak"
+ enable_holiday: "Etkinleştir"
+ holiday: "Tatil"
+ holidays:
+ header_title: "Tatiller"
+ pick_region_description: "Bir bölge seçerek o bölgenin tatil günlerini görebilirsiniz."
+ disabled_holidays_description: "Devre dışı bırakılan tatiller personel tatil takviminin dışında tutulur."
+ date: "Tarih"
+ add_to_calendar: "Google Takvim'e Ekle"
+ toggle_timezone_offset_title: "Saat dilimi farkını değiştir"
+ region:
+ title: "Bölge"
+ none: "Hiçbiri"
+ use_current_region: "Geçerli Bölgeyi Kullan"
+ names:
+ ae: "Birleşik Arap Emirlikleri"
+ ar: "Arjantin"
+ at: "Avusturya"
+ au_act: "Avustralya (au_act)"
+ au_nsw: "Avustralya (au_nsw)"
+ au_nt: "Avustralya (au_nt)"
+ au_qld_brisbane: "Avustralya (au_qld_brisbane)"
+ au_qld_cairns: "Avustralya (au_qld_cairns)"
+ au_qld: "Avustralya (au_qld)"
+ au_sa: "Avustralya (au_sa)"
+ au_tas_north: "Avustralya (au_tas_north)"
+ au_tas_south: "Avustralya (au_tas_south)"
+ au_tas: "Avustralya (au_tas)"
+ au_vic_melbourne: "Avustralya (au_vic_melbourne)"
+ au_vic: "Avustralya (au_vic)"
+ au_wa: "Avustralya (au_wa)"
+ au: "Avustralya"
+ be_fr: "Belçika (be_fr)"
+ be_nl: "Belçika (be_nl)"
+ bg_bg: "Bulgaristan (bg_bg)"
+ bg_en: "Bulgaristan (bg_bg)"
+ br: "Brezilya"
+ br_sp: "Brezilya (br_sp)"
+ br_spcapital: "Brezilya (br_spcapital)"
+ ca_ab: "Kanada (ca_ab)"
+ ca_bc: "Kanada (ca_ab)"
+ ca_mb: "Kanada (ca_mb)"
+ ca_nb: "Kanada (ca_nb)"
+ ca_nl: "Kanada (ca_nl)"
+ ca_ns: "Kanada (ca_ns)"
+ ca_nt: "Kanada (ca_nt)"
+ ca_nu: "Kanada (ca_nu)"
+ ca_on: "Kanada (ca_on)"
+ ca_pe: "Kanada (ca_pe)"
+ ca_qc: "Kanada (ca_qc)"
+ ca_sk: "Kanada (ca_sk)"
+ ca_yt: "Kanada (ca_yt)"
+ ca: "Kanada"
+ ch_ag: "İsviçre (ch_ag)"
+ ch_ai: "İsviçre (ch_ai)"
+ ch_ar: "İsviçre (ch_ar)"
+ ch_be: "İsviçre (ch_be)"
+ ch_bl: "İsviçre (ch_bl)"
+ ch_bs: "İsviçre (ch_bs)"
+ ch_fr: "İsviçre (ch_fr)"
+ ch_ge: "İsviçre (ch_ge)"
+ ch_gl: "İsviçre (ch_gl)"
+ ch_gr: "İsviçre (ch_gr)"
+ ch_ju: "İsviçre (ch_ju)"
+ ch_lu: "İsviçre (ch_lu)"
+ ch_ne: "İsviçre (ch_ne)"
+ ch_nw: "İsviçre (ch_nw)"
+ ch_ow: "İsviçre (ch_ow)"
+ ch_sg: "İsviçre (ch_sg)"
+ ch_sh: "İsviçre (ch_sh)"
+ ch_so: "İsviçre (ch_so)"
+ ch_sz: "İsviçre (ch_sz)"
+ ch_tg: "İsviçre (ch_tg)"
+ ch_ti: "İsviçre (ch_ti)"
+ ch_ur: "İsviçre (ch_ur)"
+ ch_vd: "İsviçre (ch_vd)"
+ ch_vs: "İsviçre (ch_vs)"
+ ch_zg: "İsviçre (ch_zg)"
+ ch_zh: "İsviçre (ch_zh)"
+ ch: "İsviçre"
+ cl: "Şili"
+ co: "Kolombiya"
+ cr: "Kosta Rika"
+ cz: "Çek Cumhuriyeti"
+ de_bb: "Almanya (de_bb)"
+ de_be: "Almanya (de_be)"
+ de_bw: "Almanya (de_bw)"
+ de_by_augsburg: "Almanya (de_by_augsburg)"
+ de_by_cath: "Almanya (de_by_cath)"
+ de_by: "Almanya (de_by)"
+ de_hb: "Almanya (de_hb)"
+ de_he: "Almanya (de_he)"
+ de_hh: "Almanya (de_hh)"
+ de_mv: "Almanya (de_mv)"
+ de_ni: "Almanya (de_ni)"
+ de_nw: "Almanya (de_nw)"
+ de_rp: "Almanya (de_rp)"
+ de_sh: "Almanya (de_sh)"
+ de_sl: "Almanya (de_sl)"
+ de_sn_sorbian: "Almanya (de_sn_sorbian)"
+ de_sn: "Almanya (de_sn)"
+ de_st: "Almanya (de_st)"
+ de_th_cath: "Almanya (de_th_cath)"
+ de_th: "Almanya (de_th)"
+ de: "Almanya"
+ dk: "Danimarka"
+ ee: "Estonya"
+ el: "Yunanistan"
+ es_an: "İspanya (es_an)"
+ es_ar: "İspanya (es_ar)"
+ es_ce: "İspanya (es_ce)"
+ es_cl: "İspanya (es_cl)"
+ es_cm: "İspanya (es_cm)"
+ es_cn: "İspanya (es_cn)"
+ es_ct: "İspanya (es_ct)"
+ es_ex: "İspanya (es_ex)"
+ es_ga: "İspanya (es_ga)"
+ es_ib: "İspanya (es_ib)"
+ es_lo: "İspanya (es_lo)"
+ es_m: "İspanya (es_m)"
+ es_mu: "İspanya (es_mu)"
+ es_na: "İspanya (es_na)"
+ es_o: "İspanya (es_o)"
+ es_pv: "İspanya (es_pv)"
+ es_v: "İspanya (es_v)"
+ es_vc: "İspanya (es_vc)"
+ es: "ispanya"
+ fi: "Finlandiya"
+ fr_a: "Fransa (fr_a)"
+ fr_m: "Fransa (fr_m)"
+ fr: "Fransa"
+ gb_con: "Birleşik Krallık (gb_con)"
+ gb_eaw: "Birleşik Krallık (gb_eaw)"
+ gb_eng: "Birleşik Krallık (gb_eng)"
+ gb_gsy: "Birleşik Krallık (gb_gsy)"
+ gb_iom: "Birleşik Krallık (gb_iom)"
+ gb_jsy: "Birleşik Krallık (gb_jsy)"
+ gb_nir: "Birleşik Krallık (gb_nir)"
+ gb_sct: "Birleşik Krallık (gb_sct)"
+ gb_wls: "Birleşik Krallık (gb_wls)"
+ gb: "Birleşik Krallık"
+ ge: "Gürcistan"
+ gg: "Guernsey"
+ gh: "Gana"
+ hk: "Hong Kong"
+ hr: "Hırvatistan"
+ hu: "Macaristan"
+ id: "Endonezya"
+ ie: "İrlanda"
+ im: "Man Adası"
+ in: "Hindistan"
+ in_gj: "Hindistan (in_gj)"
+ in_mh: "Hindistan (in_mh)"
+ in_rj: "Hindistan (in_rj)"
+ in_tn: "Hindistan (in_tn)"
+ in_ka: "Hindistan (in_ka)"
+ is: "İzlanda"
+ it_bl: "İtalya (it_bl)"
+ it_fi: "İtalya (it_fi)"
+ it_ge: "İtalya (it_ge)"
+ it_pd: "İtalya (it_pd)"
+ it_rm: "İtalya (it_rm)"
+ it_ro: "İtalya (it_ro)"
+ it_to: "İtalya (it_to)"
+ it_tv: "İtalya (it_tv)"
+ it_ve: "İtalya (it_ve)"
+ it_vi: "İtalya (it_vi)"
+ it_vr: "İtalya (it_vr)"
+ it: "İtalya"
+ je: "Jersey"
+ jp: "Japonya"
+ ke: "Kenya"
+ kr: "Kore (Cumhuriyeti)"
+ kz: "Kazakistan (Cumhuriyeti)"
+ li: "Lihtenştayn"
+ lt: "Litvanya"
+ lu: "Lüksemburg"
+ lv: "Letonya"
+ ma: "Fas"
+ mt_en: "Malta (mt_en)"
+ mt_mt: "Malta (mt_mt)"
+ mx_pue: "Meksika (mx_pue)"
+ mx: "Meksika"
+ my: "Malezya"
+ ng: "Nijerya"
+ nl: "Hollanda"
+ "no": "Norveç"
+ nz_ak: "Yeni Zelanda (nz_ak)"
+ nz_ca: "Yeni Zelanda (nz_ca)"
+ nz_ch: "Yeni Zelanda (nz_ch)"
+ nz_hb: "Yeni Zelanda (nz_hb)"
+ nz_mb: "Yeni Zelanda (nz_mb)"
+ nz_ne: "Yeni Zelanda (nz_ne)"
+ nz_nl: "Yeni Zelanda (nz_nl)"
+ nz_ot: "Yeni Zelanda (nz_ot)"
+ nz_sc: "Yeni Zelanda (nz_sc)"
+ nz_sl: "Yeni Zelanda (nz_sl)"
+ nz_ta: "Yeni Zelanda (nz_ta)"
+ nz_we: "Yeni Zelanda (nz_we)"
+ nz_wl: "Yeni Zelanda (nz_wl)"
+ nz: "Yeni Zelanda"
+ pe: "Peru"
+ ph: "Filipinler"
+ pl: "Polonya"
+ pt_li: "Portekiz (pt_li)"
+ pt_po: "Portekiz (pt_po)"
+ pt: "Portekiz"
+ ro: "Romanya"
+ rs_cyrl: "Sırbistan (rs_cyrl)"
+ rs_la: "Sırbistan (rs_la)"
+ ru: "Rusya Federasyonu"
+ se: "İsveç"
+ sa: "Suudi Arabistan"
+ sg: "Singapur"
+ si: "Slovenya"
+ sk: "Slovakya"
+ th: "Tayland"
+ tn: "Tunus"
+ tr: "Türkiye"
+ ua: "Ukrayna"
+ us_ak: "Amerika Birleşik Devletleri (us_ak)"
+ us_al: "Amerika Birleşik Devletleri (us_al)"
+ us_ar: "Amerika Birleşik Devletleri (us_ar)"
+ us_az: "Amerika Birleşik Devletleri (us_az)"
+ us_ca: "Amerika Birleşik Devletleri (us_ca)"
+ us_co: "Amerika Birleşik Devletleri (us_co)"
+ us_ct: "Amerika Birleşik Devletleri (us_ct)"
+ us_dc: "Amerika Birleşik Devletleri (us_dc)"
+ us_de: "Amerika Birleşik Devletleri (us_de)"
+ us_fl: "Amerika Birleşik Devletleri (us_fl)"
+ us_ga: "Amerika Birleşik Devletleri (us_ga)"
+ us_gu: "Amerika Birleşik Devletleri (us_gu)"
+ us_hi: "Amerika Birleşik Devletleri (us_hi)"
+ us_ia: "Amerika Birleşik Devletleri (us_ia)"
+ us_id: "Amerika Birleşik Devletleri (us_id)"
+ us_il: "Amerika Birleşik Devletleri (us_il)"
+ us_in: "Amerika Birleşik Devletleri (us_in)"
+ us_ks: "Amerika Birleşik Devletleri (us_ks)"
+ us_ky: "Amerika Birleşik Devletleri (us_ky)"
+ us_la: "Amerika Birleşik Devletleri (us_la)"
+ us_ma: "Amerika Birleşik Devletleri (us_ma)"
+ us_md: "Amerika Birleşik Devletleri (us_md)"
+ us_me: "Amerika Birleşik Devletleri (us_me)"
+ us_mi: "Amerika Birleşik Devletleri (us_mi)"
+ us_mn: "Amerika Birleşik Devletleri (us_mn)"
+ us_mo: "Amerika Birleşik Devletleri (us_mo)"
+ us_ms: "Amerika Birleşik Devletleri (us_ms)"
+ us_mt: "Amerika Birleşik Devletleri (us_mt)"
+ us_nc: "Amerika Birleşik Devletleri (us_nc)"
+ us_nd: "Amerika Birleşik Devletleri (us_nd)"
+ us_ne: "Amerika Birleşik Devletleri (us_ne)"
+ us_nh: "Amerika Birleşik Devletleri (us_nh)"
+ us_nj: "Amerika Birleşik Devletleri (us_nj)"
+ us_nm: "Amerika Birleşik Devletleri (us_nm)"
+ us_nv: "Amerika Birleşik Devletleri (us_nv)"
+ us_ny: "Amerika Birleşik Devletleri (us_ny)"
+ us_oh: "Amerika Birleşik Devletleri (us_oh)"
+ us_ok: "Amerika Birleşik Devletleri (us_ok)"
+ us_or: "Amerika Birleşik Devletleri (us_or)"
+ us_pa: "Amerika Birleşik Devletleri (us_pa)"
+ us_pr: "Amerika Birleşik Devletleri (us_pr)"
+ us_ri: "Amerika Birleşik Devletleri (us_ri)"
+ us_sc: "Amerika Birleşik Devletleri (us_sc)"
+ us_sd: "Amerika Birleşik Devletleri (us_sd)"
+ us_tn: "Amerika Birleşik Devletleri (us_tn)"
+ us_tx: "Amerika Birleşik Devletleri (us_tx)"
+ us_ut: "Amerika Birleşik Devletleri (us_ut)"
+ us_va: "Amerika Birleşik Devletleri (us_va)"
+ us_vi: "Amerika Birleşik Devletleri (us_vi)"
+ us_vt: "Amerika Birleşik Devletleri (us_vt)"
+ us_wa: "Amerika Birleşik Devletleri (us_wa)"
+ us_wi: "Amerika Birleşik Devletleri (us_wi)"
+ us_wv: "Amerika Birleşik Devletleri (us_wv)"
+ us_wy: "Amerika Birleşik Devletleri (us_wy)"
+ us: "Amerika Birleşik Devletleri"
+ ve: "Venezuela"
+ vi: "Virgin Adaları (ABD)"
+ za: "Güney Afrika"
+ toolbar_button:
+ today: "Bugün"
+ month: "Ay"
+ week: "Hafta"
+ day: "Gün"
+ list: "Liste"
+ group_timezones:
+ search: "Ara..."
+ group_availability: "%{group} uygunluğu"
+ discourse_post_event:
+ notifications:
+ before_event_reminder: "Bir etkinlik başlamak üzere"
+ after_event_reminder: "Bir etkinlik sona erdi"
+ ongoing_event_reminder: "Bir etkinlik devam ediyor"
+ invite_user_notification: "%{username} %{description}"
+ invite_user_predefined_attendance_notification_html: "%{username}, katılımınızı otomatik olarak ayarladı ve sizi %{description} adlı etkinliğe davet etti"
+ before_event_reminder_html: "Bir etkinlik başlamak üzere %{description}"
+ after_event_reminder_html: "Bir etkinlik sona erdi %{description}"
+ ongoing_event_reminder_html: "Bir etkinlik devam ediyor %{description}"
+ edit_reason: "Etkinlik güncellendi"
+ edit_reason_closed: "Etkinlik kapatıldı"
+ edit_reason_opened: "Etkinlik açıldı"
+ topic_title:
+ starts_at: "Etkinlik başlayacak: %{date}"
+ ended_at: "Etkinlik sona erdi: %{date}"
+ ends_in_duration: "Sona erme zamanı: %{duration}"
+ show_all: "Tümünü göster"
+ show_participants: "Katılımcıları göster"
+ participants:
+ one: "%{count} kullanıcı katıldı."
+ other: "%{count} kullanıcı katıldı."
+ invite: "Kullanıcıya bildir"
+ add_to_calendar: "Takvime ekle"
+ send_pm_to_creator: "%{username} adlı kullanıcıya kişisel mesaj gönder"
+ leave: "Etkinlikten ayrıl"
+ edit_event: "Etkinliği düzenle"
+ export_event: "Etkinliği dışa aktar"
+ created_by: "Oluşturan:"
+ bulk_invite: "Toplu Davet"
+ close_event: "Etkinliği kapat"
+ open_event: "Etkinliği aç"
+ invitees_modal:
+ title_invited: "Etkinlik Katılımı"
+ title_participated: "Katılan kullanıcıların listesi"
+ filter_placeholder: "Kullanıcıları filtrele"
+ remove_invitee: "Davetliyi listeden kaldır"
+ add_invitee: "Davetliyi listeye ekle"
+ bulk_invite_modal:
+ confirm: "onayla"
+ text: "CSV dosyası yükle"
+ title: "Toplu Davet"
+ success: "Dosya başarıyla yüklendi, işlem tamamlandığında mesaj yoluyla bilgilendirileceksiniz."
+ error: "Üzgünüz, dosya CSV biçiminde olmalıdır."
+ confirmation_message: "Yüklenen dosyadaki herkesi bilgilendirmek üzeresiniz."
+ description_public: "Herkese açık etkinlikler yalnızca toplu davetler için kullanıcı adlarını kabul eder."
+ description_private: "Özel etkinlikler yalnızca toplu davetler için grup adlarını kabul eder."
+ download_sample_csv: "Örnek bir CSV dosyası indirin"
+ send_bulk_invites: "Davet gönderin"
+ group_selector_placeholder: "Grup seçin..."
+ user_selector_placeholder: "Kullanıcı seçin..."
+ inline_title: "Satır içi toplu davet"
+ csv_title: "CSV toplu daveti"
+ upcoming_events:
+ title: "Yaklaşan etkinlikler"
+ creator: "Yaratıcı"
+ status: "Durum"
+ starts_at: "Başlangıç:"
+ upcoming_events_list:
+ title: "Yaklaşan etkinlikler"
+ empty: "Yaklaşan etkinlik yok"
+ all_day: "Tüm gün"
+ error: "Etkinlikler alınamadı"
+ try_again: "Tekrar deneyin"
+ view_all: "Tümünü görüntüle"
+ category:
+ sort_topics_by_event_start_date: "Konuları etkinlik başlangıç tarihine göre sıralayın."
+ disable_topic_resorting: "Konu yeniden sıralamasını devre dışı bırakın."
+ settings_sections:
+ event_sorting: "Etkinlik Sıralama"
+ preview:
+ more_than_one_event: "Birden fazla etkinliğiniz olamaz."
+ models:
+ invitee:
+ no_users: "Kullanıcı bulunamadı"
+ status:
+ unknown: "İlgilenmiyorum"
+ going: "Gidiyorum"
+ not_going: "Gitmiyorum"
+ interested: "İlgileniyorum"
+ going_count:
+ one: "%{count} gidiyor"
+ other: "%{count} gidiyor"
+ not_going_count:
+ one: "%{count} gitmiyor"
+ other: "%{count} gitmiyor"
+ interested_count:
+ one: "%{count} ilgileniyor"
+ other: "%{count} ilgileniyor"
+ invited_count:
+ one: "%{count} kullanıcı davet edildi"
+ other: "%{count} kullanıcı davet edildi"
+ event:
+ expired: "Süresi doldu"
+ closed: "Kapalı"
+ status:
+ standalone:
+ title: "Bağımsız"
+ description: "Bağımsız bir etkinliğe katılınamaz."
+ public:
+ title: "Herkese Açık"
+ description: "Herkese açık bir etkinliğe herkes katılabilir."
+ private:
+ title: "Özel"
+ description: "Özel bir etkinliğe yalnızca davet edilen kullanıcılar katılabilir."
+ builder_modal:
+ custom_fields:
+ label: "Özel Alanlar"
+ placeholder: "İsteğe bağlı"
+ description: "İzin verilen özel alanlar site ayarlarında tanımlanır. Özel alanlar, verileri diğer eklentilere iletmek için kullanılır."
+ create_event_title: "Etkinlik oluşturun"
+ update_event_title: "Etkinliği düzenleyin"
+ confirm_delete: "Bu etkinliği silmek istediğinizden emin misiniz?"
+ confirm_close: "Bu etkinliği kapatmak istediğinizden emin misiniz?"
+ confirm_open: "Bu etkinliği açmak istediğinizden emin misiniz?"
+ create: "Oluştur"
+ update: "Kaydet"
+ attach: "Etkinlik oluştur"
+ add_reminder: "Hatırlatıcı ekle"
+ timezone:
+ label: Saat dilimi
+ remove_timezone: Saat dilimi yok (UTC)
+ reminders:
+ label: "Anımsatıcılar"
+ types:
+ bump_topic: "konuyu otomatik üste sıçrat"
+ notification: "katılımcıları bilgilendirin"
+ units:
+ minutes: "dakika"
+ hours: "saat"
+ days: "gün"
+ weeks: "hafta"
+ periods:
+ before: "önce"
+ after: "sonra"
+ recurrence:
+ label: "Yineleme"
+ none: "Yineleme yok"
+ every_day: "Her gün"
+ every_month: "Her ay bu hafta içi günü"
+ every_weekday: "Hafta içi her gün"
+ every_week: "Her hafta bu hafta içi günü"
+ every_two_weeks: "İki haftada bir bu hafta içi günü"
+ every_four_weeks: "Her dört haftada bir hafta içi bu gün"
+ minimal:
+ label: "Minimal etkinlik"
+ checkbox_label: "Giden/Gitmeyen düğmelerini ve davetlilerin durumunu gizle"
+ url:
+ label: "URL"
+ placeholder: "İsteğe bağlı"
+ location:
+ label: "Konum"
+ description:
+ label: "Açıklama"
+ name:
+ label: "Etkinlik adı"
+ placeholder: "İsteğe bağlı, varsayılan olarak konu başlığı"
+ invitees:
+ label: "Davet edilen gruplar"
+ status:
+ label: "Durum"
+ invite_user_or_group:
+ title: "Kullanıcıları veya grupları bilgilendir"
+ invite: "Gönder"
diff --git a/plugins/discourse-calendar/config/locales/client.ug.yml b/plugins/discourse-calendar/config/locales/client.ug.yml
new file mode 100644
index 00000000000..12340f8252c
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/client.ug.yml
@@ -0,0 +1,78 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+ug:
+ js:
+ discourse_automation:
+ triggerables:
+ event_started:
+ fields:
+ topic_id:
+ label: تېما كىملىكى
+ discourse_calendar:
+ disable_holiday: "چەكلە"
+ enable_holiday: "قوزغات"
+ date: "چېسلا"
+ region:
+ none: "يوق"
+ toolbar_button:
+ today: "بۈگۈن"
+ month: "ئاي"
+ week: "ھەپتە"
+ day: "كۈن"
+ discourse_post_event:
+ notifications:
+ invite_user_notification: "%{username} %{description}"
+ show_all: "ھەممىنى كۆرسەت"
+ add_to_calendar: "يىلنامەگە قوش"
+ created_by: "قۇرغۇچى"
+ bulk_invite: "توپ تەكلىپ"
+ bulk_invite_modal:
+ confirm: "جەزملە"
+ title: "توپ تەكلىپ"
+ error: "كەچۈرۈڭ ، ھۆججەت CSV پىچىمىدا بولۇشى كېرەك."
+ upcoming_events:
+ creator: "قۇرغۇچى"
+ status: "ھالەت"
+ models:
+ event:
+ expired: "ۋاقتى توشتى"
+ closed: "تاقالدى"
+ status:
+ public:
+ title: "ئاممىۋى"
+ private:
+ title: "شەخسىي"
+ builder_modal:
+ custom_fields:
+ placeholder: "تاللاشچان"
+ create: "قۇر"
+ update: "ساقلا"
+ timezone:
+ label: ۋاقىت رايونى
+ reminders:
+ units:
+ minutes: "مىنۇت"
+ hours: "سائەت"
+ days: "كۈن"
+ periods:
+ before: "ئىلگىرى"
+ after: "كېيىن"
+ recurrence:
+ label: "تەكرار"
+ none: "تەكرارلانمايدۇ"
+ every_day: "ھەر كۈنى"
+ url:
+ label: "تور ئادرېسى"
+ placeholder: "تاللاشچان"
+ location:
+ label: "ئورنى"
+ description:
+ label: "چۈشەندۈرۈش"
+ status:
+ label: "ھالەت"
+ invite_user_or_group:
+ invite: "يوللا"
diff --git a/plugins/discourse-calendar/config/locales/client.uk.yml b/plugins/discourse-calendar/config/locales/client.uk.yml
new file mode 100644
index 00000000000..a404adf2b68
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/client.uk.yml
@@ -0,0 +1,505 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+uk:
+ admin_js:
+ admin:
+ calendar: "Календар"
+ site_settings:
+ categories:
+ discourse_post_event: "Подія Discourse"
+ discourse_calendar: "Календар Discourse"
+ js:
+ notifications:
+ titles:
+ event_reminder: "нагадування про подію"
+ event_invitation: "запрошення на подію"
+ popup:
+ event_reminder: Нагадування про подію
+ discourse_automation:
+ triggerables:
+ event_started:
+ title: Подія розпочалася
+ fields:
+ topic_id:
+ label: ID теми
+ discourse_calendar:
+ invite_user_notification: "%{username} запросив тебе: %{description}"
+ on_holiday: "У відпустці"
+ disable_holiday: "Відключити"
+ enable_holiday: "Включити"
+ holiday: "Свято"
+ holidays:
+ header_title: "Свята"
+ pick_region_description: "Оберіть регіон, щоб побачити свята для цього регіону."
+ disabled_holidays_description: "При вимкненні свят вони будуть виключені з календаря персоналу."
+ date: "Дата"
+ add_to_calendar: "Додати до календаря Google"
+ toggle_timezone_offset_title: "Змінити часовий пояс"
+ region:
+ title: "Регіон"
+ none: "Немає"
+ use_current_region: "Використовувати поточний регіон"
+ names:
+ ae: "Об'єднані Арабські Емірати"
+ ar: "Аргентина"
+ at: "Австрія"
+ au_act: "Австралія (au_act)"
+ au_nsw: "Австралія (au_nsw)"
+ au_nt: "Австралія (au_nt)"
+ au_qld_brisbane: "Австралія (au_qld_brisbane)"
+ au_qld_cairns: "Австралія (au_qld_cairns)"
+ au_qld: "Австралія (au_qld)"
+ au_sa: "Австралія (au_sa)"
+ au_tas_north: "Австралія (au_tas_north)"
+ au_tas_south: "Австралія (au_tas_south)"
+ au_tas: "Австралія (au_tas)"
+ au_vic_melbourne: "Австралія (au_vic_melbourne)"
+ au_vic: "Австралія (au_vic)"
+ au_wa: "Австралія (au_wa)"
+ au: "Австралія"
+ be_fr: "Бельгія (be_fr)"
+ be_nl: "Бельгія (be_nl)"
+ bg_bg: "Болгарія (bg_bg)"
+ bg_en: "Болгарія (bg_en)"
+ br: "Бразилія"
+ br_sp: "Бразилія (br_sp)"
+ br_spcapital: "Бразилія (br_spcapital)"
+ ca_ab: "Канада (ca_ab)"
+ ca_bc: "Канада (ca_bc)"
+ ca_mb: "Канада (ca_mb)"
+ ca_nb: "Канада (ca_nb)"
+ ca_nl: "Канада (ca_nl)"
+ ca_ns: "Канада (ca_ns)"
+ ca_nt: "Канада (ca_nt)"
+ ca_nu: "Канада (ca_nu)"
+ ca_on: "Канада (ca_on)"
+ ca_pe: "Канада (ca_pe)"
+ ca_qc: "Канада (ca_qc)"
+ ca_sk: "Канада (ca_sk)"
+ ca_yt: "Канада (ca_yt)"
+ ca: "Канада"
+ ch_ag: "Швейцарія (ch_ag)"
+ ch_ai: "Швейцарія (ch_ai)"
+ ch_ar: "Швейцарія (ch_ar)"
+ ch_be: "Швейцарія (ch_be)"
+ ch_bl: "Швейцарія (ch_bl)"
+ ch_bs: "Швейцарія (ch_bs)"
+ ch_fr: "Швейцарія (ch_fr)"
+ ch_ge: "Швейцарія (ch_ge)"
+ ch_gl: "Швейцарія (ch_gl)"
+ ch_gr: "Швейцарія (ch_gr)"
+ ch_ju: "Швейцарія (ch_ju)"
+ ch_lu: "Швейцарія (ch_lu)"
+ ch_ne: "Швейцарія (ch_ne)"
+ ch_nw: "Швейцарія (ch_nw)"
+ ch_ow: "Швейцарія (ch_ow)"
+ ch_sg: "Швейцарія (ch_sg)"
+ ch_sh: "Швейцарія (ch_sh)"
+ ch_so: "Швейцарія (ch_so)"
+ ch_sz: "Швейцарія (ch_sz)"
+ ch_tg: "Швейцарія (ch_tg)"
+ ch_ti: "Швейцарія (ch_ti)"
+ ch_ur: "Швейцарія (ch_ur)"
+ ch_vd: "Швейцарія (ch_vd)"
+ ch_vs: "Швейцарія (ch_vs)"
+ ch_zg: "Швейцарія (ch_zg)"
+ ch_zh: "Швейцарія (ch_zh)"
+ ch: "Швейцарія"
+ cl: "Чилі"
+ co: "Колумбія"
+ cr: "Коста-Ріка"
+ cz: "Чехія"
+ de_bb: "Німеччина (de_bb)"
+ de_be: "Німеччина (de_be)"
+ de_bw: "Німеччина (de_bw)"
+ de_by_augsburg: "Німеччина (de_by_augsburg)"
+ de_by_cath: "Німеччина (de_by_cath)"
+ de_by: "Німеччина (de_by)"
+ de_hb: "Німеччина (de_hb)"
+ de_he: "Німеччина (de_he)"
+ de_hh: "Німеччина (de_hh)"
+ de_mv: "Німеччина (de_mv)"
+ de_ni: "Німеччина (de_ni)"
+ de_nw: "Німеччина (de_nw)"
+ de_rp: "Німеччина (de_rp)"
+ de_sh: "Німеччина (de_sh)"
+ de_sl: "Німеччина (de_sl)"
+ de_sn_sorbian: "Німеччина (de_sn_sorbian)"
+ de_sn: "Німеччина (de_sn)"
+ de_st: "Німеччина (de_st)"
+ de_th_cath: "Німеччина (de_th_cath)"
+ de_th: "Німеччина (de_th)"
+ de: "Німеччина"
+ dk: "Данія"
+ ee: "Естонія"
+ el: "Греція"
+ es_an: "Іспанія (es_an)"
+ es_ar: "Іспанія (es_ar)"
+ es_ce: "Іспанія (es_ce)"
+ es_cl: "Іспанія (es_cl)"
+ es_cm: "Іспанія (es_cm)"
+ es_cn: "Іспанія (es_cn)"
+ es_ct: "Іспанія (es_ct)"
+ es_ex: "Іспанія (es_ex)"
+ es_ga: "Іспанія (es_ga)"
+ es_ib: "Іспанія (es_ib)"
+ es_lo: "Іспанія (es_lo)"
+ es_m: "Іспанія (es_m)"
+ es_mu: "Іспанія (es_mu)"
+ es_na: "Іспанія (es_na)"
+ es_o: "Іспанія (es_o)"
+ es_pv: "Іспанія (es_pv)"
+ es_v: "Іспанія (es_v)"
+ es_vc: "Іспанія (es_vc)"
+ es: "Іспанія"
+ fi: "Фінляндія"
+ fr_a: "Франція (fr_a)"
+ fr_m: "Франція (fr_m)"
+ fr: "Франція"
+ gb_con: "Велика Британія (gb_con)"
+ gb_eaw: "Велика Британія (gb_eaw)"
+ gb_eng: "Велика Британія (gb_eng)"
+ gb_gsy: "Великобританія (gb_gsy)"
+ gb_iom: "Великобританія (gb_iom)"
+ gb_jsy: "Великобританія (gb_jsy)"
+ gb_nir: "Велика Британія (gb_nir)"
+ gb_sct: "Велика Британія (gb_sct)"
+ gb_wls: "Велика Британія (gb_wls)"
+ gb: "Велика Британія"
+ ge: "Грузія"
+ gg: "Гернсі"
+ gh: "Гана"
+ hk: "Гонконг"
+ hr: "Хорватія"
+ hu: "Угорщина"
+ id: "Індонезія"
+ ie: "Ірландія"
+ im: "Острів Мен"
+ in: "Індія"
+ in_gj: "Індія (in_gj)"
+ in_mh: "Індія (in_mh)"
+ in_rj: "Індія (in_rj)"
+ in_tn: "Індія (in_tn)"
+ in_ka: "Індія (in_ka)"
+ is: "Ісландія"
+ it_bl: "Італія (it_bl)"
+ it_fi: "Італія (it_fi)"
+ it_ge: "Італія (it_ge)"
+ it_pd: "Італія (it_pd)"
+ it_rm: "Італія (it_rm)"
+ it_ro: "Італія (it_ro)"
+ it_to: "Італія (it_to)"
+ it_tv: "Італія (it_tv)"
+ it_ve: "Італія (it_ve)"
+ it_vi: "Італія (it_vi)"
+ it_vr: "Італія (it_vr)"
+ it: "Італія"
+ je: "Джерсі"
+ jp: "Японія"
+ ke: "Кенія"
+ kr: "Корея (Республіка)"
+ kz: "Казахстан (Республіка)"
+ li: "Ліхтенштейн"
+ lt: "Литва"
+ lu: "Люксембург"
+ lv: "Латвія"
+ ma: "Марокко"
+ mt_en: "Мальта (mt_en)"
+ mt_mt: "Мальта (mt_mt)"
+ mx_pue: "Мексика (mx_pue)"
+ mx: "Мексика"
+ my: "Малайзія"
+ ng: "Nigeria"
+ nl: "Нідерланди"
+ "no": "Норвегія"
+ nz_ak: "Нова Зеландія (nz_ak)"
+ nz_ca: "Нова Зеландія (nz_ca)"
+ nz_ch: "Нова Зеландія (nz_ch)"
+ nz_hb: "Нова Зеландія (nz_hb)"
+ nz_mb: "Нова Зеландія (nz_mb)"
+ nz_ne: "Нова Зеландія (nz_ne)"
+ nz_nl: "Нова Зеландія (nz_nl)"
+ nz_ot: "Нова Зеландія (nz_ot)"
+ nz_sc: "Нова Зеландія (nz_sc)"
+ nz_sl: "Нова Зеландія (nz_sl)"
+ nz_ta: "Нова Зеландія (nz_ta)"
+ nz_we: "Нова Зеландія (nz_we)"
+ nz_wl: "Нова Зеландія (nz_wl)"
+ nz: "Нова Зеландія"
+ pe: "Перу"
+ ph: "Філіппіни"
+ pl: "Польща"
+ pt_li: "Португалія (pt_li)"
+ pt_po: "Португалія (pt_po)"
+ pt: "Португалія"
+ ro: "Румунія"
+ rs_cyrl: "Сербія (rs_cyrl)"
+ rs_la: "Сербія (rs_la)"
+ ru: "російська федерація"
+ se: "Швеція"
+ sa: "Саудівська Аравія"
+ sg: "Сінгапур"
+ si: "Slovenia"
+ sk: "Словаччина"
+ th: "Таїланд"
+ tn: "Туніс"
+ tr: "Туреччина"
+ ua: "Україна"
+ us_ak: "Сполучені Штати Америки (us_ak)"
+ us_al: "Сполучені Штати Америки (us_al)"
+ us_ar: "Сполучені Штати Америки (us_ar)"
+ us_az: "Сполучені Штати Америки (us_az)"
+ us_ca: "Сполучені Штати Америки (us_ca)"
+ us_co: "Сполучені Штати Америки (us_co)"
+ us_ct: "Сполучені Штати Америки (us_ct)"
+ us_dc: "Сполучені Штати Америки (us_dc)"
+ us_de: "Сполучені Штати Америки (us_de)"
+ us_fl: "Сполучені Штати Америки (us_fl)"
+ us_ga: "США (us_ga)"
+ us_gu: "Сполучені Штати Америки (us_gu)"
+ us_hi: "Сполучені Штати Америки (us_hi)"
+ us_ia: "Сполучені Штати Америки (us_ia)"
+ us_id: "Сполучені Штати Америки (us_id)"
+ us_il: "Сполучені Штати Америки (us_il)"
+ us_in: "Сполучені Штати Америки (us_in)"
+ us_ks: "Сполучені Штати Америки (us_ks)"
+ us_ky: "Сполучені Штати Америки (us_ky)"
+ us_la: "Сполучені Штати Америки (us_la)"
+ us_ma: "Сполучені Штати Америки (us_ma)"
+ us_md: "Сполучені Штати Америки (us_md)"
+ us_me: "Сполучені Штати Америки (us_me)"
+ us_mi: "Сполучені Штати Америки (us_mi)"
+ us_mn: "Сполучені Штати Америки (us_mn)"
+ us_mo: "Сполучені Штати Америки (us_mo)"
+ us_ms: "Сполучені Штати Америки (us_ms)"
+ us_mt: "Сполучені Штати Америки (us_mt)"
+ us_nc: "Сполучені Штати Америки (us_nc)"
+ us_nd: "Сполучені Штати Америки (us_nd)"
+ us_ne: "Сполучені Штати Америки (us_ne)"
+ us_nh: "Сполучені Штати Америки (us_nh)"
+ us_nj: "Сполучені Штати Америки (us_nj)"
+ us_nm: "Сполучені Штати Америки (us_nm)"
+ us_nv: "Сполучені Штати Америки (us_nv)"
+ us_ny: "Сполучені Штати Америки (us_ny)"
+ us_oh: "Сполучені Штати Америки (us_oh)"
+ us_ok: "Сполучені Штати Америки (us_ok)"
+ us_or: "Сполучені Штати Америки (us_or)"
+ us_pa: "Сполучені Штати Америки (us_pa)"
+ us_pr: "Сполучені Штати Америки (us_pr)"
+ us_ri: "Сполучені Штати Америки (us_ri)"
+ us_sc: "Сполучені Штати Америки (us_sc)"
+ us_sd: "Сполучені Штати Америки (us_sd)"
+ us_tn: "Сполучені Штати Америки (us_tn)"
+ us_tx: "Сполучені Штати Америки (us_tx)"
+ us_ut: "Сполучені Штати Америки (us_ut)"
+ us_va: "Сполучені Штати Америки (us_va)"
+ us_vi: "Сполучені Штати Америки (us_vi)"
+ us_vt: "Сполучені Штати Америки (us_vt)"
+ us_wa: "Сполучені Штати Америки (us_wa)"
+ us_wi: "Сполучені Штати Америки (us_wi)"
+ us_wv: "Сполучені Штати Америки (us_wv)"
+ us_wy: "Сполучені Штати Америки (us_wy)"
+ us: "Сполучені Штати Америки"
+ ve: "Венесуела"
+ vi: "Віргінські острови (США)"
+ za: "Південна Африка"
+ zw: "Зімбабве"
+ toolbar_button:
+ today: "Сьогодні"
+ month: "Місяць"
+ week: "Тиждень"
+ day: "День"
+ list: "Список"
+ group_timezones:
+ search: "Пошук…"
+ group_availability: "%{group} доступність"
+ discourse_post_event:
+ notifications:
+ before_event_reminder: "Подія ось-ось розпочнеться"
+ after_event_reminder: "Подія завершилася"
+ ongoing_event_reminder: "Подія триває"
+ invite_user_notification: "%{username} %{description}"
+ invite_user_predefined_attendance_notification_html: "%{username} автоматично встановив вашу присутність і запросив вас на %{description}"
+ before_event_reminder_html: "Подія ось-ось розпочнеться %{description}"
+ after_event_reminder_html: "Подія закінчилася %{description}"
+ ongoing_event_reminder_html: "Подія триває %{description}"
+ edit_reason: "Подію оновлено"
+ edit_reason_closed: "Подія закрита"
+ edit_reason_opened: "Подія відкрита"
+ topic_title:
+ starts_at: "Подія розпочинається: %{date}"
+ ended_at: "Подія закінчилася: %{date}"
+ ends_in_duration: "Закінчується %{duration}"
+ show_all: "Показати всі"
+ show_participants: "Показати учасників"
+ participants:
+ one: "%{count} користувач брав участь."
+ few: "%{count} користувачі брали участь."
+ many: "%{count} користувачів брали участь."
+ other: "%{count} користувачів брали участь."
+ invite: "Сповістити користувача"
+ add_to_calendar: "Додати до календаря"
+ send_pm_to_creator: "Надіслати PM на %{username}"
+ leave: "Залишити подію"
+ edit_event: "Редагувати подію"
+ export_event: "Експорт події"
+ created_by: "Ким створено"
+ bulk_invite: "Масове запрошення"
+ close_event: "Закрити подію"
+ open_event: "Відкрити подію"
+ invitees_modal:
+ title_invited: "Участь у Події"
+ title_participated: "Список користувачів, які брали участь"
+ filter_placeholder: "Фільтр користувачів"
+ remove_invitee: "Видалити запрошення зі списку"
+ add_invitee: "Додати запрошення до списку"
+ bulk_invite_modal:
+ confirm: "підтвердити"
+ text: "Завантажити файл CSV"
+ title: "Масове запрошення"
+ success: "Файл успішно завантажений, ви отримаєте повідомлення, коли процес буде завершений."
+ error: "Вибачте, але файл повинен бути у форматі CSV."
+ confirmation_message: "Ви збираєтеся повідомити всіх у завантаженому файлі."
+ description_public: "Для масових запрошень на публічні події приймаються лише імена користувачів."
+ description_private: "Приватні події приймають лише назви груп для масових запрошень."
+ download_sample_csv: "Завантажити приклад CSV-файлу"
+ send_bulk_invites: "Надіслати запрошення"
+ group_selector_placeholder: "Виберіть групу..."
+ user_selector_placeholder: "Виберіть користувача..."
+ inline_title: "Вбудоване масове запрошення"
+ csv_title: "CSV-масове запрошення"
+ upcoming_events:
+ title: "Майбутні події"
+ creator: "Творець"
+ status: "Статус"
+ starts_at: "Починається о"
+ all_events: "Всі події"
+ my_events: "Мої події"
+ upcoming_events_list:
+ title: "Майбутні події"
+ empty: "Немає майбутніх подій"
+ all_day: "Увесь день"
+ error: "Не вдалося отримати події"
+ try_again: "Спробуйте ще раз"
+ view_all: "Переглянути всі"
+ category:
+ sort_topics_by_event_start_date: "Сортувати теми за датою початку подій."
+ disable_topic_resorting: "Відключити сортування тем."
+ settings_sections:
+ event_sorting: "Сортування подій"
+ preview:
+ more_than_one_event: "Ви не можете мати більше однієї події."
+ models:
+ invitee:
+ no_users: "Користувачів не знайдено"
+ status:
+ unknown: "Не цікавить"
+ going: "Йду"
+ not_going: "Не піду."
+ interested: "Цікавить"
+ going_count:
+ one: "%{count} йде"
+ few: "%{count} йдуть"
+ many: "%{count} йдуть"
+ other: "%{count} йдуть"
+ not_going_count:
+ one: "%{count} не збирається"
+ few: "%{count} не збираються"
+ many: "%{count} не збираються"
+ other: "%{count} не збираються"
+ interested_count:
+ one: "%{count} зацікавлений"
+ few: "%{count} зацікавлені"
+ many: "%{count} зацікавлені"
+ other: "%{count} зацікавлені"
+ invited_count:
+ one: "%{count} запрошений користувач"
+ few: "%{count} запрошені користувачі"
+ many: "%{count} запрошені користувачі"
+ other: "%{count} запрошені користувачі"
+ event:
+ expired: "Термін дії закінчився"
+ closed: "Закриті"
+ status:
+ standalone:
+ title: "Автономна"
+ description: "Не можна приєднатися до автономної події."
+ public:
+ title: "Публічні"
+ description: "До публічного заходу може приєднатися будь-хто."
+ private:
+ title: "Приватні"
+ description: "До закритої події можуть приєднатися лише запрошені користувачі."
+ builder_modal:
+ custom_fields:
+ label: "Користувацькі поля"
+ placeholder: "Опціонально"
+ description: "Дозволені спеціальні поля визначаються в налаштуваннях сайту. Користувальницькі поля використовуються для передачі даних іншим плагінам."
+ create_event_title: "Створити подію"
+ update_event_title: "Редагувати подію"
+ confirm_delete: "Ви впевнені, що хочете видалити цю подію?"
+ confirm_close: "Ви впевнені, що хочете закрити цю подію?"
+ confirm_open: "Ви впевнені, що хочете відкрити цю подію?"
+ create: "Створити"
+ update: "Зберегти"
+ attach: "Створити подію"
+ add_reminder: "Додати нагадування"
+ show_local_time:
+ label: "Показувати місцевий час"
+ description: "Дати і час відображатимуться за допомогою: %{timezone}. Використовуйте його для подій у певному місці, щоб час відображав часовий пояс, в якому відбувається подія."
+ timezone:
+ label: Часовий пояс
+ remove_timezone: Без часового поясу (UTC)
+ reminders:
+ label: "Нагадування"
+ types:
+ bump_topic: "автоматичне зібрання теми"
+ notification: "сповістити учасників"
+ units:
+ minutes: "хвилини"
+ hours: "години"
+ days: "дні"
+ weeks: "тижнів"
+ periods:
+ before: "до"
+ after: "після"
+ recurrence_until:
+ label: "До (включно)"
+ recurrence:
+ label: "Повторення"
+ none: "Без повторів"
+ every_day: "Щодня"
+ every_month: "Щомісяця в будні"
+ every_weekday: "Щодня в будній день"
+ every_week: "Щотижня в цей будній день"
+ every_two_weeks: "Кожні два тижні в цей будній день"
+ every_four_weeks: "Кожні чотири тижні в цей будній день"
+ minimal:
+ label: "Мінімальна подія"
+ checkbox_label: "Приховати кнопки \"Йду/Не йду\" та статус запрошених"
+ allow_chat:
+ label: "Інтеграція з чатом"
+ checkbox_label: "Створення та керування каналом чату для конкретних подій"
+ url:
+ label: "Посилання"
+ placeholder: "Опціонально"
+ location:
+ label: "Місцеположення"
+ placeholder: "Додайте місце розташування, посилання або щось інше."
+ description:
+ label: "Опис"
+ placeholder: "Розкажіть людям трохи більше про вашу подію. Підтримуються нові рядки та посилання."
+ name:
+ label: "Назва події"
+ placeholder: "Необов'язково, типові значення для назви теми"
+ invitees:
+ label: "Запрошені групи"
+ status:
+ label: "Статус"
+ invite_user_or_group:
+ title: "Сповістити користувача(ів) або групу(и)"
+ invite: "Надіслати"
diff --git a/plugins/discourse-calendar/config/locales/client.ur.yml b/plugins/discourse-calendar/config/locales/client.ur.yml
new file mode 100644
index 00000000000..91890cf9177
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/client.ur.yml
@@ -0,0 +1,80 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+ur:
+ js:
+ discourse_automation:
+ triggerables:
+ event_started:
+ fields:
+ topic_id:
+ label: ٹاپک ID
+ discourse_calendar:
+ disable_holiday: "غیر فعال کریں"
+ enable_holiday: "فعال کریں"
+ date: "تاریخ"
+ region:
+ none: "کوئی نہیں"
+ toolbar_button:
+ today: "آج"
+ month: "مہینہ"
+ week: "ہفتہ"
+ day: "دن"
+ group_timezones:
+ search: "تلاش کریں..."
+ discourse_post_event:
+ notifications:
+ invite_user_notification: "%{username} %{description}"
+ show_all: "تمام دکھائیں"
+ add_to_calendar: "کیلنڈر میں شامل کریں"
+ created_by: "بنائی گئی"
+ bulk_invite: "مجموئی دعوت نامہ"
+ bulk_invite_modal:
+ confirm: "تصدیق کریں"
+ title: "مجموئی دعوت نامہ"
+ success: "فائل کامیابی سے اَپ لوڈ کر دی گئی، عمل مکمل ہونے پر آپ کو پیغام کے ذریعے مطلع کر دیا جائے گا۔"
+ error: "معذرت، فائل CSV فارمیٹ میں ہونا ضروری ہے۔"
+ upcoming_events:
+ status: "سٹیٹس"
+ models:
+ event:
+ expired: "میعاد ختم ہوگئی"
+ closed: "بند"
+ status:
+ public:
+ title: "عوامی"
+ private:
+ title: "ذاتی"
+ builder_modal:
+ custom_fields:
+ placeholder: "اختیاری"
+ create: "بنائیں"
+ update: "محفوظ کریں"
+ timezone:
+ label: ٹائم زون
+ reminders:
+ units:
+ minutes: "منٹ"
+ hours: "گھنٹے"
+ days: "دن"
+ periods:
+ before: "سے پہلے"
+ after: "کے بعد"
+ recurrence:
+ label: "تَقْلِید"
+ none: "تَقْلِید نہیں"
+ every_day: "ہر دن"
+ url:
+ label: "URL"
+ placeholder: "اختیاری"
+ location:
+ label: "محل وقوع"
+ description:
+ label: "تفصیل"
+ status:
+ label: "سٹیٹس"
+ invite_user_or_group:
+ invite: "بھیجیں"
diff --git a/plugins/discourse-calendar/config/locales/client.vi.yml b/plugins/discourse-calendar/config/locales/client.vi.yml
new file mode 100644
index 00000000000..d4c87656c31
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/client.vi.yml
@@ -0,0 +1,80 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+vi:
+ js:
+ discourse_automation:
+ triggerables:
+ event_started:
+ fields:
+ topic_id:
+ label: ID Chủ đề
+ discourse_calendar:
+ disable_holiday: "Tắt"
+ enable_holiday: "Bật"
+ date: "ày"
+ region:
+ none: "Không có gì"
+ toolbar_button:
+ today: "Hôm nay"
+ month: "Tháng"
+ week: "Tuần"
+ day: "Ngày"
+ group_timezones:
+ search: "Tìm kiến..."
+ discourse_post_event:
+ notifications:
+ invite_user_notification: "%{username} %{description}"
+ show_all: "Hiển thị tất cả"
+ add_to_calendar: "Thêm vào lịch"
+ created_by: "Tạo bởi"
+ bulk_invite: "Mời hàng loạt"
+ bulk_invite_modal:
+ confirm: "xác nhận"
+ title: "Mời hàng loạt"
+ success: "Tải lên thành công, bạn sẽ được thông báo qua tin nhắn khi quá trình hoàn tất."
+ error: "Xin lỗi, file phải ở định dạng CSV."
+ upcoming_events:
+ status: "Trạng thái"
+ models:
+ event:
+ expired: "Hết hạn"
+ closed: "Đã "
+ status:
+ public:
+ title: "Công khai"
+ private:
+ title: "Riêng tư"
+ builder_modal:
+ custom_fields:
+ placeholder: "Tùy chọn"
+ create: "Tạo"
+ update: "Lưu lại"
+ timezone:
+ label: Múi giờ
+ reminders:
+ units:
+ minutes: "phút"
+ hours: "tiếng"
+ days: "ngày"
+ periods:
+ before: "trước"
+ after: "sau"
+ recurrence:
+ label: "Tái diễn"
+ none: "Không tái diễn"
+ every_day: "Mỗi ngày"
+ url:
+ label: "URL"
+ placeholder: "Tùy chọn"
+ location:
+ label: "Vị trí"
+ description:
+ label: "Mô tả"
+ status:
+ label: "Trạng thái"
+ invite_user_or_group:
+ invite: "Gửi"
diff --git a/plugins/discourse-calendar/config/locales/client.zh_CN.yml b/plugins/discourse-calendar/config/locales/client.zh_CN.yml
new file mode 100644
index 00000000000..b2298bd77f0
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/client.zh_CN.yml
@@ -0,0 +1,477 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+zh_CN:
+ admin_js:
+ admin:
+ calendar: "日历"
+ site_settings:
+ categories:
+ discourse_post_event: "Discourse Event"
+ discourse_calendar: "Discourse Calendar"
+ js:
+ notifications:
+ titles:
+ event_reminder: "活动提醒"
+ event_invitation: "活动邀请"
+ popup:
+ event_reminder: 活动提醒
+ discourse_automation:
+ triggerables:
+ event_started:
+ title: 活动已开始
+ fields:
+ topic_id:
+ label: 话题 ID
+ discourse_calendar:
+ invite_user_notification: "%{username} 邀请您加入:%{description}"
+ on_holiday: "休假"
+ disable_holiday: "禁用"
+ enable_holiday: "启用"
+ holiday: "假期"
+ holidays:
+ header_title: "假期"
+ pick_region_description: "选择一个区域以查看该区域的假期。"
+ disabled_holidays_description: "已禁用的假期将被排除在管理人员假期日历之外。"
+ date: "日期"
+ add_to_calendar: "添加到 Google 日历"
+ toggle_timezone_offset_title: "切换时区偏移"
+ region:
+ title: "区域"
+ none: "无"
+ use_current_region: "使用当前区域"
+ names:
+ ae: "阿拉伯联合酋长国"
+ ar: "阿根廷"
+ at: "奥地利"
+ au_act: "澳大利亚 (au_act)"
+ au_nsw: "澳大利亚 (au_nsw)"
+ au_nt: "澳大利亚 (au_nt)"
+ au_qld_brisbane: "澳大利亚 (au_qld_brisbane)"
+ au_qld_cairns: "澳大利亚 (au_qld_cairns)"
+ au_qld: "澳大利亚 (au_qld)"
+ au_sa: "澳大利亚 (au_sa)"
+ au_tas_north: "澳大利亚 (au_tas_north)"
+ au_tas_south: "澳大利亚 (au_tas_south)"
+ au_tas: "澳大利亚 (au_tas)"
+ au_vic_melbourne: "澳大利亚 (au_vic_melbourne)"
+ au_vic: "澳大利亚 (au_vic)"
+ au_wa: "澳大利亚 (au_wa)"
+ au: "澳大利亚"
+ be_fr: "比利时 (be_fr)"
+ be_nl: "比利时 (be_nl)"
+ bg_bg: "保加利亚 (bg_bg)"
+ bg_en: "保加利亚 (bg_en)"
+ br: "巴西"
+ br_sp: "巴西 (br_sp)"
+ br_spcapital: "巴西 (br_spcapital)"
+ ca_ab: "加拿大 (ca_ab)"
+ ca_bc: "加拿大 (ca_bc)"
+ ca_mb: "加拿大 (ca_mb)"
+ ca_nb: "加拿大 (ca_nb)"
+ ca_nl: "加拿大 (ca_nl)"
+ ca_ns: "加拿大 (ca_ns)"
+ ca_nt: "加拿大 (ca_nt)"
+ ca_nu: "加拿大 (ca_nu)"
+ ca_on: "加拿大 (ca_on)"
+ ca_pe: "加拿大 (ca_pe)"
+ ca_qc: "加拿大 (ca_qc)"
+ ca_sk: "加拿大 (ca_sk)"
+ ca_yt: "加拿大 (ca_yt)"
+ ca: "加拿大"
+ ch_ag: "瑞士 (ch_ag)"
+ ch_ai: "瑞士 (ch_ai)"
+ ch_ar: "瑞士 (ch_ar)"
+ ch_be: "瑞士 (ch_be)"
+ ch_bl: "瑞士 (ch_bl)"
+ ch_bs: "瑞士 (ch_bs)"
+ ch_fr: "瑞士 (ch_fr)"
+ ch_ge: "瑞士 (ch_ge)"
+ ch_gl: "瑞士 (ch_gl)"
+ ch_gr: "瑞士 (ch_gr)"
+ ch_ju: "瑞士 (ch_ju)"
+ ch_lu: "瑞士 (ch_lu)"
+ ch_ne: "瑞士 (ch_ne)"
+ ch_nw: "瑞士 (ch_nw)"
+ ch_ow: "瑞士 (ch_ow)"
+ ch_sg: "瑞士 (ch_sg)"
+ ch_sh: "瑞士 (ch_sh)"
+ ch_so: "瑞士 (ch_so)"
+ ch_sz: "瑞士 (ch_sz)"
+ ch_tg: "瑞士 (ch_tg)"
+ ch_ti: "瑞士 (ch_ti)"
+ ch_ur: "瑞士 (ch_ur)"
+ ch_vd: "瑞士 (ch_vd)"
+ ch_vs: "瑞士 (ch_vs)"
+ ch_zg: "瑞士 (ch_zg)"
+ ch_zh: "瑞士 (ch_zh)"
+ ch: "瑞士"
+ cl: "智利"
+ co: "哥伦比亚"
+ cr: "哥斯达黎加"
+ cz: "捷克共和国"
+ de_bb: "德国 (de_bb)"
+ de_be: "德国 (de_be)"
+ de_bw: "德国 (de_bw)"
+ de_by_augsburg: "德国 (de_by_augsburg)"
+ de_by_cath: "德国 (de_by_cath)"
+ de_by: "德国 (de_by)"
+ de_hb: "德国 (de_hb)"
+ de_he: "德国 (de_he)"
+ de_hh: "德国 (de_hh)"
+ de_mv: "德国 (de_mv)"
+ de_ni: "德国 (de_ni)"
+ de_nw: "德国 (de_nw)"
+ de_rp: "德国 (de_rp)"
+ de_sh: "德国 (de_sh)"
+ de_sl: "德国 (de_sl)"
+ de_sn_sorbian: "德国 (de_sn_sorbian)"
+ de_sn: "德国 (de_sn)"
+ de_st: "德国 (de_st)"
+ de_th_cath: "德国 (de_th_cath)"
+ de_th: "德国 (de_th)"
+ de: "德国"
+ dk: "丹麦"
+ ee: "爱沙尼亚"
+ el: "希腊"
+ es_an: "西班牙 (es_an)"
+ es_ar: "西班牙 (es_ar)"
+ es_ce: "西班牙 (es_ce)"
+ es_cl: "西班牙 (es_cl)"
+ es_cm: "西班牙 (es_cm)"
+ es_cn: "西班牙 (es_cn)"
+ es_ct: "西班牙 (es_ct)"
+ es_ex: "西班牙 (es_ex)"
+ es_ga: "西班牙 (es_ga)"
+ es_ib: "西班牙 (es_ib)"
+ es_lo: "西班牙 (es_lo)"
+ es_m: "西班牙 (es_m)"
+ es_mu: "西班牙 (es_mu)"
+ es_na: "西班牙 (es_na)"
+ es_o: "西班牙 (es_o)"
+ es_pv: "西班牙 (es_pv)"
+ es_v: "西班牙 (es_v)"
+ es_vc: "西班牙 (es_vc)"
+ es: "西班牙"
+ fi: "芬兰"
+ fr_a: "法国 (fr_a)"
+ fr_m: "法国 (fr_m)"
+ fr: "法国"
+ gb_con: "英国 (gb_con)"
+ gb_eaw: "英国 (gb_eaw)"
+ gb_eng: "英国 (gb_eng)"
+ gb_gsy: "英国 (gb_gsy)"
+ gb_iom: "英国 (gb_iom)"
+ gb_jsy: "英国 (gb_jsy)"
+ gb_nir: "英国 (gb_nir)"
+ gb_sct: "英国 (gb_sct)"
+ gb_wls: "英国 (gb_wls)"
+ gb: "英国"
+ ge: "格鲁吉亚"
+ gg: "根西岛"
+ gh: "加纳"
+ hk: "中国香港"
+ hr: "克罗地亚"
+ hu: "匈牙利"
+ id: "印度尼西亚"
+ ie: "爱尔兰"
+ im: "马恩岛"
+ in: "印度"
+ in_gj: "印度 (in_gj)"
+ in_mh: "印度 (in_mh)"
+ in_rj: "印度 (in_rj)"
+ in_tn: "印度 (in_tn)"
+ in_ka: "印度 (in_ka)"
+ is: "冰岛"
+ it_bl: "意大利 (it_bl)"
+ it_fi: "意大利 (it_fi)"
+ it_ge: "意大利 (it_ge)"
+ it_pd: "意大利 (it_pd)"
+ it_rm: "意大利 (it_rm)"
+ it_ro: "意大利 (it_ro)"
+ it_to: "意大利 (it_to)"
+ it_tv: "意大利 (it_tv)"
+ it_ve: "意大利 (it_ve)"
+ it_vi: "意大利 (it_vi)"
+ it_vr: "意大利 (it_vr)"
+ it: "意大利"
+ je: "泽西岛"
+ jp: "日本"
+ ke: "肯尼亚"
+ kr: "韩国"
+ kz: "哈萨克斯坦"
+ li: "列支敦士登"
+ lt: "立陶宛"
+ lu: "卢森堡"
+ lv: "拉脱维亚"
+ ma: "摩洛哥"
+ mt_en: "马耳他 (mt_en)"
+ mt_mt: "马耳他 (mt_mt)"
+ mx_pue: "墨西哥 (mx_pue)"
+ mx: "墨西哥"
+ my: "马来西亚"
+ ng: "尼日利亚"
+ nl: "荷兰"
+ "no": "挪威"
+ nz_ak: "新西兰 (nz_ak)"
+ nz_ca: "新西兰 (nz_ca)"
+ nz_ch: "新西兰 (nz_ch)"
+ nz_hb: "新西兰 (nz_hb)"
+ nz_mb: "新西兰 (nz_mb)"
+ nz_ne: "新西兰 (nz_ne)"
+ nz_nl: "新西兰 (nz_nl)"
+ nz_ot: "新西兰 (nz_ot)"
+ nz_sc: "新西兰 (nz_sc)"
+ nz_sl: "新西兰 (nz_sl)"
+ nz_ta: "新西兰 (nz_ta)"
+ nz_we: "新西兰 (nz_we)"
+ nz_wl: "新西兰 (nz_wl)"
+ nz: "新西兰"
+ pe: "秘鲁"
+ ph: "菲律宾"
+ pl: "波兰"
+ pt_li: "葡萄牙 (pt_li)"
+ pt_po: "葡萄牙 (pt_po)"
+ pt: "葡萄牙"
+ ro: "罗马尼亚"
+ rs_cyrl: "塞尔维亚 (rs_cyrl)"
+ rs_la: "塞尔维亚 (rs_la)"
+ ru: "俄罗斯联邦"
+ se: "瑞典"
+ sa: "沙特阿拉伯"
+ sg: "新加坡"
+ si: "斯洛文尼亚"
+ sk: "斯洛伐克"
+ th: "泰国"
+ tn: "突尼斯"
+ tr: "土耳其"
+ ua: "乌克兰"
+ us_ak: "美国 (us_ak)"
+ us_al: "美国 (us_al)"
+ us_ar: "美国 (us_ar)"
+ us_az: "美国 (us_az)"
+ us_ca: "美国 (us_ca)"
+ us_co: "美国 (us_co)"
+ us_ct: "美国 (us_ct)"
+ us_dc: "美国 (us_dc)"
+ us_de: "美国 (us_de)"
+ us_fl: "美国 (us_fl)"
+ us_ga: "美国 (us_ga)"
+ us_gu: "美国 (us_gu)"
+ us_hi: "美国 (us_hi)"
+ us_ia: "美国 (us_ia)"
+ us_id: "美国 (us_id)"
+ us_il: "美国 (us_il)"
+ us_in: "美国 (us_in)"
+ us_ks: "美国 (us_ks)"
+ us_ky: "美国 (us_ky)"
+ us_la: "美国 (us_la)"
+ us_ma: "美国 (us_ma)"
+ us_md: "美国 (us_md)"
+ us_me: "美国 (us_me)"
+ us_mi: "美国 (us_mi)"
+ us_mn: "美国 (us_mn)"
+ us_mo: "美国 (us_mo)"
+ us_ms: "美国 (us_ms)"
+ us_mt: "美国 (us_mt)"
+ us_nc: "美国 (us_nc)"
+ us_nd: "美国 (us_nd)"
+ us_ne: "美国 (us_ne)"
+ us_nh: "美国 (us_nh)"
+ us_nj: "美国 (us_nj)"
+ us_nm: "美国 (us_nm)"
+ us_nv: "美国 (us_nv)"
+ us_ny: "美国 (us_ny)"
+ us_oh: "美国 (us_oh)"
+ us_ok: "美国 (us_ok)"
+ us_or: "美国 (us_or)"
+ us_pa: "美国 (us_pa)"
+ us_pr: "美国 (us_pr)"
+ us_ri: "美国 (us_ri)"
+ us_sc: "美国 (us_sc)"
+ us_sd: "美国 (us_sd)"
+ us_tn: "美国 (us_tn)"
+ us_tx: "美国 (us_tx)"
+ us_ut: "美国 (us_ut)"
+ us_va: "美国 (us_va)"
+ us_vi: "美国 (us_vi)"
+ us_vt: "美国 (us_vt)"
+ us_wa: "美国 (us_wa)"
+ us_wi: "美国 (us_wi)"
+ us_wv: "美国 (us_wv)"
+ us_wy: "美国 (us_wy)"
+ us: "美国"
+ ve: "委内瑞拉"
+ vi: "美属维尔京群岛"
+ za: "南非"
+ toolbar_button:
+ today: "今天"
+ month: "月"
+ week: "周"
+ day: "天"
+ list: "列表"
+ group_timezones:
+ search: "搜索…"
+ group_availability: "%{group} 可用性"
+ discourse_post_event:
+ notifications:
+ before_event_reminder: "活动即将开始"
+ after_event_reminder: "活动已结束"
+ ongoing_event_reminder: "活动正在进行"
+ invite_user_notification: "%{username} %{description}"
+ invite_user_predefined_attendance_notification_html: "%{username} 已自动设置您的出席并邀请您加入“%{description}”"
+ before_event_reminder_html: "活动即将开始 – %{description}"
+ after_event_reminder_html: "活动已结束 – %{description}"
+ ongoing_event_reminder_html: "活动正在进行 – %{description}"
+ edit_reason: "活动已更新"
+ edit_reason_closed: "活动已关闭"
+ edit_reason_opened: "活动已开放"
+ topic_title:
+ starts_at: "活动将开始:%{date}"
+ ended_at: "活动已结束:%{date}"
+ ends_in_duration: "%{duration}后结束"
+ show_all: "全部显示"
+ show_participants: "显示参与者"
+ participants:
+ other: "%{count} 位用户参与。"
+ invite: "通知用户"
+ add_to_calendar: "添加到日历"
+ send_pm_to_creator: "向 %{username} 发送私信"
+ leave: "离开活动"
+ edit_event: "编辑活动"
+ export_event: "导出活动"
+ created_by: "创建者:"
+ bulk_invite: "批量邀请"
+ close_event: "关闭活动"
+ open_event: "开放活动"
+ invitees_modal:
+ title_invited: "活动参与"
+ title_participated: "参与用户列表"
+ filter_placeholder: "筛选用户"
+ remove_invitee: "从列表中移除受邀者"
+ add_invitee: "将受邀者添加到列表"
+ bulk_invite_modal:
+ confirm: "确认"
+ text: "上传 CSV 文件"
+ title: "批量邀请"
+ success: "文件上传成功。该过程完成后,您将收到消息通知。"
+ error: "抱歉,文件应为 CSV 格式。"
+ confirmation_message: "您将通知上传的文件中的所有人。"
+ description_public: "公开活动的批量邀请仅接受用户名。"
+ description_private: "不公开活动的批量邀请仅接受群组名。"
+ download_sample_csv: "下载示例 CSV 文件"
+ send_bulk_invites: "发送邀请"
+ group_selector_placeholder: "选择群组…"
+ user_selector_placeholder: "选择用户…"
+ inline_title: "内嵌批量邀请"
+ csv_title: "CSV 批量邀请"
+ upcoming_events:
+ title: "即将到来的活动"
+ creator: "创建者"
+ status: "状态"
+ starts_at: "开始时间"
+ upcoming_events_list:
+ title: "近期活动"
+ empty: "无近期活动"
+ all_day: "全天"
+ error: "无法检索活动"
+ try_again: "重试"
+ view_all: "查看全部"
+ category:
+ sort_topics_by_event_start_date: "按活动开始日期对话题进行排序。"
+ disable_topic_resorting: "禁用话题重新排序。"
+ settings_sections:
+ event_sorting: "活动排序"
+ preview:
+ more_than_one_event: "您不能有多个活动。"
+ models:
+ invitee:
+ no_users: "找不到用户"
+ status:
+ unknown: "不感兴趣"
+ going: "参加"
+ not_going: "不参加"
+ interested: "感兴趣"
+ going_count:
+ other: "%{count} 人参加"
+ not_going_count:
+ other: "%{count} 人不参加"
+ interested_count:
+ other: "%{count} 人感兴趣"
+ invited_count:
+ other: "%{count} 位受邀用户"
+ event:
+ expired: "已过期"
+ closed: "已关闭"
+ status:
+ standalone:
+ title: "独立"
+ description: "不可加入独立活动。"
+ public:
+ title: "公开"
+ description: "任何人都可以加入公开活动。"
+ private:
+ title: "不公开"
+ description: "只有受邀用户可以加入不公开活动。"
+ builder_modal:
+ custom_fields:
+ label: "自定义字段"
+ placeholder: "可选"
+ description: "允许的自定义字段在站点设置中定义。自定义字段用于将数据传输到其他插件。"
+ create_event_title: "创建活动"
+ update_event_title: "编辑活动"
+ confirm_delete: "确定要删除此活动吗?"
+ confirm_close: "确定要关闭此活动吗?"
+ confirm_open: "确定要开放此活动吗?"
+ create: "创建"
+ update: "保存"
+ attach: "创建活动"
+ add_reminder: "添加提醒"
+ timezone:
+ label: 时区
+ remove_timezone: 无时区 (UTC)
+ reminders:
+ label: "提醒"
+ types:
+ bump_topic: "自动提升话题"
+ notification: "通知参与者"
+ units:
+ minutes: "分钟"
+ hours: "小时"
+ days: "天"
+ weeks: "周"
+ periods:
+ before: "提前"
+ after: "推后"
+ recurrence:
+ label: "重复"
+ none: "不重复"
+ every_day: "每天"
+ every_month: "每个月的这个工作日"
+ every_weekday: "每个工作日"
+ every_week: "每周的这个工作日"
+ every_two_weeks: "每两周的这个工作日"
+ every_four_weeks: "每四周的这个工作日"
+ minimal:
+ label: "最低限度活动"
+ checkbox_label: "隐藏“参加/不参加”按钮和受邀者状态"
+ url:
+ label: "URL"
+ placeholder: "可选"
+ location:
+ label: "地点"
+ description:
+ label: "描述"
+ name:
+ label: "活动名称"
+ placeholder: "可选,默认为话题标题"
+ invitees:
+ label: "受邀群组"
+ status:
+ label: "状态"
+ invite_user_or_group:
+ title: "通知用户或群组"
+ invite: "发送"
diff --git a/plugins/discourse-calendar/config/locales/client.zh_TW.yml b/plugins/discourse-calendar/config/locales/client.zh_TW.yml
new file mode 100644
index 00000000000..6d41c8c108c
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/client.zh_TW.yml
@@ -0,0 +1,75 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+zh_TW:
+ js:
+ discourse_automation:
+ triggerables:
+ event_started:
+ fields:
+ topic_id:
+ label: 話題 ID
+ discourse_calendar:
+ disable_holiday: "禁用"
+ enable_holiday: "啟用"
+ date: "日期"
+ region:
+ none: "無"
+ toolbar_button:
+ today: "今天"
+ month: "月"
+ week: "週"
+ day: "天"
+ group_timezones:
+ search: "搜尋..."
+ discourse_post_event:
+ notifications:
+ invite_user_notification: "%{username} %{description}"
+ show_all: "顯示全部"
+ bulk_invite_modal:
+ success: "檔案已上傳成功,處理完畢後將以私人訊息通知你。"
+ error: "上傳的檔案必須是 csv 格式。"
+ upcoming_events:
+ status: "狀態"
+ models:
+ event:
+ expired: "已過期"
+ closed: "不公開"
+ status:
+ public:
+ title: "公開"
+ private:
+ title: "私密"
+ builder_modal:
+ custom_fields:
+ placeholder: "選擇性"
+ create: "創建"
+ update: "保存"
+ timezone:
+ label: 時區
+ reminders:
+ units:
+ minutes: "分鐘"
+ hours: "小時"
+ days: "天"
+ periods:
+ before: "之前"
+ after: "之後"
+ recurrence:
+ label: "週期"
+ none: "單一"
+ every_day: "每天"
+ url:
+ label: "網址"
+ placeholder: "選擇性"
+ location:
+ label: "位置"
+ description:
+ label: "簡述"
+ status:
+ label: "狀態"
+ invite_user_or_group:
+ invite: "發送"
diff --git a/plugins/discourse-calendar/config/locales/server.ar.yml b/plugins/discourse-calendar/config/locales/server.ar.yml
new file mode 100644
index 00000000000..243c4c4fa3b
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/server.ar.yml
@@ -0,0 +1,104 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+ar:
+ reports:
+ currently_away:
+ title: المستخدمون الغائبون حاليًا
+ labels:
+ username: اسم المستخدم
+ discourse_automation:
+ triggerables:
+ event_started:
+ title: بدأ الحدث
+ system_messages:
+ discourse_calendar_holiday_region_invalid: "منطقة العطلة التي أرسلتها غير موجودة."
+ discourse_calendar_enable_holiday_failed: "تعذَّر تفعيل هذه العطلة، إنها مفعَّلة حاليًا أو لم يتم إيقافها."
+ discourse_post_event_bulk_invite_succeeded:
+ title: "الحدث - نجحت الدعوة الجماعية"
+ subject_template: "تمت معالجة الدعوة الجماعية للمستخدمين بنجاح"
+ text_body_template: "تمت معالجة ملف الدعوة الجماعية الخاص بك، وتم إنشاء %{processed} من المدعوين."
+ discourse_post_event_bulk_invite_failed:
+ title: "الحدث - فشلت الدعوة الجماعية"
+ subject_template: "تمت معالجة الدعوة الجماعية للمستخدمين مع وجود أخطاء"
+ text_body_template: |
+ تمت معالجة ملف الدعوة الجماعية الخاص بك، وتم إنشاء %{processed} من المدعوين مع وجود %{failed} من الأخطاء.
+
+ إليك السجل:
+
+ ```text
+ %{logs}
+ ```
+ site_settings:
+ events_max_rows: "الحد الأقصى للصفوف النصية لكل حدث في التقويم."
+ map_events_to_color: "تعيين لون لكل وسم أو فئة."
+ map_events_title: "يستبدل \"الأحداث\" في عنوان الشريط الجانبي \"الأحداث القادمة\" لكل فئة."
+ calendar_enabled: "قم بتفعيل المكوِّن الإضافي لتقويم Discourse. سيضيف ذلك دعمًا لعلامة [calendar][/calendar] في أول منشور في الموضوع."
+ discourse_post_event_enabled: "يفعِّل ميزات الحدث. ملاحظة: يحتاج أيضًا إلى تفعيل `calendar enabled`."
+ displayed_invitees_limit: "يحد من أعداد المدعوين المعروضة في حدث."
+ display_post_event_date_on_topic_title: "يعرض تاريخ الحدث بعد عنوان الموضوع."
+ use_local_event_date: "استخدام التاريخ المحلي بعد عنوان الموضوع بدلًا من الوقت النسبي."
+ discourse_post_event_allowed_on_groups: "المجموعات المسموح لها بإنشاء الأحداث."
+ discourse_post_event_allowed_custom_fields: "يتيح السماح لكل حدث بتحديد قيمة الحقول المخصَّصة."
+ discourse_post_event_edit_notifications_time_extension: "يمد (بالدقائق) الفترة بعد نهاية حدث عندما يستمر المدعوون ذوي الحالة `going` في تلقي الإشعارات عند التعديل في المنشور الأصلي."
+ holiday_calendar_topic_id: "معرِّف الموضوع لتقويم العطلات/الغياب لفريق العمل"
+ holiday_status_emoji: يحدِّد الرمز التعبيري المستخدم في حالة العطلة.
+ delete_expired_event_posts_after: "سيتم حذف المنشورات ذات الأحداث المنتهية تلقائيًا بعد (n) من الساعات. اضبط القيمة على 1- لإيقاف الحذف."
+ all_day_event_start_time: "ستبدأ الأحداث التي ليس بها وقت محدَّد للبدء في هذا الوقت. التنسيق هو HH:mm. للساعة 6 صباحًا، أدخل 06:00."
+ all_day_event_end_time: "ستنتهي الأحداث التي ليس بها وقت محدَّد للانتهاء في هذا الوقت. التنسيق هو HH:mm. للساعة 6 مساءً، أدخل 18:00."
+ all_day_event_time_error: "الوقت غير صالح. يجب كتابة الوقت بالتنسيق HH:mm (مثال: 08:00)."
+ calendar_categories: "عرض تقويم في أعلى الفئة. الإعدادات الإلزامية هي categoryId وpostId. مثال: categoryId=6;postId=453\n الإعدادات الصالحة الأخرى: tzPicker وweekends وdefaultView."
+ calendar_categories_outlet: "يسمح بتغيير المنفذ المفترض أن يعرض تقويم الفئة."
+ working_days: "تحديد أيام العمل. يمكنك عرض مدى توافر مجموعة باستخدام علامة `timezones` في منشور: مثال: `[timezones group=admins][timezones]`"
+ working_day_start_hour: "وقت البدء لساعات يوم العمل"
+ working_day_end_hour: "وقت الانتهاء لساعات يوم العمل"
+ close_to_working_day_hours_extension: "تحديد وقت التمديد في ساعات يوم العمل لتمييز المناطق الزمنية"
+ events_calendar_categories: "عرض تقويم الأحداث في أعلى الفئة."
+ sort_categories_by_event_start_date_enabled: "تفعيل ترتيب موضوعات الفئات حسب تاريخ بدء الحدث."
+ disable_resorting_on_categories_enabled: "السماح للفئات بإيقاف قدرة المستخدمين على الترتيب حسب فئة الحدث."
+ calendar_automatic_holidays_enabled: "يحدِّد حالة العطلة تلقائيًا حسب المنطقة الجغرافية للمستخدمين (ملاحظة: يمكنك إيقاف عطلات تلقائية محدَّدة في إعدادات المكوِّن الإضافي)"
+ event_participation_buttons: "قائمة أزرار المشاركة في الأحداث التي يمكن للمستخدمين استخدامها."
+ sidebar_show_upcoming_events: "عرض رابط الأحداث القادمة في الشريط الجانبي ضمن \"المزيد\"."
+ include_expired_events_on_calendar: "تضمين الأحداث الماضية/منتهية الصلاحية في طرق عرض تقويم الفئة والأحداث القادمة."
+ discourse_calendar:
+ invite_user_notification: "دعاك %{username} إلى: %{description}"
+ calendar_must_be_in_first_post: "لا يمكن استخدام علامة التقويم إلا في المنشور الأول في الموضوع."
+ more_than_one_calendar: "لا يمكن أن يكون لديك أكثر من تقويم واحد في المنشور."
+ more_than_two_dates: "لا يمكن أن يحتوي منشور ضمن موضوع في التقويم على أكثر من تاريخين."
+ event_expired: "انتهى الحدث"
+ holiday_status:
+ description: "في عطلة"
+ discourse_post_event:
+ notifications:
+ before_event_reminder: "%{title} على وشك البدء."
+ after_event_reminder: "انتهى %{title}."
+ ongoing_event_reminder: "%{title} جارٍ."
+ errors:
+ bulk_invite:
+ max_invitees: "تم إنشاء أول %{max_invittes} من المدعوين. جرِّب تقسيم الملف إلى أجزاء أصغر."
+ error: "حدث خطأ في تحميل هذا الملف. يُرجى إعادة المحاولة لاحقًا."
+ models:
+ event:
+ only_one_event: "لا يمكن أن يتضمَّن المنشور أكثر من حدثٍ واحد."
+ must_be_in_first_post: "لا يمكن إلا للمنشور الأول في موضوع أن يتضمَّن حدثًا."
+ raw_invitees_length: "يقتصر الحدث على %{count} من المستخدمين/المجموعات."
+ raw_invitees:
+ only_group: "لا يقبل الحدث إلا أسماء المجموعات."
+ ends_at_before_starts_at: "لا يمكن أن ينتهي الحدث قبل أن يبدأ."
+ start_must_be_present_and_a_valid_date: "يتطلَّب الحدث تاريخ بدء صالحًا."
+ end_must_be_a_valid_date: "يجب أن يكون تاريخ الانتهاء تاريخًا صالحًا."
+ invalid_recurrence: "يجب أن يكون التكرار واحدًا مما يلي: every_month أو every_week أو every_two_weeks أو every_four_week أو every_day، أو every_weekday."
+ invalid_timezone: "لم يتم التعرُّف على المنطقة الزمنية."
+ acting_user_not_allowed_to_create_event: "غير مسموح للمستخدم الحالي بإنشاء أحداث."
+ acting_user_not_allowed_to_act_on_this_event: "غير مسموح للمستخدم الحالي باتخاذ إجراء بشأن هذا الحدث."
+ invalid_allowed_groups: "المجموعات المسموح بها غير صالحة."
+ acting_user_not_allowed_to_invite_these_groups: "غير مسموح للمستخدم الحالي بدعوة تلك المجموعات."
+ custom_field_is_invalid: "غير مسموح بالحقل المخصَّص `%{field}`."
+ name:
+ length: "يجب أن يتراوح طول اسم الحدث بين %{minimum} و%{maximum} من الأحرف."
+ discourse_push_notifications:
+ popup:
+ event_reminder: "تذكير بالحدث"
diff --git a/plugins/discourse-calendar/config/locales/server.be.yml b/plugins/discourse-calendar/config/locales/server.be.yml
new file mode 100644
index 00000000000..20eda3cf78d
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/server.be.yml
@@ -0,0 +1,15 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+be:
+ reports:
+ currently_away:
+ labels:
+ username: Імя карыстальніка
+ discourse_post_event:
+ errors:
+ bulk_invite:
+ error: "Была памылка загрузкі гэтага файла. Калі ласка паспрабуйце зноў пазней."
diff --git a/plugins/discourse-calendar/config/locales/server.bg.yml b/plugins/discourse-calendar/config/locales/server.bg.yml
new file mode 100644
index 00000000000..6d339b1a1d7
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/server.bg.yml
@@ -0,0 +1,11 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+bg:
+ reports:
+ currently_away:
+ labels:
+ username: Потребителско име
diff --git a/plugins/discourse-calendar/config/locales/server.bs_BA.yml b/plugins/discourse-calendar/config/locales/server.bs_BA.yml
new file mode 100644
index 00000000000..877829d4ad8
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/server.bs_BA.yml
@@ -0,0 +1,11 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+bs_BA:
+ reports:
+ currently_away:
+ labels:
+ username: Nadimak
diff --git a/plugins/discourse-calendar/config/locales/server.ca.yml b/plugins/discourse-calendar/config/locales/server.ca.yml
new file mode 100644
index 00000000000..38e0537b23f
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/server.ca.yml
@@ -0,0 +1,15 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+ca:
+ reports:
+ currently_away:
+ labels:
+ username: Nom d'usuari
+ discourse_post_event:
+ errors:
+ bulk_invite:
+ error: "Hi ha hagut un error en carregar aquest fitxer. Proveu-ho de nou més tard."
diff --git a/plugins/discourse-calendar/config/locales/server.cs.yml b/plugins/discourse-calendar/config/locales/server.cs.yml
new file mode 100644
index 00000000000..5c6616ff01c
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/server.cs.yml
@@ -0,0 +1,104 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+cs:
+ reports:
+ currently_away:
+ title: Aktuálně nedostupní uživatelé
+ labels:
+ username: Uživatelské jméno
+ discourse_automation:
+ triggerables:
+ event_started:
+ title: Zahájena událost
+ system_messages:
+ discourse_calendar_holiday_region_invalid: "Vámi zadaný region pro svátky neexistuje."
+ discourse_calendar_enable_holiday_failed: "Tento svátek nelze povolit, je již povolen nebo není zakázán."
+ discourse_post_event_bulk_invite_succeeded:
+ title: "Událost – hromadné pozvání bylo úspěšné"
+ subject_template: "Hromadná pozvánka byla úspěšně zpracována"
+ text_body_template: "Váš soubor s hromadnou pozvánkou byl zpracován, bylo vytvořeno %{processed} pozvánek."
+ discourse_post_event_bulk_invite_failed:
+ title: "Událost – hromadné pozvání se nezdařilo"
+ subject_template: "Zpracování hromadné pozvánky skončilo s chybami"
+ text_body_template: |
+ Váš soubor s hromadnou pozvánkou byl zpracován, bylo vytvořeno %{processed} pozvánek s %{failed} chybami.
+
+ Zde je výpis:
+
+ ```text
+ %{logs}
+ ```
+ site_settings:
+ events_max_rows: "Maximální počet řádků textu na událost v kalendáři."
+ map_events_to_color: "Každému štítku nebo kategorii přiřadit barvu."
+ map_events_title: "Přepíše „Události“ v názvu postranního panelu „Nadcházející události“ podle kategorie."
+ calendar_enabled: "Povolte plugin `discourse-calendar`. Přidá podporu pro značku `[calendar][/calendar]` v prvním příspěvku tématu."
+ discourse_post_event_enabled: "Aktivuje funkce událostí. Poznámka: Také je třeba povolit `kalendář povolen`."
+ displayed_invitees_limit: "Omezuje, kolik pozvaných se u události zobrazí."
+ display_post_event_date_on_topic_title: "Zobrazí datum události za názvem tématu."
+ use_local_event_date: "Za názvem tématu použít místo relativního času místní datum."
+ discourse_post_event_allowed_on_groups: "Skupiny, které smí vytvářet události."
+ discourse_post_event_allowed_custom_fields: "Umožňuje každé události nastavit hodnotu vlastních polí."
+ discourse_post_event_edit_notifications_time_extension: "Prodlouží (v minutách) dobu po skončení události, kdy jsou účastníci, kteří se `zúčastní` stále upozorňováni na úpravy v původním příspěvku."
+ holiday_calendar_topic_id: "Identifikátor tématu kalendáře pro dovolenou nebo nepřítomnost."
+ holiday_status_emoji: Definuje emotikon použitý pro stav dovolené.
+ delete_expired_event_posts_after: "Příspěvky s uplynulými událostmi se automaticky smažou za (n) hodin. Nastavte na -1, aby se mazání vypnulo."
+ all_day_event_start_time: "Události, které nemají určený čas začátku, začnou v tento čas. Formát je HH:mm. Pro 6:00 zadejte 06:00"
+ all_day_event_end_time: "Události, které nemají určený čas konce, skončí v tento čas. Formát je HH:mm. Pro 18:00 zadejte 18:00"
+ all_day_event_time_error: "Neplatný čas. Formát musí být HH:mm (např. 08:00)."
+ calendar_categories: "Zobrazit kalendář nahoře kategorie. Povinné volby jsou `categoryId` a `postId`. Např.: `categoryId=6;postId=453`\n Další platné volby: `tzPicker`, `weekends` a `defaultView`."
+ calendar_categories_outlet: "Umožňuje změnit, ve které zásuvce se zobrazí kalendář kategorie."
+ working_days: "Nastavení pracovních dnů. Dostupnost skupiny můžete zobrazit pomocí značky `timezones` v příspěvku, např.: `[timezones group=admins][timezones]`"
+ working_day_start_hour: "Čas, kdy začíná pracovní doba."
+ working_day_end_hour: "Čas, kdy končí pracovní doba."
+ close_to_working_day_hours_extension: "Set extension time in working day hours to highlight the timezones."
+ events_calendar_categories: "Zobrazit kalendář událostí v horní části kategorie."
+ sort_categories_by_event_start_date_enabled: "Povolit řazení témat kategorií podle data zahájení události."
+ disable_resorting_on_categories_enabled: "Povolit kategoriím zakázat možnost uživatelů řadit podle kategorie událostí."
+ calendar_automatic_holidays_enabled: "Automaticky nastavit stav dovolené na základě regionu uživatele (poznámka: konkrétní automatické svátky můžete zakázat v nastavení pluginu)"
+ event_participation_buttons: "Seznam tlačítek účasti na události, která mohou uživatelé použít."
+ sidebar_show_upcoming_events: "Zobrazit odkaz na nadcházející události na postranním panelu pod položkou 'Více'."
+ include_expired_events_on_calendar: "Zahrnout minulé/vypršené události do zobrazení Kalendář kategorií a Nadcházející události."
+ discourse_calendar:
+ invite_user_notification: "%{username} vás zve do kalendáře: %{description}"
+ calendar_must_be_in_first_post: "Značka pro kalendář se může vložit jen do prvního příspěvku tématu."
+ more_than_one_calendar: "V jednom příspěvku nemůže být více než jeden kalendář."
+ more_than_two_dates: "Příspěvek pod tématem kalendáře nemůže obsahovat více než dvě data."
+ event_expired: "Událost uplynula"
+ holiday_status:
+ description: "Na dovolené"
+ discourse_post_event:
+ notifications:
+ before_event_reminder: "%{title} brzy začne."
+ after_event_reminder: "%{title} skončila."
+ ongoing_event_reminder: "%{title} probíhá."
+ errors:
+ bulk_invite:
+ max_invitees: "Bylo vytvořeno prvních %{max_invittes} pozvánek. Zkuste soubor rozdělit na menší části."
+ error: "Nastala chyba při nahrávání souboru. Prosím opakujte akci později."
+ models:
+ event:
+ only_one_event: "Příspěvek může obsahovat jen jednu událost."
+ must_be_in_first_post: "Událost může být jen v prvním příspěvku tématu."
+ raw_invitees_length: "Událost má limit %{count} uživatelů nebo skupin."
+ raw_invitees:
+ only_group: "Událost přijímá pouze názvy skupin."
+ ends_at_before_starts_at: "Událost nemůže končit dříve, než začne."
+ start_must_be_present_and_a_valid_date: "Událost vyžaduje platné datum začátku."
+ end_must_be_a_valid_date: "Datum konce musí být platné datum."
+ invalid_recurrence: "Opakování musí být jedno z následujících: every_month, every_week, every_two_weeks, every_four_weeks, every_day, every_weekday."
+ invalid_timezone: "Časové pásmo nebylo rozpoznáno."
+ acting_user_not_allowed_to_create_event: "Současný uživatel nemůže vytvářet události."
+ acting_user_not_allowed_to_act_on_this_event: "Současný uživatel nemůže reagovat na tuto událost."
+ invalid_allowed_groups: "Neplatné povolené skupiny."
+ acting_user_not_allowed_to_invite_these_groups: "Aktuální uživatel nemá povoleno zvát tyto skupiny."
+ custom_field_is_invalid: "Vlastní pole `%{field}` není povoleno."
+ name:
+ length: "Název události musí být dlouhý %{minimum} až %{maximum} znaků."
+ discourse_push_notifications:
+ popup:
+ event_reminder: "Připomenutí události"
diff --git a/plugins/discourse-calendar/config/locales/server.da.yml b/plugins/discourse-calendar/config/locales/server.da.yml
new file mode 100644
index 00000000000..50392724bb4
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/server.da.yml
@@ -0,0 +1,75 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+da:
+ reports:
+ currently_away:
+ labels:
+ username: Brugernavn
+ system_messages:
+ discourse_post_event_bulk_invite_succeeded:
+ title: "Begivenhed - masseinvitation lykkedes"
+ subject_template: "Masseinvitationen blev behandlet"
+ text_body_template: "Din masseinvitationsfil blev behandlet, %{processed} inviterede oprettet."
+ discourse_post_event_bulk_invite_failed:
+ title: "Begivenhed - Masseinvitation mislykkedes"
+ subject_template: "Masseinvitation behandlet med fejl"
+ text_body_template: |
+ Din masseinvitationsfil blev behandlet, %{processed} inviterede oprettet med %{failed} fejl.
+
+ Her er loggen:
+
+ ````text
+ %{logs}
+ ````
+ site_settings:
+ calendar_enabled: "Aktiver discourse-kalendar udvidelse. Dette vil tilføje understøttelse af et [calendar][/calendar] i det første indlæg af et emne."
+ displayed_invitees_limit: "Begrænser antallet af inviterede, der vises på en begivenhed."
+ display_post_event_date_on_topic_title: "Viser datoen for begivenheden efter emnets titel."
+ discourse_post_event_allowed_on_groups: "Grupper, der har lov til at oprette begivenheder."
+ discourse_post_event_allowed_custom_fields: "Tillader, at hver begivenhed kan indstille værdien af brugerdefinerede felter."
+ discourse_post_event_edit_notifications_time_extension: "Forlænger (i minutter) perioden efter afslutningen af en begivenhed, hvor \"deltagende\" inviterede stadig underrettes om redigering i det oprindelige indlæg."
+ holiday_calendar_topic_id: "Emne ID for personalets ferie / fraværs kalender."
+ delete_expired_event_posts_after: "Indlæg med udløbne begivenheder slettes automatisk efter (n) timer. Sæt til -1 for at deaktivere sletning."
+ all_day_event_start_time: "Begivenheder, der ikke har et angivet starttidspunkt, starter på dette tidspunkt. Format er HH: mm. For 6:00 am, indtast 06:00"
+ all_day_event_end_time: "Begivenheder, der ikke har en bestemt sluttid, slutter på dette tidspunkt. Format er HH: mm. Kl. 18:00 skal du indtaste 18:00"
+ all_day_event_time_error: "Ugyldig tid. Formatet skal være HH:mm (eks: 08:00)."
+ calendar_categories: "Vis en kalender øverst i en kategori. Obligatoriske indstillinger er kategoriId og postId. fx: categoryId=6;postId=453\n Andre gyldige indstillinger: tzPicker, weekends og defaultView."
+ calendar_categories_outlet: "Giver mulighed for at ændre, hvilken tilslutning der skal vise kategorikalenderen."
+ working_days: "Angiv arbejdsdage. Du kan vise tilgængeligheden af en gruppe ved hjælp af 'tidszoner'-mærket i et indlæg, fx: '[timezones group=admins][timezones]`"
+ working_day_start_hour: "Starttidspunkt for arbejdsdagstiderne."
+ working_day_end_hour: "Sluttidspunkt for arbejdsdagstiderne."
+ close_to_working_day_hours_extension: "Indstil udvidelsestid i arbejdstimer for at fremhæve tidszonerne."
+ discourse_calendar:
+ invite_user_notification: "%{username} inviterede dig til: %{description}"
+ calendar_must_be_in_first_post: "Kalender-tag kan kun bruges i første opslag af et emne."
+ more_than_one_calendar: "Du kan ikke have mere end en kalender i et indlæg."
+ more_than_two_dates: "Et indlæg i et kalenderemne kan ikke indeholde mere end to datoer."
+ event_expired: "Begivenhed udløbet"
+ discourse_post_event:
+ notifications:
+ before_event_reminder: "%{title} er ved at starte."
+ after_event_reminder: "%{title} er slut."
+ ongoing_event_reminder: "%{title} er i gang."
+ errors:
+ bulk_invite:
+ max_invitees: "De første %{max_invittes} inviterede er oprettet. Prøv at opdele filen i mindre dele."
+ error: "Der opstod en fejl under overførsel af filen. Prøv venligst igen senere."
+ models:
+ event:
+ only_one_event: "Et indlæg kan kun have en begivenhed."
+ must_be_in_first_post: "En begivenhed kan kun være i det første indlæg af et emne."
+ raw_invitees_length: "En begivenhed er begrænset til %{count} brugere/grupper."
+ raw_invitees:
+ only_group: "En begivenhed accepterer kun gruppenavne."
+ ends_at_before_starts_at: "En begivenhed kan ikke slutte, før den starter."
+ start_must_be_present_and_a_valid_date: "En begivenhed kræver en gyldig startdato."
+ end_must_be_a_valid_date: "Slutdato skal være en gyldig dato."
+ acting_user_not_allowed_to_create_event: "Aktuel bruger har ikke lov til at oprette begivenheder."
+ acting_user_not_allowed_to_act_on_this_event: "Nuværende bruger har ikke lov til at handle på denne begivenhed."
+ custom_field_is_invalid: "Det brugerdefinerede felt '%{field}' er ikke tilladt."
+ name:
+ length: "Længden på begivenhedsnavnet skal være mellem %{minimum} og %{maximum} tegn."
diff --git a/plugins/discourse-calendar/config/locales/server.de.yml b/plugins/discourse-calendar/config/locales/server.de.yml
new file mode 100644
index 00000000000..1192e81d2ed
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/server.de.yml
@@ -0,0 +1,104 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+de:
+ reports:
+ currently_away:
+ title: Derzeit abwesende Benutzer
+ labels:
+ username: Benutzername
+ discourse_automation:
+ triggerables:
+ event_started:
+ title: Ereignis hat begonnen
+ system_messages:
+ discourse_calendar_holiday_region_invalid: "Die von dir angegebene Feiertagsregion existiert nicht."
+ discourse_calendar_enable_holiday_failed: "Dieser Feiertag konnte nicht aktiviert werden. Er ist bereits aktiviert oder er ist nicht deaktiviert."
+ discourse_post_event_bulk_invite_succeeded:
+ title: "Ereignis – Masseneinladung erfolgreich"
+ subject_template: "Masseneinladung wurde erfolgreich verarbeitet"
+ text_body_template: "Deine Masseneinladungsdatei wurde verarbeitet, %{processed} eingeladene Person(en) erstellt."
+ discourse_post_event_bulk_invite_failed:
+ title: "Ereignis – Masseneinladung fehlgeschlagen"
+ subject_template: "Bei der Verarbeitung der Masseneinladung sind Fehler aufgetreten"
+ text_body_template: |
+ Deine Masseneinladungsdatei wurde verarbeitet. %{processed} eingeladene Person(en) wurde(n) mit %{failed} Fehler(n) erstellt.
+
+ Hier ist das Protokoll:
+
+ ```Text
+ %{logs}
+ ```
+ site_settings:
+ events_max_rows: "Maximale Textzeilen pro Ereignis im Kalender."
+ map_events_to_color: "Weise jedem Schlagwort oder jeder Kategorie eine Farbe zu."
+ map_events_title: "Überschreibt „Ereignisse“ im Titel der Seitenleiste „Anstehende Ereignisse“ pro Kategorie."
+ calendar_enabled: "Aktiviere das discourse-calendar-Plug-in. Dadurch wird Unterstützung für einen [calendar][/calendar]-Tag im ersten Beitrag eines Themas hinzugefügt."
+ discourse_post_event_enabled: "Aktiviert die Ereignisfunktionen. Hinweis: `calendar enabled` muss ebenfalls aktiviert sein."
+ displayed_invitees_limit: "Begrenzt die Anzahl der eingeladenen Personen, die für ein Ereignis angezeigt werden."
+ display_post_event_date_on_topic_title: "Zeigt das Datum des Ereignisses nach dem Thementitel an."
+ use_local_event_date: "Verwende das lokale Datum nach dem Titel des Themas anstatt der relativen Zeit."
+ discourse_post_event_allowed_on_groups: "Gruppen, die Ereignisse erstellen dürfen."
+ discourse_post_event_allowed_custom_fields: "Ermöglicht es, für jedes Ereignis den Wert von benutzerdefinierten Feldern zu setzen."
+ discourse_post_event_edit_notifications_time_extension: "Verlängert (in Minuten) den Zeitraum nach dem Ende eines Ereignisses, in dem eingeladene Personen (`going`) noch von der Bearbeitung im ursprünglichen Beitrag benachrichtigt werden."
+ holiday_calendar_topic_id: "Themen-ID des Urlaubs-/Abwesenheitskalenders des Teams."
+ holiday_status_emoji: Legt das Emoji fest, das für den Urlaubsstatus verwendet wird.
+ delete_expired_event_posts_after: "Beiträge mit abgelaufenen Ereignissen werden nach (n) Stunden automatisch gelöscht. Setze den Wert auf -1, um die Löschung zu deaktivieren."
+ all_day_event_start_time: "Ereignisse, für die keine Startzeit angegeben ist, beginnen zu dieser Zeit. Das Format lautet HH:mm. Gib „06:00“ für 6 Uhr morgens ein"
+ all_day_event_end_time: "Ereignisse, für die keine Endzeit angegeben ist, enden zu dieser Zeit. Das Format lautet HH:mm. Gib „18:00“ für 6 Uhr abends ein"
+ all_day_event_time_error: "Ungültige Zeit. Format muss HH:mm sein (Beispiel: 08:00)."
+ calendar_categories: "Zeigt einen Kalender oben in einer Kategorie an. Erforderliche Einstellungen sind categoryId und postId. Zum Beispiel: categoryId=6;postId=453\n Andere gültige Einstellungen: tzPicker, weekends und defaultView."
+ calendar_categories_outlet: "Hier kannst du einstellen, welches Outlet den Kategoriekalender anzeigen soll."
+ working_days: "Lege Arbeitstage fest. Du kannst die Verfügbarkeit einer Gruppe mit dem `timezones`-Tag in einem Beitrag anzeigen, z. B.: `[timezones group=admins][timezones]`"
+ working_day_start_hour: "Startzeit des Arbeitstages."
+ working_day_end_hour: "Endzeit des Arbeitstages."
+ close_to_working_day_hours_extension: "Stelle die Verlängerungszeit in Arbeitstagsstunden ein, um die Zeitzonen hervorzuheben."
+ events_calendar_categories: "Zeige einen Ereigniskalender oben in einer Kategorie an."
+ sort_categories_by_event_start_date_enabled: "Aktiviere die Sortierung der Kategoriethemen nach dem Startdatum des Ereignisses."
+ disable_resorting_on_categories_enabled: "Erlaube Kategorien, die Möglichkeit der Sortierung nach Ereigniskategorie für Benutzer zu deaktivieren."
+ calendar_automatic_holidays_enabled: "Feiertagsstatus automatisch basierend auf der Region eines Benutzers festlegen (Hinweis: Du kannst bestimmte automatische Feiertage in den Plug-in-Einstellungen deaktivieren)"
+ event_participation_buttons: "Liste der Schaltflächen für die Teilnahme am Ereignis, die Benutzer verwenden können."
+ sidebar_show_upcoming_events: "Zeige den Link für anstehende Ereignisse in der Seitenleiste unter „Mehr“."
+ include_expired_events_on_calendar: "Schließe vergangene/abgelaufene Ereignisse in die Ansichten „Kategoriekalender“ und „Anstehende Ereignisse“ ein."
+ discourse_calendar:
+ invite_user_notification: "%{username} lädt dich ein zu: %{description}"
+ calendar_must_be_in_first_post: "Kalender-Tag kann nur im ersten Beitrag eines Themas verwendet werden."
+ more_than_one_calendar: "Ein Beitrag kann nicht mehr als einen Kalender enthalten."
+ more_than_two_dates: "Ein Beitrag zu einem Kalenderthema kann nicht mehr als zwei Daten enthalten."
+ event_expired: "Ereignis abgelaufen"
+ holiday_status:
+ description: "Im Urlaub"
+ discourse_post_event:
+ notifications:
+ before_event_reminder: "%{title} fängt gleich an."
+ after_event_reminder: "%{title} ist beendet."
+ ongoing_event_reminder: "%{title} ist im Gange."
+ errors:
+ bulk_invite:
+ max_invitees: "Die ersten %{max_invittes} eingeladenen Personen wurden erstellt. Versuche, die Datei in kleinere Teile aufzuteilen."
+ error: "Beim Hochladen dieser Datei ist ein Fehler aufgetreten. Bitte versuche es später noch einmal."
+ models:
+ event:
+ only_one_event: "Ein Beitrag kann nur ein Ereignis haben."
+ must_be_in_first_post: "Ein Ereignis kann nur im ersten Beitrag eines Themas enthalten sein."
+ raw_invitees_length: "Ein Ereignis ist auf %{count} Benutzer/Gruppen beschränkt."
+ raw_invitees:
+ only_group: "Ein Ereignis akzeptiert nur Gruppennamen."
+ ends_at_before_starts_at: "Ein Ereignis kann nicht enden, bevor es beginnt."
+ start_must_be_present_and_a_valid_date: "Ein Ereignis erfordert ein gültiges Startdatum."
+ end_must_be_a_valid_date: "Das Enddatum muss ein gültiges Datum sein."
+ invalid_recurrence: "Die Wiederholung muss einer der folgenden Werte sein: every_month, every_week, every_two_weeks, every_four_weeks, every_day, every_weekday."
+ invalid_timezone: "Zeitzone nicht erkannt."
+ acting_user_not_allowed_to_create_event: "Der aktuelle Benutzer darf keine Ereignisse erstellen."
+ acting_user_not_allowed_to_act_on_this_event: "Der aktuelle Benutzer darf nicht auf dieses Ereignis reagieren."
+ invalid_allowed_groups: "Ungültige erlaubte Gruppen."
+ acting_user_not_allowed_to_invite_these_groups: "Der aktuelle Benutzer darf diese Gruppen nicht einladen."
+ custom_field_is_invalid: "Das benutzerdefinierte Feld `%{field}` ist nicht zulässig."
+ name:
+ length: "Die Länge des Ereignisnamens muss zwischen %{minimum} und %{maximum} Zeichen liegen."
+ discourse_push_notifications:
+ popup:
+ event_reminder: "Event-Erinnerung"
diff --git a/plugins/discourse-calendar/config/locales/server.el.yml b/plugins/discourse-calendar/config/locales/server.el.yml
new file mode 100644
index 00000000000..38b8d80b549
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/server.el.yml
@@ -0,0 +1,15 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+el:
+ reports:
+ currently_away:
+ labels:
+ username: Όνομα Χρήστη
+ discourse_post_event:
+ errors:
+ bulk_invite:
+ error: "Παρουσίαστηκε ένα σφάλμα κατά το ανέβασμα του αρχείου σας, Παρακαλώ δοκιμάστε αργότερα."
diff --git a/plugins/discourse-calendar/config/locales/server.en.yml b/plugins/discourse-calendar/config/locales/server.en.yml
new file mode 100644
index 00000000000..fe5592ad8fa
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/server.en.yml
@@ -0,0 +1,100 @@
+en:
+ reports:
+ currently_away:
+ title: Users currently away
+ labels:
+ username: Username
+
+ discourse_automation:
+ triggerables:
+ event_started:
+ title: Event started
+ system_messages:
+ discourse_calendar_holiday_region_invalid: "The holiday region you provided does not exist."
+ discourse_calendar_enable_holiday_failed: "This holiday could not be enabled, it's already enabled or it's not disabled."
+ discourse_post_event_bulk_invite_succeeded:
+ title: "Event - Bulk Invite Succeeded"
+ subject_template: "Bulk invite processed successfully"
+ text_body_template: "Your bulk invite file was processed, %{processed} invitee(s) created."
+ discourse_post_event_bulk_invite_failed:
+ title: "Event - Bulk Invite Failed"
+ subject_template: "Bulk invite processed with errors"
+ text_body_template: |
+ Your bulk invite file was processed, %{processed} invitee(s) created with %{failed} error(s).
+
+ Here's the log:
+
+ ```text
+ %{logs}
+ ```
+
+ site_settings:
+ events_max_rows: "Maximum text rows per event in the Calendar."
+ map_events_to_color: "Assign a color to each tag or category."
+ map_events_title: "Overwrites 'Events' in the 'Upcoming Events' sidebar title per category."
+ calendar_enabled: "Enable the discourse-calendar plugin. This will add support for a [calendar][/calendar] tag in the first post of a topic."
+ discourse_post_event_enabled: "Enables the Event features. Note: also needs `calendar enabled` to be enabled."
+ displayed_invitees_limit: "Limits the numbers of invitees displayed on an event."
+ display_post_event_date_on_topic_title: "Displays the date of the event after the topic title."
+ use_local_event_date: "Use local date after topic title instead of relative time."
+ discourse_post_event_allowed_on_groups: "Groups that are allowed to create events."
+ discourse_post_event_allowed_custom_fields: "Allows to let each event to set the value of custom fields."
+ discourse_post_event_edit_notifications_time_extension: "Extends (in minutes) the period after the end of an event when `going` invitees are still being notified from edit in the original post."
+ holiday_calendar_topic_id: "Topic ID of staffs holiday / absence calendar."
+ holiday_status_emoji: Defines the emoji used for the holiday status.
+ delete_expired_event_posts_after: "Posts with expired events will be automatically deleted after (n) hours. Set to -1 to disable deletion."
+ all_day_event_start_time: "Events that do not have a start time specified will start at this time. Format is HH:mm. For 6:00 am, enter 06:00"
+ all_day_event_end_time: "Events that do not have a end time specified will end at this time. Format is HH:mm. For 6:00 pm, enter 18:00"
+ all_day_event_time_error: "Invalid time. Format needs to be HH:mm (ex: 08:00)."
+ calendar_categories: "Display a calendar at the top of a category. Mandatory settings are categoryId and postId. eg: categoryId=6;postId=453\n Other valid settings: tzPicker, weekends and defaultView."
+ calendar_categories_outlet: "Allows to change which outlet should show the category calendar."
+ working_days: "Set working days. You can display the availability of a group using the `timezones` tag in a post, eg: `[timezones group=admins][timezones]`"
+ working_day_start_hour: "Start time of the working day hours."
+ working_day_end_hour: "End time of the working day hours."
+ close_to_working_day_hours_extension: "Set extension time in working day hours to highlight the timezones."
+ events_calendar_categories: "Display an events calendar at the top of a category."
+ sort_categories_by_event_start_date_enabled: "Enable the sorting of category topics by event start date."
+ disable_resorting_on_categories_enabled: "Allow categories to disable the ability for users to sort on the event category."
+ calendar_automatic_holidays_enabled: "Automatically set holiday status based on a users region (note: you can disable specific automatic holidays in plugin settings)"
+ event_participation_buttons: "List of event participation buttons users can use."
+ sidebar_show_upcoming_events: "Show upcoming events link in the sidebar under 'More'."
+ include_expired_events_on_calendar: "Include past/expired events on Category Calendar and Upcoming Events views."
+ discourse_calendar:
+ invite_user_notification: "%{username} invited you to: %{description}"
+ calendar_must_be_in_first_post: "Calendar tag can only be used in first post of a topic."
+ more_than_one_calendar: "You can’t have more than one calendar in a post."
+ more_than_two_dates: "A post of a calendar topic can’t contain more than two dates."
+ event_expired: "Event expired"
+ holiday_status:
+ description: "On holiday"
+ discourse_post_event:
+ notifications:
+ before_event_reminder: "%{title} is about to start."
+ after_event_reminder: "%{title} has ended."
+ ongoing_event_reminder: "%{title} is ongoing."
+ errors:
+ bulk_invite:
+ max_invitees: "First %{max_invittes} invitees have been created. Try splitting the file in smaller parts."
+ error: "There was an error uploading that file. Please try again later."
+ models:
+ event:
+ only_one_event: "A post can only have one event."
+ must_be_in_first_post: "An event can only be in the first post of a topic."
+ raw_invitees_length: "An event is limited to %{count} users/groups."
+ raw_invitees:
+ only_group: "An event accepts only group names."
+ ends_at_before_starts_at: "An event can't end before it starts."
+ start_must_be_present_and_a_valid_date: "An event requires a valid start date."
+ end_must_be_a_valid_date: "End date must be a valid date."
+ invalid_recurrence: "Recurrence must be one of: every_month, every_week, every_two_weeks, every_four_weeks, every_day, every_weekday."
+ invalid_timezone: "Timezone not recognized."
+ acting_user_not_allowed_to_create_event: "Current user is not allowed to create events."
+ acting_user_not_allowed_to_act_on_this_event: "Current user is not allowed to act on this event."
+ invalid_allowed_groups: "Invalid allowed groups."
+ acting_user_not_allowed_to_invite_these_groups: "Current user is not allowed to invite these groups."
+ custom_field_is_invalid: "The custom field `%{field}` is not allowed."
+ name:
+ length: "Event name length must be between %{minimum} and %{maximum} characters."
+ discourse_push_notifications:
+ popup:
+ event_reminder: "Event Reminder"
diff --git a/plugins/discourse-calendar/config/locales/server.en_GB.yml b/plugins/discourse-calendar/config/locales/server.en_GB.yml
new file mode 100644
index 00000000000..2d4fa180ec7
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/server.en_GB.yml
@@ -0,0 +1,7 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+en_GB:
diff --git a/plugins/discourse-calendar/config/locales/server.es.yml b/plugins/discourse-calendar/config/locales/server.es.yml
new file mode 100644
index 00000000000..2ab39535c5f
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/server.es.yml
@@ -0,0 +1,104 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+es:
+ reports:
+ currently_away:
+ title: Usuarios actualmente ausentes
+ labels:
+ username: Nombre de usuario
+ discourse_automation:
+ triggerables:
+ event_started:
+ title: Evento iniciado
+ system_messages:
+ discourse_calendar_holiday_region_invalid: "La región de vacaciones que has proporcionado no existe."
+ discourse_calendar_enable_holiday_failed: "Este día festivo no se pudo habilitar, ya está habilitado o no está deshabilitado."
+ discourse_post_event_bulk_invite_succeeded:
+ title: "Eventos - Invitación masiva realizada con éxito"
+ subject_template: "Invitación masiva procesada con éxito"
+ text_body_template: "Se ha procesado tu archivo de invitación masiva, %{processed} invitado(s) creado(s)."
+ discourse_post_event_bulk_invite_failed:
+ title: "Evento: error en la invitación masiva"
+ subject_template: "Invitación masiva procesada con errores"
+ text_body_template: |
+ Se ha procesado tu archivo de invitación masiva, %{processed} invitado(s) creado(s) con %{failed} error(es).
+
+ Aquí está el registro:
+
+ ```text
+ %{logs}
+ ```
+ site_settings:
+ events_max_rows: "Máximo de filas de texto por evento en el Calendario."
+ map_events_to_color: "Asigna un color a cada etiqueta o categoría."
+ map_events_title: "Reemplaza «Eventos» en el título de la barra lateral «Próximos eventos» por categoría."
+ calendar_enabled: "Habilitar el complemento de calendario de Discourse. Esto añadirá soporte para una etiqueta [calendar][/calendar] en la primera entrada de un tema."
+ discourse_post_event_enabled: "Activa las funciones de Eventos. Nota: también es necesario que esté «activado el calendario» para activarlo."
+ displayed_invitees_limit: "Limita el número de invitados que se muestran en un evento."
+ display_post_event_date_on_topic_title: "Muestra la fecha del evento después del título del tema."
+ use_local_event_date: "Utiliza la fecha local después del título del tema en lugar de la hora relativa."
+ discourse_post_event_allowed_on_groups: "Grupos a los que se les permite crear eventos."
+ discourse_post_event_allowed_custom_fields: "Permite dejar que cada evento establezca el valor de los campos personalizados."
+ discourse_post_event_edit_notifications_time_extension: "Amplía (en minutos) el período después del final de un evento en el que los invitados «asistentes» aún reciben notificaciones de edición en la publicación original."
+ holiday_calendar_topic_id: "ID del tema del calendario de vacaciones / ausencia del personal."
+ holiday_status_emoji: Define el emoji utilizado para el estado de vacaciones.
+ delete_expired_event_posts_after: "Las publicaciones con eventos caducados se eliminarán automáticamente después de (n) horas. Establecer en -1 para deshabilitar la eliminación."
+ all_day_event_start_time: "Los eventos que no tienen una hora de inicio especificada comenzarán a esta hora. El formato es HH:mm. Para las 6:00 am, introduce 06:00"
+ all_day_event_end_time: "Los eventos que no tienen una hora de finalización especificada terminarán a esta hora. El formato es HH:mm. Para las 6:00 pm, introduce 18:00"
+ all_day_event_time_error: "Hora no válida. El formato debe ser HH:mm (por ejemplo: 08:00)."
+ calendar_categories: "Muestra un calendario en la parte superior de una categoría. Las configuraciones obligatorias son categoryId y postId. por ejemplo: categoryId = 6; postId = 453\n Otras configuraciones válidas: tzPicker, fines de semana y defaultView."
+ calendar_categories_outlet: "Permite cambiar qué salida debe mostrar el calendario de categorías."
+ working_days: "Establecer días laborables. Puede mostrar la disponibilidad de un grupo usando la etiqueta `timezones` en una publicación, por ejemplo: `[timezones group = admins][timezones]`"
+ working_day_start_hour: "Hora de inicio de la jornada laboral."
+ working_day_end_hour: "Hora de finalización de la jornada laboral."
+ close_to_working_day_hours_extension: "Establezca el tiempo de extensión en horas del día laborable para resaltar las zonas horarias."
+ events_calendar_categories: "Mostrar un calendario de eventos en la parte superior de una categoría."
+ sort_categories_by_event_start_date_enabled: "Habilitar la clasificación de los temas de las categorías por fecha de inicio del evento."
+ disable_resorting_on_categories_enabled: "Permitir que las categorías deshabiliten la posibilidad de que los usuarios clasifiquen en la categoría del evento."
+ calendar_automatic_holidays_enabled: "Establecer automáticamente el estado de vacaciones en función de la región del usuario (nota: puedes desactivar las vacaciones automáticas específicas en la configuración del plugin)"
+ event_participation_buttons: "Lista de botones de participación en eventos que los usuarios pueden utilizar."
+ sidebar_show_upcoming_events: "Muestra el enlace de los próximos eventos en la barra lateral en «Más»."
+ include_expired_events_on_calendar: "Incluir eventos pasados/vencidos en las vistas Calendario de categorías y Próximos eventos."
+ discourse_calendar:
+ invite_user_notification: "%{username} te invitó a: %{description}"
+ calendar_must_be_in_first_post: "La etiqueta de calendario solo se puede utilizar en la primera publicación de un tema."
+ more_than_one_calendar: "No puedes tener más de un calendario en una publicación."
+ more_than_two_dates: "Una publicación de un tema de calendario no puede contener más de dos fechas."
+ event_expired: "Evento caducado"
+ holiday_status:
+ description: "En día festivo"
+ discourse_post_event:
+ notifications:
+ before_event_reminder: "%{title} está a punto de comenzar."
+ after_event_reminder: "%{title} ha terminado."
+ ongoing_event_reminder: "%{title} está en curso."
+ errors:
+ bulk_invite:
+ max_invitees: "Las primeras %{max_invittes} invitaciones se han enviado. Intenta dividir el archivo en partes más pequeñas."
+ error: "Se produjo un error al subir este archivo. Inténtalo de nuevo más tarde."
+ models:
+ event:
+ only_one_event: "Una publicación solo puede tener un evento."
+ must_be_in_first_post: "Un evento solo puede estar en la primera publicación de un tema."
+ raw_invitees_length: "Un evento está limitado a %{count} usuarios / grupos."
+ raw_invitees:
+ only_group: "Un evento acepta solo nombres de grupos."
+ ends_at_before_starts_at: "Un evento no puede terminar antes de que comience."
+ start_must_be_present_and_a_valid_date: "Un evento requiere una fecha de inicio válida."
+ end_must_be_a_valid_date: "La fecha de finalización debe ser una fecha válida."
+ invalid_recurrence: "La recurrencia debe ser una de las siguientes: cada mes, cada semana, cada dos semanas, cada cuatro semanas, cada día, cada día de la semana."
+ invalid_timezone: "Zona horaria no reconocida."
+ acting_user_not_allowed_to_create_event: "El usuario actual no tiene permiso para crear eventos."
+ acting_user_not_allowed_to_act_on_this_event: "El usuario actual no puede actuar en este evento."
+ invalid_allowed_groups: "Grupos permitidos no válidos."
+ acting_user_not_allowed_to_invite_these_groups: "El usuario actual no puede invitar a estos grupos."
+ custom_field_is_invalid: "El campo personalizado «%{field}» no está permitido."
+ name:
+ length: "La longitud del nombre del evento debe tener entre %{minimum} y %{maximum} caracteres."
+ discourse_push_notifications:
+ popup:
+ event_reminder: "Recordatorio de evento"
diff --git a/plugins/discourse-calendar/config/locales/server.et.yml b/plugins/discourse-calendar/config/locales/server.et.yml
new file mode 100644
index 00000000000..a6d18632004
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/server.et.yml
@@ -0,0 +1,15 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+et:
+ reports:
+ currently_away:
+ labels:
+ username: Kasutajanimi
+ discourse_post_event:
+ errors:
+ bulk_invite:
+ error: "Selle faili üleslaadimisel tekkis viga. Palun proovi hiljem uuesti."
diff --git a/plugins/discourse-calendar/config/locales/server.fa_IR.yml b/plugins/discourse-calendar/config/locales/server.fa_IR.yml
new file mode 100644
index 00000000000..c06caa54652
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/server.fa_IR.yml
@@ -0,0 +1,31 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+fa_IR:
+ reports:
+ currently_away:
+ labels:
+ username: نامکاربری
+ discourse_automation:
+ triggerables:
+ event_started:
+ title: رویداد شروع شد
+ site_settings:
+ events_calendar_categories: "نمایش رویدادهای تقویم در بالای دستهبندی"
+ sidebar_show_upcoming_events: "پیوند رویدادهای آینده را در نوار کناری زیر «بیشتر» نمایش دهید."
+ discourse_calendar:
+ invite_user_notification: "%{username} شما را دعوت کرده به: %{description}"
+ holiday_status:
+ description: "در تعطیلات"
+ discourse_post_event:
+ errors:
+ bulk_invite:
+ error: "خطایی هنگام آپلود فایل مربوطه رخ داده است. لطفا بعدا امتحان کنید."
+ models:
+ event:
+ invalid_timezone: "منطقهزمانی شناسایی نشد."
+ invalid_allowed_groups: "گروههای مجاز نامعتبر."
+ acting_user_not_allowed_to_invite_these_groups: "کاربر فعلی اجازه دعوت از این گروهها را ندارد."
diff --git a/plugins/discourse-calendar/config/locales/server.fi.yml b/plugins/discourse-calendar/config/locales/server.fi.yml
new file mode 100644
index 00000000000..3646cb2f669
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/server.fi.yml
@@ -0,0 +1,104 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+fi:
+ reports:
+ currently_away:
+ title: Tällä hetkellä poissa olevat käyttäjät
+ labels:
+ username: Käyttäjätunnus
+ discourse_automation:
+ triggerables:
+ event_started:
+ title: Tapahtuma alkoi
+ system_messages:
+ discourse_calendar_holiday_region_invalid: "Antamaasi juhlapyhäaluetta ei ole olemassa."
+ discourse_calendar_enable_holiday_failed: "Tätä juhlapyhää ei voitu ottaa käyttöön; se on jo käytössä tai sitä ei ole poistettu käytöstä."
+ discourse_post_event_bulk_invite_succeeded:
+ title: "Tapahtuma – joukkokutsu onnistui"
+ subject_template: "Joukkokutsun käsittely onnistui"
+ text_body_template: "Joukkokutsutiedostosi on käsitelty, %{processed} kutsuttua luotiin lähetetty."
+ discourse_post_event_bulk_invite_failed:
+ title: "Tapahtuma – joukkokutsu epäonnistui"
+ subject_template: "Joukkokutsun käsittelyssä tapahtui virhe"
+ text_body_template: |
+ Joukkokutsutiedostosi käsiteltiin, %{processed} kutsuttua luotiin %{failed} virheellä.
+
+ Tässä on loki:
+
+ ```text
+ %{logs}
+ ```
+ site_settings:
+ events_max_rows: "Tekstirivien enimmäismäärä tapahtumaa kohden kalenterissa."
+ map_events_to_color: "Määritä jokaiselle tunnisteelle tai alueelle väri."
+ map_events_title: "Korvaa \"Tapahtumat\" \"Tulevat tapahtumat\" -sivupalkin otsikossa aluekohtaisesti."
+ calendar_enabled: "Ota discourse-calendar-lisäosa käyttöön. Tämä lisää tuen [calendar][/calendar]-tunnisteelle ketjun ensimmäisessä viestissä."
+ discourse_post_event_enabled: "Ottaa tapahtumaominaisuudet käyttöön. Huomautus: edellyttää myös, että \"calendar enabled\" on käytössä."
+ displayed_invitees_limit: "Rajoittaa tapahtumassa näytettävien kutsuttujen määrää."
+ display_post_event_date_on_topic_title: "Näyttää tapahtuman päivämäärän ketjun otsikon jälkeen."
+ use_local_event_date: "Käytä paikallista päivämäärää ketjun otsikon jälkeen suhteellisen ajan sijaan."
+ discourse_post_event_allowed_on_groups: "Ryhmät, jotka saavat luoda tapahtumia."
+ discourse_post_event_allowed_custom_fields: "Antaa jokaisen tapahtuman määrittää mukautettujen kenttien arvon."
+ discourse_post_event_edit_notifications_time_extension: "Pidentää (minuutteina) tapahtuman päättymisen jälkeistä aikaa, jolloin \"menossa\" olevat kutsutut saavat edelleen ilmoituksen alkuperäisen viestin muokkauksesta."
+ holiday_calendar_topic_id: "Henkilökunnan loma-/poissaolokalenterin ketjun tunnus."
+ holiday_status_emoji: Määrittää lomatilassa käytettävän emojin.
+ delete_expired_event_posts_after: "Viestit, joissa on vanhentuneita tapahtumia, poistetaan automaattisesti (n) tunnin kuluttua. Poista poisto käytöstä asettamalla arvoksi -1."
+ all_day_event_start_time: "Tapahtumat, joille ei ole määritetty alkamisaikaa, alkavat tähän aikaan. Muoto on tt:mm. Esim. kirjoita klo 6.00 muodossa 06:00."
+ all_day_event_end_time: "Tapahtumat, joille ei ole määritetty loppumisaikaa, päättyvät tähän aikaan. Muoto on tt:mm. Esim. kirjoita klo 18.00 muodossa 18:00."
+ all_day_event_time_error: "Virheellinen aika. Muodon täytyy olla tt:mm (esim. 08:00)."
+ calendar_categories: "Näytä kalenteri alueen yläosassa. Pakolliset asetukset ovat categoryId ja postId. esim.: categoryId=6;postId=453\n Muut kelvolliset asetukset: tzPicker, weekends ja defaultView."
+ calendar_categories_outlet: "Mahdollistaa sen muuttamisen, missä julkaisukohdassa alueen kalenteri näytetään."
+ working_days: "Aseta työpäivät. Voit näyttää ryhmän saatavuuden käyttämällä viestissä `timezones`-tunnistetta, esim.: `[timezones group=admins][timezones]`"
+ working_day_start_hour: "Työajan alkamisaika."
+ working_day_end_hour: "Työajan päättymisaika."
+ close_to_working_day_hours_extension: "Aseta pidennysaika työaikana aikavyöhykkeiden korostamiseksi."
+ events_calendar_categories: "Näytä tapahtumakalenteri alueen yläosassa."
+ sort_categories_by_event_start_date_enabled: "Ota käyttöön alueen ketjujen lajittelu tapahtuman alkamispäivän mukaan."
+ disable_resorting_on_categories_enabled: "Salli alueiden poistaa käytöstä käyttäjien mahdollisuus lajitella tapahtuman luokan mukaan."
+ calendar_automatic_holidays_enabled: "Aseta lomatila automaattisesti käyttäjän alueen mukaan (huomaa: voit poistaa tietyt automaattiset lomat käytöstä lisäosan asetuksissa)"
+ event_participation_buttons: "Luettelo tapahtuman osallistumispainikkeista, joita käyttäjät voivat käyttää."
+ sidebar_show_upcoming_events: "Näytä tulevat tapahtumat -linkki sivupalkissa Lisää-kohdan alla."
+ include_expired_events_on_calendar: "Sisällytä menneet/vanhentuneet tapahtumat Alueen kalenteri- ja Tulevat tapahtumat -näkymiin."
+ discourse_calendar:
+ invite_user_notification: "%{username} kutsui sinut: %{description}"
+ calendar_must_be_in_first_post: "Kalenteritunnistetta voi käyttää vain ketjun ensimmäisessä viestissä."
+ more_than_one_calendar: "Yhdessä viestissä voi olla vain yksi kalenteri."
+ more_than_two_dates: "Kalenteriketjun viesti voi sisältää enintään kaksi päivämäärää."
+ event_expired: "Tapahtuma on vanhentunut"
+ holiday_status:
+ description: "Lomalla"
+ discourse_post_event:
+ notifications:
+ before_event_reminder: "%{title} on alkamassa."
+ after_event_reminder: "%{title} on päättynyt."
+ ongoing_event_reminder: "%{title} on käynnissä."
+ errors:
+ bulk_invite:
+ max_invitees: "Ensimmäiset %{max_invittes} kutsuttua on luotu. Kokeile jakaa tiedosto pienempiin osiin."
+ error: "Tiedoston lataus epäonnistui. Yritä myöhemmin uudelleen."
+ models:
+ event:
+ only_one_event: "Viestissä voi olla vain yksi tapahtuma."
+ must_be_in_first_post: "Tapahtuma voi olla vain ketjun ensimmäisessä viestissä."
+ raw_invitees_length: "Tapahtuma on rajoitettu %{count} käyttäjään/ryhmään."
+ raw_invitees:
+ only_group: "Tapahtuma hyväksyy vain ryhmien nimiä."
+ ends_at_before_starts_at: "Tapahtuma ei voi päättyä ennen kuin se alkaa."
+ start_must_be_present_and_a_valid_date: "Tapahtuma vaatii kelvollisen alkamispäivän."
+ end_must_be_a_valid_date: "Päättymispäivän täytyy olla kelvollinen päivämäärä."
+ invalid_recurrence: "Toistuvuuden on oltava jokin seuraavista: every_month, every_week, every_two_weeks, every_four_weeks, every_day, every_weekday."
+ invalid_timezone: "Aikavyöhykettä ei tunnisteta."
+ acting_user_not_allowed_to_create_event: "Nykyinen käyttäjä ei saa luoda tapahtumia."
+ acting_user_not_allowed_to_act_on_this_event: "Nykyinen käyttäjä ei saa tehdä toimia tässä tapahtumassa."
+ invalid_allowed_groups: "Virheelliset sallitut ryhmät."
+ acting_user_not_allowed_to_invite_these_groups: "Nykyinen käyttäjä ei saa kutsua näitä ryhmiä."
+ custom_field_is_invalid: "Mukautettua kenttää %{field} ei sallita."
+ name:
+ length: "Tapahtuman nimen täytyy olla %{minimum}–%{maximum} merkin pituinen."
+ discourse_push_notifications:
+ popup:
+ event_reminder: "Tapahtumamuistutus"
diff --git a/plugins/discourse-calendar/config/locales/server.fr.yml b/plugins/discourse-calendar/config/locales/server.fr.yml
new file mode 100644
index 00000000000..8d83892155c
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/server.fr.yml
@@ -0,0 +1,104 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+fr:
+ reports:
+ currently_away:
+ title: Utilisateurs actuellement absents
+ labels:
+ username: Nom d'utilisateur
+ discourse_automation:
+ triggerables:
+ event_started:
+ title: L'événement a commencé
+ system_messages:
+ discourse_calendar_holiday_region_invalid: "La région de vacances que vous avez indiquée n'existe pas."
+ discourse_calendar_enable_holiday_failed: "Ce jour férié n'a pas pu être activé, il est déjà activé ou il n'est pas désactivé."
+ discourse_post_event_bulk_invite_succeeded:
+ title: "Événement - Invitation groupée réussie"
+ subject_template: "Envoi des invitations groupées réussi"
+ text_body_template: "Votre envoi d'invitations groupées a été effectué. %{processed} invitation(s) envoyée(s)."
+ discourse_post_event_bulk_invite_failed:
+ title: "Événement - Échec de l'invitation groupée"
+ subject_template: "Invitation groupée exécutée avec des erreurs"
+ text_body_template: |
+ Votre envoi d'invitations groupées a été effectué. %{processed} invitation(s) ont été envoyée(s) avec %{failed} erreur(s).
+
+ Voici le détail :
+
+ ```texte
+ %{logs}
+ ```
+ site_settings:
+ events_max_rows: "Nombre maximal de lignes de texte par événement dans le calendrier."
+ map_events_to_color: "Assigner une couleur à chaque étiquette ou catégorie."
+ map_events_title: "Remplace « Événements » dans le titre de la barre latérale « Événements à venir » par catégorie."
+ calendar_enabled: "Activer l'extension discourse-calendar. Ceci ajoutera la prise en charge de l'étiquette [calendar][/calendar] dans le premier message d'un sujet."
+ discourse_post_event_enabled: "Active les fonctionnalités d'événement. Remarque : il faut également que « calendrier activé » soit activé."
+ displayed_invitees_limit: "Limite le nombre d'invités affichés sur un événement."
+ display_post_event_date_on_topic_title: "Affiche la date de l'événement après le titre du sujet."
+ use_local_event_date: "Utiliser la date locale après le titre du sujet au lieu de l'heure relative."
+ discourse_post_event_allowed_on_groups: "Groupes autorisés à créer des événements."
+ discourse_post_event_allowed_custom_fields: "Permet à chaque événement de définir la valeur des champs personnalisés."
+ discourse_post_event_edit_notifications_time_extension: "Prolonge (en minutes) la période suivant la fin d'un événement pendant laquelle les invités « participants » sont toujours notifiés de la modification du message original."
+ holiday_calendar_topic_id: "ID du sujet du calendrier des vacances/absences du personnel."
+ holiday_status_emoji: Définit l’émoji utilisé pour le statut de vacances.
+ delete_expired_event_posts_after: "Les messages comprenant des événements expirés seront automatiquement supprimés après (n) heures. Fixez cette valeur sur -1 pour désactiver la suppression."
+ all_day_event_start_time: "Les événements qui n'ont pas d'heure de début spécifiée commenceront à cette heure. Le format est HH:mm. Pour indiquer 6:00 heures, saisissez 06:00"
+ all_day_event_end_time: "Les événements qui n'ont pas d'heure de fin spécifiée se termineront à cette heure. Le format est HH:mm. Pour indiquer 18:00 heures, saisissez 18:00"
+ all_day_event_time_error: "Heure invalide. Le format doit être HH:mm (p. ex. : 08:00)."
+ calendar_categories: "Affiche un calendrier en haut d'une catégorie. Les paramètres obligatoires sont categoryId et postId. P. ex. : categoryId=6;postId=453\n Autres paramètres valides : tzPicker, weekends et defaultView."
+ calendar_categories_outlet: "Permet de changer quelle sortie doit afficher le calendrier de la catégorie."
+ working_days: "Définissez les jours ouvrés. Vous pouvez afficher la disponibilité d'un groupe en utilisant l'étiquette « timezones » dans un message. P. ex. : « [timezones group=admins][timezones] »"
+ working_day_start_hour: "Heure de début des heures de travail."
+ working_day_end_hour: "Heure de fin des heures de travail."
+ close_to_working_day_hours_extension: "Définir le temps d'extension dans les heures de travail pour mettre en évidence les fuseaux horaires."
+ events_calendar_categories: "Afficher un calendrier des événements en haut d'une catégorie."
+ sort_categories_by_event_start_date_enabled: "Activer le tri des rubriques de la catégorie par date de début de l'événement."
+ disable_resorting_on_categories_enabled: "Autoriser les catégories pour désactiver la possibilité pour les utilisateurs de trier la catégorie d'événement."
+ calendar_automatic_holidays_enabled: "Définir automatiquement le statut des vacances en fonction de la région de l'utilisateur (note : il est possible de désactiver des vacances automatiques spécifiques dans les paramètres de l'extension)"
+ event_participation_buttons: "Liste des boutons de participation aux événements que les utilisateurs peuvent utiliser."
+ sidebar_show_upcoming_events: "Afficher le lien des événements à venir dans la barre latérale sous « Plus »."
+ include_expired_events_on_calendar: "Inclure les événements passés ou expirés dans le calendrier des catégories et les vues des événements à venir."
+ discourse_calendar:
+ invite_user_notification: "%{username} vous a invité(e) à rejoindre : %{description}"
+ calendar_must_be_in_first_post: "L'étiquette du calendrier ne peut être utilisée que dans le premier message d'un sujet."
+ more_than_one_calendar: "Vous ne pouvez pas avoir plus d'un calendrier dans un message."
+ more_than_two_dates: "Un message d'un sujet du calendrier ne peut pas contenir plus de deux dates."
+ event_expired: "Événement expiré"
+ holiday_status:
+ description: "En vacances"
+ discourse_post_event:
+ notifications:
+ before_event_reminder: "L'événement %{title} est sur le point de commencer."
+ after_event_reminder: "L'événement %{title} est terminé."
+ ongoing_event_reminder: "L'événement %{title} est en cours."
+ errors:
+ bulk_invite:
+ max_invitees: "Les %{max_invittes} premières invitations ont été créées. Essayez de diviser le fichier en plus petites parties."
+ error: "Une erreur s'est produite lors du téléversement du fichier. Veuillez réessayer plus tard."
+ models:
+ event:
+ only_one_event: "Un message ne peut contenir qu'un seul événement."
+ must_be_in_first_post: "Un événement ne peut figurer que dans le premier message d'un sujet."
+ raw_invitees_length: "Un événement est limité à %{count} utilisateurs/groupes."
+ raw_invitees:
+ only_group: "Un événement ne peut accepter que les noms de groupes."
+ ends_at_before_starts_at: "Un événement ne peut pas se terminer avant d'avoir commencé."
+ start_must_be_present_and_a_valid_date: "Un événement nécessite une date de début valide."
+ end_must_be_a_valid_date: "La date de fin doit être une date valide."
+ invalid_recurrence: "La récurrence doit être l'une des suivantes : every_month, every_week, every_two_weeks, every_four_weeks, every_day, every_weekday."
+ invalid_timezone: "Fuseau horaire non reconnu."
+ acting_user_not_allowed_to_create_event: "L'utilisateur actuel n'est pas autorisé à créer des événements."
+ acting_user_not_allowed_to_act_on_this_event: "L'utilisateur actuel n'est pas autorisé à agir sur cet événement."
+ invalid_allowed_groups: "Groupes autorisés non valides."
+ acting_user_not_allowed_to_invite_these_groups: "L'utilisateur actuel n'est pas autorisé à inviter ces groupes."
+ custom_field_is_invalid: "Le champ personnalisé « %{field} » n'est pas autorisé."
+ name:
+ length: "Le nom de l'événement doit contenir entre %{minimum} et %{maximum} caractères."
+ discourse_push_notifications:
+ popup:
+ event_reminder: "Rappel d'événement"
diff --git a/plugins/discourse-calendar/config/locales/server.gl.yml b/plugins/discourse-calendar/config/locales/server.gl.yml
new file mode 100644
index 00000000000..863b2526c4e
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/server.gl.yml
@@ -0,0 +1,15 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+gl:
+ reports:
+ currently_away:
+ labels:
+ username: Nome de usuario
+ discourse_post_event:
+ errors:
+ bulk_invite:
+ error: "Houbo un erro ao cargar o ficheiro. Ténteo máis tarde."
diff --git a/plugins/discourse-calendar/config/locales/server.he.yml b/plugins/discourse-calendar/config/locales/server.he.yml
new file mode 100644
index 00000000000..b7cc74d5254
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/server.he.yml
@@ -0,0 +1,104 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+he:
+ reports:
+ currently_away:
+ title: משתמשים שאינם נמצאים כרגע
+ labels:
+ username: שם משתמש
+ discourse_automation:
+ triggerables:
+ event_started:
+ title: האירוע התחיל
+ system_messages:
+ discourse_calendar_holiday_region_invalid: "אזור החג שסיפקת לא קיים."
+ discourse_calendar_enable_holiday_failed: "לא ניתן היה לאפשר את החג הזה, הוא כבר מאופשר או שהוא לא מושבת."
+ discourse_post_event_bulk_invite_succeeded:
+ title: "אירוע - הזמנה קבוצתית הצליחה"
+ subject_template: "ההזמנה המרוכזת עובדה בהצלחה"
+ text_body_template: "קובץ ההזמנה המרוכזת שלך עובד, נוצרו %{processed} מוזמנים."
+ discourse_post_event_bulk_invite_failed:
+ title: "אירוע - הזמנה מרוכזת נכשלה"
+ subject_template: "ההזמנה המרוכזת עובדה עם שגיאות"
+ text_body_template: |
+ קובץ ההזמנה המרוכזת שלך עבר עיבוד, %{processed} המוזמנים נוצרו על אף %{failed} השגיאות שארעו:
+
+ הנה התיעוד:
+
+ ```text
+ %{logs}
+ ```
+ site_settings:
+ events_max_rows: "כמות שורות טקסט מרבית לאירוע בלוח השנה."
+ map_events_to_color: "הקצאת צבע לכל תגית או קטגוריה."
+ map_events_title: "דריסת ‚אירועים’ בכותרת סרגל הצד ה‚אירועים הקרובים’ לפי קטגוריה."
+ calendar_enabled: "נא להפעיל את התוסף discourse-calendar. כך תתווסף תמיכה בתגית [calendar][/calendar] בפוסט ראשון בנושא."
+ discourse_post_event_enabled: "הפעלת יכולות האירועים. לתשומת ליבך: דורש גם את הפעלת `לוח שנה פעיל`."
+ displayed_invitees_limit: "מגביל את מספר המוזמנים שמוצגים לאירוע."
+ display_post_event_date_on_topic_title: "מציג את תאריך האירוע אחרי כותרת הנושא."
+ use_local_event_date: "להשתמש בתאריך המקומי אחרי כותרת הנושא במקום בזמן יחסי."
+ discourse_post_event_allowed_on_groups: "קבוצות שמורשות ליצור אירועים."
+ discourse_post_event_allowed_custom_fields: "מאפשר לכל אירוע להגדיר את ערכי השדות המותאמים אישית לעצמו."
+ discourse_post_event_edit_notifications_time_extension: "מאריך (בדקות) את הזמן שלאחר סוף אירוע כאשר מוזמנים ש‚הולכים’ עדיין מקבלים התראות על עריכה בפרסום המקורי"
+ holiday_calendar_topic_id: "מזהה נושא של לוח שנה עם חגי / היעדרות הסגל."
+ holiday_status_emoji: מגדיר את האמוג׳י המשמש לסימון חופשה.
+ delete_expired_event_posts_after: "פוסטים עם אירועים שתוקפם פג יימחקו אוטומטית לאחר (n) שעות. יש להגדיר ל־-1 כדי להשבית מחיקה."
+ all_day_event_start_time: "אירועים שלא צוינה להם שעת התחלה יתחילו בשעה הזאת. התבנית היא HH:mm. ל־6:00 בבוקר יש להקליד 06:00"
+ all_day_event_end_time: "אירועים שלא צוינה להם שעת סיום יסתיימו בשעה הזאת. התבנית היא HH:mm. ל־6:00 בערב יש להקליד 18:00"
+ all_day_event_time_error: "שעה שגויה. התבנית צריכה להיות HH:mm (למשל: 08:00)."
+ calendar_categories: "הצג לוח שנה בראש קטגוריה. הגדרות חובה הן categoryId ו־postId. למשל: categoryId=6;postId=453\n הגדרות תקפות נוספות: tzPicker, weekends ו־defaultView."
+ calendar_categories_outlet: "מאפשר לך לשנות איזה מתאם תצוגה יציג את לוח השנה של הקטגוריה."
+ working_days: "הגדרת ימי עבודה. ניתן להציג את זמינות הקבוצה באמצעות תגית `timezones` (אזורי זמן) בפוסט, למשל: `[timezones group=admins][timezones]`"
+ working_day_start_hour: "שעת תחילת יום העבודה."
+ working_day_end_hour: "שעת סיום יום העבודה."
+ close_to_working_day_hours_extension: "הגדרת זמן הארכה בשעות כדי להדגיש את אזורי הזמן."
+ events_calendar_categories: "הצגת לוח אירועים בראש קטגוריה."
+ sort_categories_by_event_start_date_enabled: "לאפשר מיון של נושאי קטגוריות לפי תאריך תחילת האירוע."
+ disable_resorting_on_categories_enabled: "לאפשר לקטגוריות להשבית את היכולת של משתמשים למיין לפי קטגוריית האירוע."
+ calendar_automatic_holidays_enabled: "להגדיר מצב חופשה לפי האזור של המשתמש (לתשומת לבך: ניתן להשבית חגים אוטומטיים מסוימים בהגדרות התוסף)"
+ event_participation_buttons: "רשימת כפתורי השתתפות באירועים לשימוש המשתמשים."
+ sidebar_show_upcoming_events: "הצגת קישור לאירועים קרובים בסרגל הצד תחת ‚עוד’."
+ include_expired_events_on_calendar: "לכלול אירועי קודמים/שפג תוקפם בתצוגות לוח השנה של הקטגוריה ובאירועים קרבים."
+ discourse_calendar:
+ invite_user_notification: "קיבלת הזמנה מאת %{username} אל: %{description}"
+ calendar_must_be_in_first_post: "ניתן להשתמש בתגית יומן רק בפוסט הראשון של נושא."
+ more_than_one_calendar: "לא יכול להיות יותר מלוח שנה אחד בפוסט."
+ more_than_two_dates: "פוסט של נושא יומן לא יכול להכיל יותר משני תאריכים."
+ event_expired: "תוקף האירוע פג"
+ holiday_status:
+ description: "בחג"
+ discourse_post_event:
+ notifications:
+ before_event_reminder: "%{title} עומד להתחיל."
+ after_event_reminder: "%{title} הסתיים."
+ ongoing_event_reminder: "%{title} מתרחש כרגע."
+ errors:
+ bulk_invite:
+ max_invitees: "%{max_invittes} המוזמנים הראשונים נוצרו. עדיף לנסות לפצל את הקובץ לחלקים קטנים יותר."
+ error: "אירעה שגיאה בשליחת הקובץ. נא לנסות שוב מאוחר יותר."
+ models:
+ event:
+ only_one_event: "לפוסט יכול להיות רק אירוע אחד."
+ must_be_in_first_post: "אירוע יכול להיות רק בפוסט הראשון של נושא."
+ raw_invitees_length: "אירוע מוגבל ל־%{count} משתמשים/קבוצות."
+ raw_invitees:
+ only_group: "אירוע מקבל רק שמות קבוצות."
+ ends_at_before_starts_at: "אירוע לא יכול להסתיים לפני שהתחיל."
+ start_must_be_present_and_a_valid_date: "לאירוע נחוץ תאריך התחלה תקף."
+ end_must_be_a_valid_date: "תאריך הסיום חייב להיות תאריך תקף."
+ invalid_recurrence: "חזרה חייבת להיות אחת מבין: every_month, every_week, every_two_weeks, every_four_weeks, every_day, every_weekday."
+ invalid_timezone: "אזור הזמן לא מזוהה."
+ acting_user_not_allowed_to_create_event: "למשתמש הנוכחי אסור ליצור אירועים."
+ acting_user_not_allowed_to_act_on_this_event: "אסור למשתמש הנוכחי לפעול כנגד האירוע הזה."
+ invalid_allowed_groups: "קבוצות מותרות שגויות."
+ acting_user_not_allowed_to_invite_these_groups: "למשתמש הנוכחי אסור להזמין את הקבוצות האלו."
+ custom_field_is_invalid: "השדה המותאם אישית `%{field}` אסור לשימוש."
+ name:
+ length: "אורך שם האירוע חייב להיות בין %{minimum} ל־%{maximum} תווים."
+ discourse_push_notifications:
+ popup:
+ event_reminder: "תזכורת לאירוע"
diff --git a/plugins/discourse-calendar/config/locales/server.hr.yml b/plugins/discourse-calendar/config/locales/server.hr.yml
new file mode 100644
index 00000000000..f1aba5679d1
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/server.hr.yml
@@ -0,0 +1,15 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+hr:
+ reports:
+ currently_away:
+ labels:
+ username: Korisničko ime
+ discourse_post_event:
+ errors:
+ bulk_invite:
+ error: "Dogodila se greška pri učitavanju te datoteke. Molimo kasnije pokušajte ponovno."
diff --git a/plugins/discourse-calendar/config/locales/server.hu.yml b/plugins/discourse-calendar/config/locales/server.hu.yml
new file mode 100644
index 00000000000..9651b182d01
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/server.hu.yml
@@ -0,0 +1,65 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+hu:
+ reports:
+ currently_away:
+ title: Jelenleg távol lévő felhasználók
+ labels:
+ username: Felhasználónév
+ discourse_automation:
+ triggerables:
+ event_started:
+ title: Az esemény elkezdődött
+ system_messages:
+ discourse_post_event_bulk_invite_succeeded:
+ title: "Esemény – A csoportos meghívás sikeres"
+ subject_template: "A csoportos meghívás feldolgozása sikeres"
+ text_body_template: "A csoportos meghívás feldolgozása sikeres, %{processed} meghívott létrehozva."
+ discourse_post_event_bulk_invite_failed:
+ title: "Esemény – A csoportos meghívás sikertelen"
+ subject_template: "Hiba a csoportos meghívás feldolgozása során"
+ text_body_template: |
+ A csoportos meghívási fájl feldolgozása megtörtént, %{processed} meghívott jött létre %{failed} hibával.
+
+ Ez a napló:
+
+ ```text
+ %{logs}
+ ```
+ site_settings:
+ working_day_start_hour: "A munkanapok kezdési időpontja."
+ working_day_end_hour: "A munkanapok befejezési időpontja."
+ discourse_calendar:
+ invite_user_notification: "%{username} meghívta erre: %{description}"
+ calendar_must_be_in_first_post: "A naptár címke csak a téma első bejegyzésében használható."
+ more_than_one_calendar: "Egy bejegyzésben nem lehet több naptár."
+ more_than_two_dates: "Egy naptár téma bejegyzése nem tartalmazhat kettőnél több dátumot."
+ event_expired: "Az esemény lejárt"
+ discourse_post_event:
+ notifications:
+ before_event_reminder: "A(z) %{title} mindjárt kezdődik."
+ after_event_reminder: "A(z) %{title} véget ért."
+ ongoing_event_reminder: "A(z) %{title} folyamatban van."
+ errors:
+ bulk_invite:
+ max_invitees: "Az első %{max_invittes} meghívott lett létrehozva. Próbálja meg a fájlt kisebb részekre osztani."
+ error: "Hiba történt a fájl feltöltésekor. Kérlek, próbáld újra később."
+ models:
+ event:
+ only_one_event: "Egy bejegyzésnek csak egy eseménye lehet."
+ must_be_in_first_post: "Esemény csak a téma első bejegyzésében lehet."
+ raw_invitees_length: "Egy esemény %{count} felhasználóra/csoportra korlátozódik."
+ raw_invitees:
+ only_group: "Egy esemény csak csoportneveket fogad el."
+ ends_at_before_starts_at: "Egy esemény nem érhet véget, mielőtt elkezdődne."
+ start_must_be_present_and_a_valid_date: "Az eseményhez érvényes kezdési dátum szükséges."
+ end_must_be_a_valid_date: "A befejezés dátumának érvényes dátumnak kell lennie."
+ acting_user_not_allowed_to_create_event: "A jelenlegi felhasználó nem hozhat létre eseményeket."
+ acting_user_not_allowed_to_act_on_this_event: "A jelenlegi felhasználó nem tehet semmit ennél az eseménynél."
+ custom_field_is_invalid: "A(z) „%{field}” egyéni mező nem engedélyezett."
+ name:
+ length: "Az eseménynév hosszának %{minimum} és %{maximum} karakter között kell lennie."
diff --git a/plugins/discourse-calendar/config/locales/server.hy.yml b/plugins/discourse-calendar/config/locales/server.hy.yml
new file mode 100644
index 00000000000..317099750be
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/server.hy.yml
@@ -0,0 +1,15 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+hy:
+ reports:
+ currently_away:
+ labels:
+ username: Օգտանուն
+ discourse_post_event:
+ errors:
+ bulk_invite:
+ error: "Այդ ֆայլը վերբեռնելիս տեղի է ունեցել սխալ: Խնդրում ենք փորձել կրկին ավելի ուշ: "
diff --git a/plugins/discourse-calendar/config/locales/server.id.yml b/plugins/discourse-calendar/config/locales/server.id.yml
new file mode 100644
index 00000000000..d023190f090
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/server.id.yml
@@ -0,0 +1,17 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+id:
+ reports:
+ currently_away:
+ labels:
+ username: Nama Pengguna
+ discourse_calendar:
+ invite_user_notification: "%{username} mengundang Anda ke: %{description}"
+ discourse_post_event:
+ errors:
+ bulk_invite:
+ error: "Terjadi kesalahan saat mengunggah file itu. Coba lagi nanti."
diff --git a/plugins/discourse-calendar/config/locales/server.it.yml b/plugins/discourse-calendar/config/locales/server.it.yml
new file mode 100644
index 00000000000..ebccb109511
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/server.it.yml
@@ -0,0 +1,97 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+it:
+ reports:
+ currently_away:
+ title: Utenti attualmente assenti
+ labels:
+ username: Nome utente
+ discourse_automation:
+ triggerables:
+ event_started:
+ title: Evento iniziato
+ system_messages:
+ discourse_calendar_holiday_region_invalid: "La regione che hai fornito per le festività non esiste."
+ discourse_calendar_enable_holiday_failed: "Questa festività non può essere abilitata, è già abilitata o non è disabilitata."
+ discourse_post_event_bulk_invite_succeeded:
+ title: "Evento: Invito collettivo riuscito"
+ subject_template: "Invito collettivo elaborato correttamente"
+ text_body_template: "Il tuo file con l'invito collettivo è stato elaborato, %{processed} inviti creati."
+ discourse_post_event_bulk_invite_failed:
+ title: "Evento: Invito collettivo non riuscito"
+ subject_template: "Invito collettivo elaborato con errori"
+ text_body_template: "Il tuo file di invito collettivo è stato elaborato, %{processed} inviti creati con %{failed} errori. \n\nEcco il log:\n\n``` text\n%{logs}\n```\n"
+ site_settings:
+ events_max_rows: "Numero massimo di righe di testo per evento nel calendario."
+ map_events_to_color: "Assegna un colore a ciascuna etichetta o categoria."
+ map_events_title: "Sovrascrive \"Eventi\" nel titolo della barra laterale \"Prossimi eventi\" per categoria."
+ calendar_enabled: "Abilita il plugin del calendario di discourse. Questo permetterà di supportare un tag [calendar][/calendar] nel primo messaggio di un argomento."
+ discourse_post_event_enabled: "Abilita le funzionalità dell'evento. Nota: è necessario che anche l'impostazione `calendario abilitato` sia abilitata."
+ displayed_invitees_limit: "Limita il numero di invitati visualizzati in un evento."
+ display_post_event_date_on_topic_title: "Visualizza la data dell'evento dopo il titolo dell'argomento."
+ use_local_event_date: "Usa la data locale dopo il titolo dell'argomento invece dell'orario relativo."
+ discourse_post_event_allowed_on_groups: "Gruppi che sono autorizzati a creare eventi."
+ discourse_post_event_allowed_custom_fields: "Consenti a ciascun evento di impostare il valore dei campi personalizzati."
+ discourse_post_event_edit_notifications_time_extension: "Prolunga la durata (in minuti) dopo la fine di un evento in cui gli invitati che \"parteciperanno\" ricevono ancora notifiche delle modifiche al messaggio originale."
+ holiday_calendar_topic_id: "ID argomento del calendario ferie / assenze dello staff."
+ holiday_status_emoji: Definisce l'emoji utilizzato per lo stato vacanza.
+ delete_expired_event_posts_after: "I messaggi con eventi scaduti verranno automaticamente eliminati dopo (n) ore. Impostare l'opzione a -1 per disabilitare la cancellazione."
+ all_day_event_start_time: "Gli eventi che non hanno un orario iniziale indicato inizieranno in questo orario. Il formato è HH:mm. Per le 6:00, inserire 06:00"
+ all_day_event_end_time: "Gli eventi che non hanno un orario finale indicato termineranno a questo orario. Il formato è HH:mm. Per le 18:00, inserire 18:00"
+ all_day_event_time_error: "Orario non valido. Il formato deve essere HH:mm (Ad es.: 08:00)."
+ calendar_categories: "Visualizza un calendario all'inizio di una categoria. Le impostazioni obbligatorie sono categoryId e postId. Ad es.: categoryId=6;postId=453\n Altre impostazioni valide: tzPicker, weekends e defaultView."
+ calendar_categories_outlet: "Consente di modificare la posizione (outlet) in cui il calendario della categoria.deve essere visualizzato."
+ working_days: "Imposta giorni lavorativi. Puoi visualizzare la disponibilità di un gruppo utilizzando il tag `timezones` in un messaggio, ad esempio: `[timezones group=admins][timezones]`"
+ working_day_start_hour: "Orario iniziale della giornata lavorativa."
+ working_day_end_hour: "Orario finale della giornata lavorativa."
+ close_to_working_day_hours_extension: "Imposta il tempo di estensione nell'orario lavorativo per evidenziare i fusi orari."
+ events_calendar_categories: "Visualizza un calendario degli eventi nella parte superiore di una categoria."
+ sort_categories_by_event_start_date_enabled: "Abilita l'ordinamento degli argomenti di categoria in base alla data di inizio dell'evento."
+ disable_resorting_on_categories_enabled: "Consenti alle categorie di disabilitare la possibilità per gli utenti di ordinare in base alla categoria dell'evento."
+ calendar_automatic_holidays_enabled: "Imposta automaticamente lo stato di vacanza in base all'area geografica degli utenti (nota: puoi disabilitare specifiche festività automatiche nelle impostazioni del plugin)"
+ event_participation_buttons: "Elenco dei pulsanti di partecipazione agli eventi che gli utenti possono utilizzare."
+ sidebar_show_upcoming_events: "Mostra il link ai prossimi eventi nella barra laterale sotto \"Altro\"."
+ include_expired_events_on_calendar: "Includi eventi passati/scaduti nel calendario delle categorie e nelle visualizzazioni dei prossimi eventi."
+ discourse_calendar:
+ invite_user_notification: "%{username} ti ha invitato a: %{description}"
+ calendar_must_be_in_first_post: "Il tag del calendario può essere utilizzato solo nel primo messaggio di un argomento."
+ more_than_one_calendar: "Non puoi avere più di un calendario in un messaggio."
+ more_than_two_dates: "Un messaggio di un argomento del calendario non può contenere più di due date."
+ event_expired: "Evento scaduto"
+ holiday_status:
+ description: "In vacanza"
+ discourse_post_event:
+ notifications:
+ before_event_reminder: "%{title} sta per iniziare."
+ after_event_reminder: "%{title} è terminato."
+ ongoing_event_reminder: "%{title} è in corso."
+ errors:
+ bulk_invite:
+ max_invitees: "I primi %{max_invittes} inviti sono stati creati. Prova a dividere il file in parti più piccole."
+ error: "Si è verificato un errore durante il caricamento del file. Riprova più tardi."
+ models:
+ event:
+ only_one_event: "Un messaggio può avere un solo evento."
+ must_be_in_first_post: "Un evento può trovarsi solo nel primo messaggio di un argomento."
+ raw_invitees_length: "Un evento è limitato a %{count} utenti/gruppi."
+ raw_invitees:
+ only_group: "Un evento accetta solo nomi di gruppi."
+ ends_at_before_starts_at: "Un evento non può finire prima di iniziare."
+ start_must_be_present_and_a_valid_date: "Un evento richiede una data iniziale valida."
+ end_must_be_a_valid_date: "La data finale deve essere una data valida."
+ invalid_recurrence: "La ricorrenza deve essere una tra: every_month, every_week, every_two_weeks, every_four_weeks, every_day, every_weekday."
+ invalid_timezone: "Fuso orario non riconosciuto."
+ acting_user_not_allowed_to_create_event: "L'utente corrente non è autorizzato a creare eventi."
+ acting_user_not_allowed_to_act_on_this_event: "L'utente corrente non è autorizzato ad agire su questo evento."
+ invalid_allowed_groups: "Gruppi consentiti non validi."
+ acting_user_not_allowed_to_invite_these_groups: "L'utente corrente non è autorizzato a invitare questi gruppi."
+ custom_field_is_invalid: "Il campo personalizzato `%{field}` non è consentito."
+ name:
+ length: "La lunghezza del nome dell'evento deve essere compresa tra %{minimum} e %{maximum} caratteri."
+ discourse_push_notifications:
+ popup:
+ event_reminder: "Promemoria eventi"
diff --git a/plugins/discourse-calendar/config/locales/server.ja.yml b/plugins/discourse-calendar/config/locales/server.ja.yml
new file mode 100644
index 00000000000..1efac4fdc07
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/server.ja.yml
@@ -0,0 +1,104 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+ja:
+ reports:
+ currently_away:
+ title: ユーザーは現在退席中
+ labels:
+ username: ユーザー名
+ discourse_automation:
+ triggerables:
+ event_started:
+ title: イベント開始
+ system_messages:
+ discourse_calendar_holiday_region_invalid: "指定された祝祭日の地域は存在しません。"
+ discourse_calendar_enable_holiday_failed: "この祝祭日を有効にできませんでした。すでに有効であるか無効になっていません。"
+ discourse_post_event_bulk_invite_succeeded:
+ title: "イベント - 一括招待に成功しました"
+ subject_template: "一括招待は正常に処理されました"
+ text_body_template: "あなたの一括招待ファイルの処理が完了し、%{processed} 人の招待者が作成されました。"
+ discourse_post_event_bulk_invite_failed:
+ title: "イベント - 一括招待に失敗しました"
+ subject_template: "一括招待の処理にエラーが発生しました"
+ text_body_template: |
+ あなたの一括招待ファイルは処理され、%{processed} 人の招待者の作成に %{failed} 個のエラーが発生しました。
+
+ こちらがログです。
+
+ ```text
+ %{logs}
+ ```
+ site_settings:
+ events_max_rows: "カレンダー内のイベントごとの最大テキスト行数。"
+ map_events_to_color: "各タグまたはカテゴリに色を割り当てる。"
+ map_events_title: "カテゴリごとに '今後のイベント' の 'イベント' を上書きします。"
+ calendar_enabled: "discourse-calendar プラグインを有効にします。これにより、トピックの最初の投稿に [calendar][/calendar] タグのサポートが追加されます。"
+ discourse_post_event_enabled: "イベント機能を有効にします。注意: `calendar enabled` も有効である必要があります。"
+ displayed_invitees_limit: "イベントに表示される招待者数を制限します。"
+ display_post_event_date_on_topic_title: "トピックタイトルの後にイベントの日付を表示します。"
+ use_local_event_date: "トピックタイトルの後に、相対時間ではなく、現地の日付を使用します。"
+ discourse_post_event_allowed_on_groups: "イベントを作成できるグループ。"
+ discourse_post_event_allowed_custom_fields: "各イベントにカスタムフィールドの値を設定できるようにします。"
+ discourse_post_event_edit_notifications_time_extension: "`going` 参加者が元の投稿の編集に関する通知を受け取り続けるイベント終了後の期間を延長(分単位)します。"
+ holiday_calendar_topic_id: "スタッフの休暇/不在カレンダーのトピック ID。"
+ holiday_status_emoji: 休暇ステータスに使用される絵文字を定義します。
+ delete_expired_event_posts_after: "期限切れのイベントのある投稿は、(n)時間後に自動的に削除されます。削除を無効にするには、-1 に設定します。"
+ all_day_event_start_time: "開始時刻が指定されていないイベントはこの時刻に開始します。フォーマットは HH:mm です。午前 6 時の場合は、06:00 と入力します"
+ all_day_event_end_time: "終了時刻が指定されていないイベントはこの時刻に終了します。フォーマットは HH:mm です。午後 6 時の場合は、18:00 と入力します"
+ all_day_event_time_error: "無効な時刻です。フォーマットは HH:mm である必要があります(例: 08:00)。"
+ calendar_categories: "カテゴリの先頭にカレンダーを表示します。categoriId と postId は必須の設定です。例: categoryId=6;postId=453\n 他の有効な設定: tzPicker、weekends、defaultView。"
+ calendar_categories_outlet: "カテゴリカレンダーを表示する場所を変更できるようにします。"
+ working_days: "稼働日を設定します。投稿に `timezones` タグを使用して、グループの空き状況を表示できます。例: `[timezones group=admins][timezones]`"
+ working_day_start_hour: "稼働時間の開始時刻。"
+ working_day_end_hour: "稼働時間の終了時刻。"
+ close_to_working_day_hours_extension: "稼働時間の延長時間を設定して、タイムゾーンを強調表示します。"
+ events_calendar_categories: "カテゴリの上にイベントカレンダーを表示します。"
+ sort_categories_by_event_start_date_enabled: "イベント開始日順によるカテゴリトピックの並べ替えを有効にします。"
+ disable_resorting_on_categories_enabled: "ユーザーがイベントカテゴリで並べ替える機能をカテゴリで無効にできるようにします。"
+ calendar_automatic_holidays_enabled: "ユーザーの地域に基づいて休暇ステータスを自動的に設定します (注意: 特定の自動休暇はプラグインの設定で無効にできます)"
+ event_participation_buttons: "ユーザーが使用できるイベント参加ボタンのリスト。"
+ sidebar_show_upcoming_events: "サイドバーの「もっと」の下に今後のイベントのリンクを表示する。"
+ include_expired_events_on_calendar: "カテゴリカレンダーと今後のイベントビューに過去/期限切れのイベントを含める。"
+ discourse_calendar:
+ invite_user_notification: "%{username} があなたを招待しました: %{description}"
+ calendar_must_be_in_first_post: "Calendar タグは、トピックの最初の投稿でのみ使用できます。"
+ more_than_one_calendar: "投稿に複数のカレンダーを含めることはできません。"
+ more_than_two_dates: "カレンダートピックの投稿には、3 つ以上の日付を含めることはできません。"
+ event_expired: "イベント終了"
+ holiday_status:
+ description: "休暇中"
+ discourse_post_event:
+ notifications:
+ before_event_reminder: "%{title} は間もなく開始します。"
+ after_event_reminder: "%{title} は終了しました。"
+ ongoing_event_reminder: "%{title} は進行中です。"
+ errors:
+ bulk_invite:
+ max_invitees: "最初の %{max_invittes} 人の招待者が作成されました。ファイルをより小さく分割してください。"
+ error: "そのファイルをアップロード中にエラーが発生しました。後でもう一度お試しください。"
+ models:
+ event:
+ only_one_event: "投稿には 1 つのイベントのみを含めることができます。"
+ must_be_in_first_post: "イベントは、トピックの最初の投稿にのみ含めることができます。"
+ raw_invitees_length: "イベントは、%{count} ユーザー/グループに制限されています。"
+ raw_invitees:
+ only_group: "イベントにはグループ名のみを使用できます。"
+ ends_at_before_starts_at: "イベントを開始時刻より前に終了されることはできません。"
+ start_must_be_present_and_a_valid_date: "イベントには有効な開始日が必要です。"
+ end_must_be_a_valid_date: "終了日は有効な日付である必要があります。"
+ invalid_recurrence: "繰り返しは、every_month、every_week、every_two_weeks、every_four_weeks、every_day、every_weekday のいずれかである必要があります。"
+ invalid_timezone: "タイムゾーンが認識されません。"
+ acting_user_not_allowed_to_create_event: "現在のユーザーは、イベントを作成できません。"
+ acting_user_not_allowed_to_act_on_this_event: "現在のユーザーはこのイベントにアクションを実行することはできません。"
+ invalid_allowed_groups: "許可されたグループは無効です。"
+ acting_user_not_allowed_to_invite_these_groups: "現在のユーザーはこれらのグループを招待できません。"
+ custom_field_is_invalid: "カスタムフィールド `%{field}` は許可されていません。"
+ name:
+ length: "イベント名の長さは、%{minimum}~%{maximum} 文字である必要があります。"
+ discourse_push_notifications:
+ popup:
+ event_reminder: "イベントのリマインダー"
diff --git a/plugins/discourse-calendar/config/locales/server.ko.yml b/plugins/discourse-calendar/config/locales/server.ko.yml
new file mode 100644
index 00000000000..e383c9b2614
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/server.ko.yml
@@ -0,0 +1,21 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+ko:
+ reports:
+ currently_away:
+ labels:
+ username: 사용자명
+ discourse_automation:
+ triggerables:
+ event_started:
+ title: 이벤트 시작됨
+ discourse_calendar:
+ invite_user_notification: "%{username}님이 사용자님을 초대했습니다: %{description}"
+ discourse_post_event:
+ errors:
+ bulk_invite:
+ error: "해당 파일을 업로드하는 중에 오류가 발생했습니다. 나중에 다시 시도하십시오."
diff --git a/plugins/discourse-calendar/config/locales/server.lt.yml b/plugins/discourse-calendar/config/locales/server.lt.yml
new file mode 100644
index 00000000000..6d0900ad35f
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/server.lt.yml
@@ -0,0 +1,40 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+lt:
+ reports:
+ currently_away:
+ title: Šiuo metu išvykę naudotojai
+ labels:
+ username: Naudotojo vardas
+ discourse_automation:
+ triggerables:
+ event_started:
+ title: Įvykis prasidėjo
+ system_messages:
+ discourse_calendar_holiday_region_invalid: "Nurodytas švenčių regionas neegzistuoja."
+ discourse_calendar_enable_holiday_failed: "Šios šventės negalėjo būti įjungtos, ji jau įjungta arba ji nėra išjungta."
+ discourse_calendar:
+ holiday_status:
+ description: "Švenčių metu"
+ discourse_post_event:
+ notifications:
+ before_event_reminder: "%{title} netrukus prasidės."
+ after_event_reminder: "%{title} jau baigėsi."
+ ongoing_event_reminder: "%{title} vyksta."
+ errors:
+ bulk_invite:
+ error: "Įkeliant failą įvyko klaida. Pabandykite dar kartą vėliau."
+ models:
+ event:
+ raw_invitees:
+ only_group: "Įvykiui priimami tik grupių pavadinimai."
+ invalid_timezone: "Laiko juosta neatpažinta."
+ acting_user_not_allowed_to_act_on_this_event: "Dabartiniam naudotojui neleidžiama veikti su šiuo įvykiu."
+ invalid_allowed_groups: "Netinkamos leidžiamos grupės."
+ discourse_push_notifications:
+ popup:
+ event_reminder: "Įvykio priminimas"
diff --git a/plugins/discourse-calendar/config/locales/server.lv.yml b/plugins/discourse-calendar/config/locales/server.lv.yml
new file mode 100644
index 00000000000..11135755129
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/server.lv.yml
@@ -0,0 +1,11 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+lv:
+ reports:
+ currently_away:
+ labels:
+ username: Lietotājvārds
diff --git a/plugins/discourse-calendar/config/locales/server.nb_NO.yml b/plugins/discourse-calendar/config/locales/server.nb_NO.yml
new file mode 100644
index 00000000000..30c5ee66098
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/server.nb_NO.yml
@@ -0,0 +1,15 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+nb_NO:
+ reports:
+ currently_away:
+ labels:
+ username: Brukernavn
+ discourse_post_event:
+ errors:
+ bulk_invite:
+ error: "Det skjedde en feil når filen ble lastet opp. Prøv igjen senere. "
diff --git a/plugins/discourse-calendar/config/locales/server.nl.yml b/plugins/discourse-calendar/config/locales/server.nl.yml
new file mode 100644
index 00000000000..cff0ddcca18
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/server.nl.yml
@@ -0,0 +1,104 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+nl:
+ reports:
+ currently_away:
+ title: Gebruikers die momenteel afwezig zijn
+ labels:
+ username: Gebruikersnaam
+ discourse_automation:
+ triggerables:
+ event_started:
+ title: Evenement begonnen
+ system_messages:
+ discourse_calendar_holiday_region_invalid: "De opgegeven feestdagenregio bestaat niet."
+ discourse_calendar_enable_holiday_failed: "Deze feestdag kon niet worden ingeschakeld, is al ingeschakeld of is niet uitgeschakeld."
+ discourse_post_event_bulk_invite_succeeded:
+ title: "Evenement - bulkuitnodiging geslaagd"
+ subject_template: "Bulkuitnodiging verwerkt"
+ text_body_template: "Je bulkuitnodigingsbestand is verwerkt, %{processed} genodigde(n) gemaakt."
+ discourse_post_event_bulk_invite_failed:
+ title: "Evenement - bulkuitnodiging mislukt"
+ subject_template: "Bulkuitnodiging verwerkt met fouten"
+ text_body_template: |
+ Je bulkuitnodigingsbestand is verwerkt, %{processed} genodigde(n) gemaakt met %{failed} fout(en).
+
+ Hier is de log:
+
+ ```text
+ %{logs}
+ ```
+ site_settings:
+ events_max_rows: "Maximaal aantal tekstrijen per evenement in de agenda."
+ map_events_to_color: "Wijs een kleur toe aan elke tag of categorie."
+ map_events_title: "Overschrijft 'Evenementen' in de zijbalktitel 'Aankomende evenementen' per categorie."
+ calendar_enabled: "Schakel de Discourse-Kalender-plug-in in. Dit voegt ondersteuning toe voor een [calendar][/calendar]-tag in het eerste bericht van een topic."
+ discourse_post_event_enabled: "Schakelt de evenementfuncties in. Opmerking: hiervoor moet ook 'calendar enabled' ingeschakeld zijn."
+ displayed_invitees_limit: "Beperkt het aantal genodigden van een evenement."
+ display_post_event_date_on_topic_title: "Geeft de datum van het evenement weer na de topictitel."
+ use_local_event_date: "Gebruik lokale datum na topictitel in plaats van relatieve tijd."
+ discourse_post_event_allowed_on_groups: "Groepen die evenementen mogen maken."
+ discourse_post_event_allowed_custom_fields: "Maakt het mogelijk om de waarde van aangepaste velden in te stellen voor elk evenement."
+ discourse_post_event_edit_notifications_time_extension: "Verlengt (in minuten) de periode na afloop van een evenement waarin 'gaande’ nog bericht krijgen van bewerking in het oorspronkelijke bericht."
+ holiday_calendar_topic_id: "Topic-ID van de feestdagen-/verzuimkalender voor medewerkers."
+ holiday_status_emoji: Definieert de emoji die wordt gebruikt voor de feestdagstatus.
+ delete_expired_event_posts_after: "Berichten met verlopen evenementen worden na (n) uur automatisch verwijderd. Stel dit in op -1 om verwijdering uit te schakelen."
+ all_day_event_start_time: "Evenementen waarvoor geen begintijd is opgegeven, beginnen op deze tijd. De notatie is Hh:mm. Voor 6:00 uur voer je 06:00 in"
+ all_day_event_end_time: "Evenementen waarvoor geen eindtijd is opgegeven, eindigen op deze tijd. De notatie is HH:mm. Voor 18:00 uur voer je 18:00 in"
+ all_day_event_time_error: "Ongeldige tijd. Notatie moet HH:mm zijn (bijvoorbeeld 08:00)."
+ calendar_categories: "Geeft een kalender weer bovenaan een categorie. Verplichte instellingen zijn categoryId en postId, bijvoorbeeld: categoryId=6;postId=453\nAndere geldige instellingen: tzPicker, weekends en defaultView."
+ calendar_categories_outlet: "Hiermee kun je veranderen welke outlet de categoriekalender moet weergeven."
+ working_days: "Stel werkdagen in. Je kunt de beschikbaarheid van een groep weergeven met de 'timezones'-tag in een bericht, bijvoorbeeld: '[timezones group=admins][timezones]'"
+ working_day_start_hour: "Begintijd van werkdag."
+ working_day_end_hour: "Eindtijd van werkdag."
+ close_to_working_day_hours_extension: "Stel de verlengingstijd in uren in om de tijdzones te markeren."
+ events_calendar_categories: "Geef een evenementenkalender weer bovenaan een categorie."
+ sort_categories_by_event_start_date_enabled: "Schakel het sorteren van categorietopics op begindatum van evenement in."
+ disable_resorting_on_categories_enabled: "Sta toe dat categorieën de mogelijkheid voor gebruikers om te sorteren op evenementcategorie uitschakelen."
+ calendar_automatic_holidays_enabled: "Stel automatisch de feestdagstatus in op basis van een gebruikersregio (opmerking: je kunt specifieke automatische feestdagen uitschakelen in de plug-in-instellingen)"
+ event_participation_buttons: "Lijst met knoppen voor evenementdeelname die gebruikers kunnen gebruiken."
+ sidebar_show_upcoming_events: "Geef een link naar aankomende evenementen weer in de zijbalk onder 'Meer'."
+ include_expired_events_on_calendar: "Neem eerdere/afgelopen evenementen op in de weergaven Categoriekalender en Aankomende evenementen."
+ discourse_calendar:
+ invite_user_notification: "%{username} heeft je uitgenodigd voor: %{description}"
+ calendar_must_be_in_first_post: "Calendar-tag kan alleen worden gebruikt in het eerste bericht van een topic."
+ more_than_one_calendar: "Je kunt niet meer dan één kalender hebben in een topic."
+ more_than_two_dates: "Een bericht van een kalendertopic kan niet meer dan twee datums bevatten."
+ event_expired: "Evenement verlopen"
+ holiday_status:
+ description: "Op feestdag"
+ discourse_post_event:
+ notifications:
+ before_event_reminder: "%{title} gaat beginnen."
+ after_event_reminder: "%{title} is afgelopen."
+ ongoing_event_reminder: "%{title} is gaande."
+ errors:
+ bulk_invite:
+ max_invitees: "De eerste %{max_invittes} genodigden zijn gemaakt. Probeer het bestand op te splitsen in kleinere delen."
+ error: "Er is een fout opgetreden bij het uploaden van het bestand. Probeer het later opnieuw."
+ models:
+ event:
+ only_one_event: "Een bericht kan slechts één evenement hebben."
+ must_be_in_first_post: "Een evenement kan alleen in het eerste bericht van een topic staan."
+ raw_invitees_length: "Een evenement is beperkt tot %{count} gebruikers/groepen."
+ raw_invitees:
+ only_group: "Een evenement accepteert alleen groepsnamen."
+ ends_at_before_starts_at: "Een evenement kan niet eindigen voordat het begint."
+ start_must_be_present_and_a_valid_date: "Een evenement vereist een geldige begindatum."
+ end_must_be_a_valid_date: "Einddatum moet een geldige datum zijn."
+ invalid_recurrence: "Herhaling moet een van de volgende zijn: every_month, every_week, every_two_weeks, every_four_weeks, every_day, every_weekday."
+ invalid_timezone: "Tijdzone niet herkend."
+ acting_user_not_allowed_to_create_event: "Huidige gebruiker mag geen evenementen maken."
+ acting_user_not_allowed_to_act_on_this_event: "Huidige gebruiker mag niets doen met dit evenement."
+ invalid_allowed_groups: "Ongeldige toegestane groepen."
+ acting_user_not_allowed_to_invite_these_groups: "Huidige gebruiker mag deze groepen niet uitnodigen."
+ custom_field_is_invalid: "Het aangepaste veld '%{field}' is niet toegestaan."
+ name:
+ length: "Evenementnaam moet %{minimum} tot %{maximum} tekens lang zijn."
+ discourse_push_notifications:
+ popup:
+ event_reminder: "Evenementherinnering"
diff --git a/plugins/discourse-calendar/config/locales/server.pl_PL.yml b/plugins/discourse-calendar/config/locales/server.pl_PL.yml
new file mode 100644
index 00000000000..226602a34dd
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/server.pl_PL.yml
@@ -0,0 +1,80 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+pl_PL:
+ reports:
+ currently_away:
+ title: Użytkownicy aktualnie nieobecni
+ labels:
+ username: Nazwa konta
+ discourse_automation:
+ triggerables:
+ event_started:
+ title: Wydarzenie rozpoczęte
+ system_messages:
+ discourse_calendar_holiday_region_invalid: "Podany przez Ciebie region świąt nie istnieje."
+ discourse_calendar_enable_holiday_failed: "Nie można włączyć tego święta, jest ono już włączone lub nie jest wyłączone."
+ discourse_post_event_bulk_invite_succeeded:
+ title: "Zdarzenie — zaproszenie zbiorcze powiodło się"
+ subject_template: "Zaproszenie zbiorcze zostało pomyślnie przetworzone"
+ text_body_template: "Twój plik z zaproszeniami zbiorczymi został przetworzony, utworzono %{processed} zaproszeń."
+ discourse_post_event_bulk_invite_failed:
+ title: "Zdarzenie — zaproszenie zbiorcze nie powiodło się"
+ subject_template: "Zaproszenie zbiorcze zostało przetworzone z błędami"
+ site_settings:
+ events_max_rows: "Maksymalna liczba wierszy tekstu na wydarzenie w kalendarzu."
+ map_events_to_color: "Przypisz kolor do każdego tagu lub kategorii."
+ displayed_invitees_limit: "Ogranicza liczbę zaproszeń wyświetlanych w wydarzeniu."
+ display_post_event_date_on_topic_title: "Wyświetla datę wydarzenia po tytule tematu."
+ use_local_event_date: "Użyj daty lokalnej po tytule tematu zamiast czasu względnego."
+ discourse_post_event_allowed_on_groups: "Grupy, które mogą tworzyć wydarzenia."
+ discourse_post_event_edit_notifications_time_extension: "Wydłuża (w minutach) okres po zakończeniu wydarzenia, w którym osoby zaproszone są nadal powiadamiane o edycji w oryginalnym poście."
+ holiday_calendar_topic_id: "Identyfikator tematu kalendarza świąt/nieobecności personelu."
+ holiday_status_emoji: Określa emoji używane dla statusu świąt.
+ working_days: "Ustaw dni robocze. Możesz wyświetlić dostępność grupy za pomocą tagu `timezones` w poście, np.: `[timezones group=admins][timezones]`"
+ events_calendar_categories: "Wyświetl kalendarz wydarzeń na górze kategorii."
+ sort_categories_by_event_start_date_enabled: "Włącz sortowanie tematów kategorii według daty rozpoczęcia wydarzenia."
+ calendar_automatic_holidays_enabled: "Automatycznie ustawiaj status świąt na podstawie regionu użytkownika (uwaga: możesz wyłączyć określone automatyczne święta w ustawieniach wtyczki)"
+ sidebar_show_upcoming_events: "Pokaż link do nadchodzących wydarzeń na pasku bocznym w sekcji „Więcej”."
+ include_expired_events_on_calendar: "Uwzględnij minione/wygasłe wydarzenia w widokach kalendarza kategorii i nadchodzących wydarzeń."
+ discourse_calendar:
+ invite_user_notification: "%{username} zaprosił Cię do: %{description}"
+ calendar_must_be_in_first_post: "Tag kalendarza może być użyty tylko w pierwszym poście tematu."
+ more_than_one_calendar: "W poście nie można umieścić więcej niż jednego kalendarza."
+ more_than_two_dates: "Post w temacie kalendarza nie może zawierać więcej niż dwóch dat."
+ event_expired: "Wydarzenie wygasło"
+ holiday_status:
+ description: "Na świętach"
+ discourse_post_event:
+ notifications:
+ before_event_reminder: "%{title} wkrótce się rozpocznie."
+ after_event_reminder: "%{title} dobiegło końca."
+ ongoing_event_reminder: "%{title} trwa."
+ errors:
+ bulk_invite:
+ error: "Wystąpił błąd podczas wgrywania tego pliku. Spróbuj ponownie później."
+ models:
+ event:
+ only_one_event: "Post może mieć tylko jedno wydarzenie."
+ must_be_in_first_post: "Wydarzenie może znajdować się tylko w pierwszym poście tematu."
+ raw_invitees_length: "Wydarzenie jest ograniczone do %{count} użytkowników/grup."
+ raw_invitees:
+ only_group: "Wydarzenie akceptuje tylko nazwy grup."
+ ends_at_before_starts_at: "Wydarzenie nie może zakończyć się zanim się zacznie."
+ start_must_be_present_and_a_valid_date: "Wydarzenie wymaga prawidłowej daty rozpoczęcia."
+ end_must_be_a_valid_date: "Data zakończenia musi być prawidłową datą."
+ invalid_recurrence: "Powtarzalność musi być jedną z następujących wartości: every_month, every_week, every_two_weeks, every_four_weeks, every_day, every_weekday."
+ invalid_timezone: "Strefa czasowa nie została rozpoznana."
+ acting_user_not_allowed_to_create_event: "Bieżący użytkownik nie ma uprawnień do tworzenia wydarzeń."
+ acting_user_not_allowed_to_act_on_this_event: "Obecny użytkownik nie może działać w związku z tym wydarzeniem."
+ invalid_allowed_groups: "Nieprawidłowe dozwolone grupy."
+ acting_user_not_allowed_to_invite_these_groups: "Bieżący użytkownik nie może zapraszać tych grup."
+ custom_field_is_invalid: "Pole niestandardowe `%{field}` jest niedozwolone."
+ name:
+ length: "Długość nazwy wydarzenia musi zawierać się w przedziale od %{minimum} do %{maximum} znaków."
+ discourse_push_notifications:
+ popup:
+ event_reminder: "Przypomnienie o wydarzeniu"
diff --git a/plugins/discourse-calendar/config/locales/server.pt.yml b/plugins/discourse-calendar/config/locales/server.pt.yml
new file mode 100644
index 00000000000..e42b317fe4c
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/server.pt.yml
@@ -0,0 +1,19 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+pt:
+ reports:
+ currently_away:
+ labels:
+ username: Nome de Utilizador
+ discourse_automation:
+ triggerables:
+ event_started:
+ title: Evento iniciado
+ discourse_post_event:
+ errors:
+ bulk_invite:
+ error: "Ocorreu um erro ao carregar esse ficheiro. Por favor tente mais tarde."
diff --git a/plugins/discourse-calendar/config/locales/server.pt_BR.yml b/plugins/discourse-calendar/config/locales/server.pt_BR.yml
new file mode 100644
index 00000000000..822070c181c
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/server.pt_BR.yml
@@ -0,0 +1,104 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+pt_BR:
+ reports:
+ currently_away:
+ title: Usuários ausentes no momento
+ labels:
+ username: Usuário
+ discourse_automation:
+ triggerables:
+ event_started:
+ title: Evento iniciado
+ system_messages:
+ discourse_calendar_holiday_region_invalid: "A região de férias que você forneceu não existe."
+ discourse_calendar_enable_holiday_failed: "Este feriado não pôde ser ativado, já está ativado ou não está desativado."
+ discourse_post_event_bulk_invite_succeeded:
+ title: "Evento - Convites em massa bem-sucedidos"
+ subject_template: "Convites em massa processados"
+ text_body_template: "Seu arquivo de convites em massa para usuários(as) foi processado: %{processed} convites enviados por e-mail."
+ discourse_post_event_bulk_invite_failed:
+ title: "Evento - Convites em massa fracassaram"
+ subject_template: "Seus convites em massa não foram enviados, entre em contato com os(as) moderadores(as)."
+ text_body_template: |
+ Seu arquivo de convites em massa para usuários(as) foi processado, %{processed} convites foram enviados por e-mail com %{failed} erro(s).
+
+ Aqui está o registro:
+
+ ``` texto
+ %{logs}
+ ```
+ site_settings:
+ events_max_rows: "Máximo de linhas de texto por evento no Calendário."
+ map_events_to_color: "Atribua uma cor a cada etiqueta ou categoria."
+ map_events_title: "Substitui \"Eventos\" no título da barra lateral \"Próximos eventos\" por categoria."
+ calendar_enabled: "Ativar o plugin discourse-calendar. Isso adicionará suporte a uma tag [calendar][/calendar] na primeira postagem de um tópico."
+ discourse_post_event_enabled: "Habilita os recursos de Evento. Observação: também é preciso habilitar \"calendário habilidado\""
+ displayed_invitees_limit: "Limita o número de convidados(as) exibidos(as) em um evento."
+ display_post_event_date_on_topic_title: "Exibe a data do evento após o título do tópico."
+ use_local_event_date: "Use a data local após o título do tópico em vez da hora relativa."
+ discourse_post_event_allowed_on_groups: "Grupos que têm permissão para criar eventos."
+ discourse_post_event_allowed_custom_fields: "Permite que cada evento defina o valor dos campos personalizados."
+ discourse_post_event_edit_notifications_time_extension: "Prolonga (em minutos) o período após o final de um evento quando os(as) convidados(as) \"de saída' ainda estão sendo notificados da edição na postagem original."
+ holiday_calendar_topic_id: "ID do tópico do calendário de feriados/ausências da equipe."
+ holiday_status_emoji: Define o emoji usado para o status de feriado.
+ delete_expired_event_posts_after: "As mensagens com eventos vencidos serão automaticamente apagadas após (n) horas. Defina para -1 para desativar a exclusão."
+ all_day_event_start_time: "Os eventos que não têm uma hora de início especificada começarão neste momento. O formato é HH:mm. Para 6:00 da manhã, digite 06:00"
+ all_day_event_end_time: "Os eventos que não têm uma hora de início especificada começarão neste momento. O formato é HH:mm. Para 6:00 da manhã, digite 18:00"
+ all_day_event_time_error: "Hora inválida. O formato precisa ser HH:mm (ex: 08:00)."
+ calendar_categories: "Mostrar um calendário no topo de uma categoria. As configurações obrigatórias são categoryId e postId. eg: categoryId=6;postId=453\n Outras configurações válidas: tzPicker, fins de semana e defaultView."
+ calendar_categories_outlet: "Permite mudar qual saída deve mostrar o calendário da categoria."
+ working_days: "Dias úteis definidos. Você pode exibir a disponibilidade de um grupo utilizando a etiqueta \"timezones\" em uma postagem, por exemplo: `[timezones group=admins][timezones]`"
+ working_day_start_hour: "Hora de início das horas do dia útil."
+ working_day_end_hour: "Hora de término das horas do dia útil."
+ close_to_working_day_hours_extension: "Defina o tempo de extensão nas horas do dia útil para destacar os fusos horários."
+ events_calendar_categories: "Exiba um calendário de eventos no topo de uma categoria."
+ sort_categories_by_event_start_date_enabled: "Habilite a classificação dos tópicos da categoria por data de início do evento."
+ disable_resorting_on_categories_enabled: "Permita que categorias desativem a capacidade dos usuários de classificar na categoria do evento."
+ calendar_automatic_holidays_enabled: "Definir automaticamente o status do feriado conforme a região do(a) usuário(a) (observação: você pode desativar feriados automáticos específicos nas configurações do plugin)"
+ event_participation_buttons: "Lista botões de participação no evento que os(as) usuários(as) podem usar."
+ sidebar_show_upcoming_events: "Mostre o link dos próximos eventos na barra lateral, em \"Mais\"."
+ include_expired_events_on_calendar: "Inclua eventos passados/expirados na categoria Calendário e visualizações dos próximos eventos."
+ discourse_calendar:
+ invite_user_notification: "%{username} convidou para participar de %{description}"
+ calendar_must_be_in_first_post: "A etiqueta do calendário só pode ser usada no primeiro post de um tópico."
+ more_than_one_calendar: "Você não pode ter mais de um calendário em uma postagem."
+ more_than_two_dates: "Uma postagem de um tópico do calendário não pode conter mais de duas datas."
+ event_expired: "Evento expirado"
+ holiday_status:
+ description: "De férias"
+ discourse_post_event:
+ notifications:
+ before_event_reminder: "%{title} está prestes a começar."
+ after_event_reminder: "%{title} terminou."
+ ongoing_event_reminder: "%{title} está em andamento."
+ errors:
+ bulk_invite:
+ max_invitees: "Os primeiros %{max_invittes} convites foram enviados. Tente dividir o arquivo em partes menores."
+ error: "Houve um erro ao tentar enviar este arquivo. Tente novamente mais tarde."
+ models:
+ event:
+ only_one_event: "Uma postagem só pode ter um evento."
+ must_be_in_first_post: "Um evento só pode estar na primeira postagem de um tópico."
+ raw_invitees_length: "Um evento é limitado a %{count} usuários(as)/grupos."
+ raw_invitees:
+ only_group: "Um evento aceita apenas nomes de grupos."
+ ends_at_before_starts_at: "Um evento não pode terminar antes de começar."
+ start_must_be_present_and_a_valid_date: "Um evento requer uma data de início válida."
+ end_must_be_a_valid_date: "A data final deve ser uma data válida."
+ invalid_recurrence: "A recorrência deve ser: every_month, every_week, every_two_weeks, every_four_weeks, every_day, every_weekday."
+ invalid_timezone: "Fuso horário não reconhecido."
+ acting_user_not_allowed_to_create_event: "O(a) usuário(a) atual não tem permissão para criar eventos."
+ acting_user_not_allowed_to_act_on_this_event: "O(a) usuário(a) atual não tem permissão para agir neste evento."
+ invalid_allowed_groups: "Grupos permitidos inválidos."
+ acting_user_not_allowed_to_invite_these_groups: "O(a) usuário(a) atual não tem permissão para convidar estes grupos."
+ custom_field_is_invalid: "O campo personalizado \"%{field}\" não é permitido."
+ name:
+ length: "A duração do nome do evento deve ter entre %{minimum} e %{maximum} caracteres."
+ discourse_push_notifications:
+ popup:
+ event_reminder: "Lembrete de evento"
diff --git a/plugins/discourse-calendar/config/locales/server.ro.yml b/plugins/discourse-calendar/config/locales/server.ro.yml
new file mode 100644
index 00000000000..db88ac3b780
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/server.ro.yml
@@ -0,0 +1,19 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+ro:
+ reports:
+ currently_away:
+ labels:
+ username: Nume de utilizator
+ site_settings:
+ map_events_title: "Suprascrie „Evenimente” în titlul barei laterale „Evenimente viitoare” pe categorie."
+ sidebar_show_upcoming_events: "Afișează legătură evenimente viitoare în bara laterală sub „Mai multe”."
+ include_expired_events_on_calendar: "Include evenimentele trecute sau expirate în vizualizările de calendar pe categorii și evenimente viitoare."
+ discourse_post_event:
+ errors:
+ bulk_invite:
+ error: "A apărut o eroare la încărcarea acestui fișier. Te rugăm să încerci din nou, mai târziu."
diff --git a/plugins/discourse-calendar/config/locales/server.ru.yml b/plugins/discourse-calendar/config/locales/server.ru.yml
new file mode 100644
index 00000000000..a36f3f37637
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/server.ru.yml
@@ -0,0 +1,104 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+ru:
+ reports:
+ currently_away:
+ title: Пользователи отсутствуют
+ labels:
+ username: Имя пользователя
+ discourse_automation:
+ triggerables:
+ event_started:
+ title: Мероприятие началось
+ system_messages:
+ discourse_calendar_holiday_region_invalid: "Указанный вами регион праздничного календаря не существует."
+ discourse_calendar_enable_holiday_failed: "Этот праздник нельзя включить: он уже включён или не был отключен."
+ discourse_post_event_bulk_invite_succeeded:
+ title: "Событие - Массовое приглашение успешно выполнено"
+ subject_template: "Массовое приглашение успешно обработано"
+ text_body_template: "Ваш файл для массового приглашения пользователей был успешно обработан, отправлено приглашений: %{processed}."
+ discourse_post_event_bulk_invite_failed:
+ title: "Событие - Не удалось выполнить массовое приглашение"
+ subject_template: "Массовое приглашение обработано с ошибками"
+ text_body_template: |
+ Ваш файл массового приглашения был обработан, %{processed} приглашенных. Ошибок: %{failed}.
+
+ Журнал приглашений:
+
+ ```text
+ %{logs}
+ ```
+ site_settings:
+ events_max_rows: "Максимальное количество текстовых строк на мероприятие в календаре."
+ map_events_to_color: "Назначать цвет каждому тегу или категории."
+ map_events_title: "Перезаписывает «События» в заголовке боковой панели «Предстоящие события» для каждой категории."
+ calendar_enabled: "Включить плагин discourse-calendar. Это добавит поддержку тега [calendar][/calendar] в первой публикации темы."
+ discourse_post_event_enabled: "Включает функции мероприятий. Примечание: также необходимо, чтобы был включен параметр `calendar enabled`."
+ displayed_invitees_limit: "Ограничивать количество приглашений, отображаемых в мероприятии ."
+ display_post_event_date_on_topic_title: "Отображать дату мероприятия после названия темы."
+ use_local_event_date: "Использовать локальную дату после заголовка темы вместо прошедшего времени."
+ discourse_post_event_allowed_on_groups: "Группы, которым разрешено создавать мероприятия."
+ discourse_post_event_allowed_custom_fields: "Позволять каждому мероприятию устанавливать значение настраиваемых полей."
+ discourse_post_event_edit_notifications_time_extension: "Продлить на указанное здесь количество минут период после окончания мероприятия, когда приглашённые всё ещё получают уведомление об изменении исходной записи."
+ holiday_calendar_topic_id: "Идентификатор темы календаря праздников и отсутствия сотрудников."
+ holiday_status_emoji: Эмодзи для статуса «праздники».
+ delete_expired_event_posts_after: "Сообщения с истекшими мероприятиями будут автоматически удалены после указанного здесь количества часов (n). Установите значение -1 для отключения удаления."
+ all_day_event_start_time: "Мероприятия, для которых не указано время начала, начнутся в указанное здесь время. Формат времени — ЧЧ:мм. Например, для 6:00 утра введите 06:00"
+ all_day_event_end_time: "Мероприятия, для которых не указано время окончания, завершатся в указанное здесь время. Формат времени — ЧЧ:мм. Например, для 6:00 вечера введите 18:00"
+ all_day_event_time_error: "Неверное время. Формат должен быть ЧЧ: мм (например, 08:00)."
+ calendar_categories: "Отображать календарь в верхней части раздела. Обязательные настройки - categoryId и postId. например: categoryId = 6; postId = 453\n Другие допустимые настройки: часовые пояса, выходные дни и формат отображения."
+ calendar_categories_outlet: "Позволяет указать, где отображать календарь категории."
+ working_days: "Установка рабочих дней. Вы можете отобразить доступность группы с помощью тега `timezones`, пометив им сообщение, например:` [timezones group = admins][timezones]`"
+ working_day_start_hour: "Время начала рабочего дня."
+ working_day_end_hour: "Время окончания рабочего дня."
+ close_to_working_day_hours_extension: "Продлевать время рабочего дня с учётом часовых поясов."
+ events_calendar_categories: "Отображать календарь мероприятий в верхней части категории."
+ sort_categories_by_event_start_date_enabled: "Включить сортировку тем категории по дате начала мероприятия."
+ disable_resorting_on_categories_enabled: "Разрешить категориям скрывать возможность сортировать мероприятия по категориям."
+ calendar_automatic_holidays_enabled: "Автоматически задавать статус «праздники» по региону пользователя (отключить конкретные автоматически устанавливаемые праздники можно в настройках плагина)"
+ event_participation_buttons: "Список кнопок участия в мероприятиях, которые могут использовать пользователи."
+ sidebar_show_upcoming_events: "Показывать ссылку на предстоящие мероприятия на боковой панели в разделе «Еще»."
+ include_expired_events_on_calendar: "Включать прошедшие/истекшие мероприятия в представлениях «Календарь категорий» и «Предстоящие мероприятия»."
+ discourse_calendar:
+ invite_user_notification: "Пользователь %{username} приглашает вас: %{description}"
+ calendar_must_be_in_first_post: "Тег календаря может быть использован только в первой публикации темы."
+ more_than_one_calendar: "В записи не может быть более одного календаря."
+ more_than_two_dates: "Запись в теме календаря не может содержать более двух дат."
+ event_expired: "Срок проведения мероприятия истек"
+ holiday_status:
+ description: "Выходные"
+ discourse_post_event:
+ notifications:
+ before_event_reminder: "Мероприятие «%{title}» вот-вот начнется."
+ after_event_reminder: "Мероприятие «%{title}» закончилось."
+ ongoing_event_reminder: "Мероприятие «%{title}» продолжается."
+ errors:
+ bulk_invite:
+ max_invitees: "Были созданы первые приглашения (%{max_invittes}). Попробуйте разделить файл на более мелкие части."
+ error: "Произошла ошибка при загрузке файла. Повторите попытку позже."
+ models:
+ event:
+ only_one_event: "В публикации может быть только одно мероприятие."
+ must_be_in_first_post: "Мероприятие может быть только в первой публикации темы."
+ raw_invitees_length: "Мероприятие ограничено по числу пользователей/групп (%{count})."
+ raw_invitees:
+ only_group: "Мероприятие принимает только имена групп."
+ ends_at_before_starts_at: "Мероприятие не может закончиться до того, как начнется."
+ start_must_be_present_and_a_valid_date: "В мероприятии должна быть установлена правильная дата начала."
+ end_must_be_a_valid_date: "Дата окончания должна быть правильной."
+ invalid_recurrence: "Повторение должно быть одним из следующих значений: every_month, every_week, every_two_weeks, every_four_weeks, every_day, every_weekday."
+ invalid_timezone: "Часовой пояс не распознан."
+ acting_user_not_allowed_to_create_event: "Текущему пользователю не разрешено создавать мероприятия."
+ acting_user_not_allowed_to_act_on_this_event: "Текущий пользователь не имеет права работать с этим мероприятием."
+ invalid_allowed_groups: "Недействительные разрешённые группы."
+ acting_user_not_allowed_to_invite_these_groups: "Текущему пользователю не разрешено приглашать эти группы."
+ custom_field_is_invalid: "Настраиваемое поле `%{field}` не допускается."
+ name:
+ length: "Название мероприятия должно содержать столько символов: от %{minimum} до %{maximum}."
+ discourse_push_notifications:
+ popup:
+ event_reminder: "Напоминание о мероприятии"
diff --git a/plugins/discourse-calendar/config/locales/server.sk.yml b/plugins/discourse-calendar/config/locales/server.sk.yml
new file mode 100644
index 00000000000..6d949bca27a
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/server.sk.yml
@@ -0,0 +1,15 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+sk:
+ reports:
+ currently_away:
+ labels:
+ username: Používateľské meno
+ discourse_post_event:
+ errors:
+ bulk_invite:
+ error: "Počas nahrávanie súboru sa vyskytla chyba. Prosím, vyskúšajte ho nahraď znovu neskôr."
diff --git a/plugins/discourse-calendar/config/locales/server.sl.yml b/plugins/discourse-calendar/config/locales/server.sl.yml
new file mode 100644
index 00000000000..3981c7c9409
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/server.sl.yml
@@ -0,0 +1,74 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+sl:
+ reports:
+ currently_away:
+ labels:
+ username: Uporabniško ime
+ system_messages:
+ discourse_post_event_bulk_invite_succeeded:
+ title: "Dogodek - Skupinsko vabilo je uspelo"
+ subject_template: "Skupinsko vabilo je bilo uspešno obdelano"
+ text_body_template: "Datoteka udeležencev je bila obdelana, število dodanih povabljencev: %{processed}."
+ discourse_post_event_bulk_invite_failed:
+ title: "Dogodek - Skupinsko vabilo ni uspelo"
+ subject_template: "Skupinsko vabilo je bilo obdelano z napakami"
+ text_body_template: |
+ Datoteka udeležencev je bila obdelana, število dodanih povabljencev: %{processed}, število napak %{failed}.
+
+ Podrobno poročilo:
+
+ ```text
+ %{logs}
+ ```
+ site_settings:
+ calendar_enabled: "Omogoči vtičnik discourse-calendar. To bo omogočilo dodajanje oznak [calendar][/calendar] v prvo objavo teme."
+ displayed_invitees_limit: "Omeji število povabljencev na dogodek."
+ display_post_event_date_on_topic_title: "Prikaže datum dogodka ob koncu naslova teme."
+ discourse_post_event_allowed_on_groups: "Grupe z dovoljenjem za ustvarjanje dogodkov."
+ discourse_post_event_allowed_custom_fields: "Omogoči dodajanje polj po meri za posamezen dogodek."
+ discourse_post_event_edit_notifications_time_extension: "Podaljša (v minutah) čas po koncu dogodka, ko so udeleženci s statusom 'pridem' še obveščeni o popravkih prve objave."
+ holiday_calendar_topic_id: "ID teme za prikaz koledarja praznikov / odsotnosti osebja."
+ delete_expired_event_posts_after: "Objave, ki vsebujejo pretekle časovne obsege, bodo samodejno izbrisane po (n) urah. Nastavi na -1 da preprečiš brisanje."
+ all_day_event_start_time: "Dogodki, ki nimajo časa začetka, se bodo začeli ob tem času. Oblika je HH:mm (06:00 in 18:00)"
+ all_day_event_end_time: "Dogodki, ki nimajo časa zaključka, se bodo končali ob tem času. Oblika je HH:mm (06:00 in 18:00)"
+ all_day_event_time_error: "Nepravilna oblika. Čas mora biti oblike HH:mm (npr.: 08:00)."
+ calendar_categories: "Prikaži koledar na vrhu kategorije. Obvezni nastavitvi sta categoryId in postId. npr.: categoryId=6;postId=453\n Ostale veljavne nastavitve: tzPicker, weekends in defaultView."
+ calendar_categories_outlet: "Omogoča spremembo mesta prikaza za koledar kategorije."
+ working_day_start_hour: "Začetna ura delovnega dne."
+ working_day_end_hour: "Končna ura delovnega dne."
+ close_to_working_day_hours_extension: "Nastavite podaljšan delovni čas, da izpostavite časovni obseg."
+ discourse_calendar:
+ invite_user_notification: "%{username} vas vabi na: %{description}"
+ calendar_must_be_in_first_post: "Oznako za koledar lahko uporabite samo v prvi objavi teme."
+ more_than_one_calendar: "V objavi sme biti največ en koledar."
+ more_than_two_dates: "Posamezna objava v temi s koledarjem sme vsebovati največ dva datuma."
+ event_expired: "Dogodek je končan"
+ discourse_post_event:
+ notifications:
+ before_event_reminder: "%{title} se bo pričel."
+ after_event_reminder: "%{title} je končan."
+ ongoing_event_reminder: "%{title} je v teku."
+ errors:
+ bulk_invite:
+ max_invitees: "Ustvarjenih je bilo prvih %{max_invittes} povabljencev. Poskusite razbiti datoteko na manjše dele."
+ error: "Pri nalaganju te datoteke je prišlo do napake. Prosimo, poskusite ponovno."
+ models:
+ event:
+ only_one_event: "Objava lahko vsebuje samo en dogodek."
+ must_be_in_first_post: "Dogodek sme biti samo v prvi objavi teme."
+ raw_invitees_length: "Dogodek je omejen na %{count} uporabnikov/skupin."
+ raw_invitees:
+ only_group: "Dogodek sprejema samo grupe."
+ ends_at_before_starts_at: "Dogodek se ne more končati preden se začne."
+ start_must_be_present_and_a_valid_date: "Dogodek zahteva začetni datum veljavne oblike."
+ end_must_be_a_valid_date: "Končni datum mora biti veljavne oblike."
+ acting_user_not_allowed_to_create_event: "Trenutni uporabnik nima pravic za ustvarjanje dogodkov."
+ acting_user_not_allowed_to_act_on_this_event: "Trenutni uporabnik nima pravic za upravljanje tega dogodka."
+ custom_field_is_invalid: "Polje po meri `%{field}` ni dovoljeno."
+ name:
+ length: "Ime dogodka sme vsebovati od %{minimum} do %{maximum} znakov."
diff --git a/plugins/discourse-calendar/config/locales/server.sq.yml b/plugins/discourse-calendar/config/locales/server.sq.yml
new file mode 100644
index 00000000000..b609ed6f36e
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/server.sq.yml
@@ -0,0 +1,11 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+sq:
+ reports:
+ currently_away:
+ labels:
+ username: Emri i përdoruesit
diff --git a/plugins/discourse-calendar/config/locales/server.sr.yml b/plugins/discourse-calendar/config/locales/server.sr.yml
new file mode 100644
index 00000000000..d361f4cdfbb
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/server.sr.yml
@@ -0,0 +1,11 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+sr:
+ reports:
+ currently_away:
+ labels:
+ username: Korisničko Ime
diff --git a/plugins/discourse-calendar/config/locales/server.sv.yml b/plugins/discourse-calendar/config/locales/server.sv.yml
new file mode 100644
index 00000000000..30e2f8e920f
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/server.sv.yml
@@ -0,0 +1,84 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+sv:
+ reports:
+ currently_away:
+ title: Användare som för närvarande är borta
+ labels:
+ username: Användarnamn
+ discourse_automation:
+ triggerables:
+ event_started:
+ title: Händelsen startad
+ system_messages:
+ discourse_calendar_holiday_region_invalid: "Den semesterregion som du angav finns inte."
+ discourse_calendar_enable_holiday_failed: "Denna semester kunde inte aktiveras, den är redan aktiverad eller den är inte inaktiverad."
+ discourse_post_event_bulk_invite_succeeded:
+ title: "Evenemang - Massinbjudan lyckades"
+ subject_template: "Massinbjudan har bearbetats framgångsrikt"
+ text_body_template: "Din massinbjudningsfil bearbetades, %{processed} inbjudningar skapades."
+ discourse_post_event_bulk_invite_failed:
+ title: "Evenemang - Massinbjudan misslyckades"
+ subject_template: "Massinbjudan av användare genererade fel"
+ text_body_template: |
+ Din massinbjudningsfil behandlades, %{processed} inbjudningar skickades med %{failed} fel.
+
+ Här är loggen:
+
+ `` text
+ %{logs}
+ ```
+ site_settings:
+ calendar_enabled: "Aktivera tillägget för discourse-kalendern. Detta kommer att lägga till stöd för en [calendar][/calendar]-tagg för första inlägget i ett ämne."
+ displayed_invitees_limit: "Begränsar antalet inbjudna som visas för ett evenemang."
+ display_post_event_date_on_topic_title: "Visar datumet för evenemanget efter ämnesrubriken."
+ discourse_post_event_allowed_on_groups: "Grupper som får skapa evenemang."
+ discourse_post_event_allowed_custom_fields: "Tillåter att varje evenemang anger värdet på anpassade fält."
+ discourse_post_event_edit_notifications_time_extension: "Förlänger (i minuter) perioden efter slutet av ett evenemang när \"närvarande\" inbjudna fortfarande underrättas om redigering i det ursprungliga inlägget."
+ holiday_calendar_topic_id: "Ämnes-ID för personalens semester-/frånvarokalender."
+ delete_expired_event_posts_after: "Inlägg med utgångna evenemang raderas automatiskt efter (n) timmar. Ställ in till -1 för att inaktivera radering."
+ all_day_event_start_time: "Evenemang som inte har en angiven starttid börjar vid denna tidpunkt. Formatet är HH:mm. För kl. 06.00, ange 06:00"
+ all_day_event_end_time: "Evenemang som inte har en angiven starttid börjar vid denna tidpunkt. Formatet är HH:mm. För kl. 18.00, ange 18:00"
+ all_day_event_time_error: "Ogiltig tid. Formatet måste vara HH:mm (t.ex: 08:00)."
+ calendar_categories: "Visa en kalender högst upp i en kategori. Obligatoriska inställningar är categoryId och postId. t.ex.: categoryId=6; postId=453\n Andra giltiga inställningar: tzPicker, weekends och defaultView."
+ calendar_categories_outlet: "Gör det möjligt att ändra vilken utgång som ska visa kategorins kalender."
+ working_days: "Ange arbetsdagar. Du kan visa tillgängligheten för en grupp med hjälp av taggen `timezones` i ett inlägg, t.ex.: `[timezones group=admins][timezones]`"
+ working_day_start_hour: "Starttid för arbetsdagens timmar."
+ working_day_end_hour: "Sluttid för arbetsdagens timmar."
+ close_to_working_day_hours_extension: "Ställ in förlängningstid i arbetsdagstimmar för att markera tidszonerna."
+ events_calendar_categories: "Visa en evenemangskalender högst upp i en kategori."
+ discourse_calendar:
+ invite_user_notification: "%{username} har bjudit in dig till: %{description}"
+ calendar_must_be_in_first_post: "Kalendertaggen kan endast användas i första inlägget i ett ämne."
+ more_than_one_calendar: "Du kan inte ha mer än en kalender i ett inlägg."
+ more_than_two_dates: "Ett inlägg i ett kalenderämne får inte innehålla mer än två datum."
+ event_expired: "Evenemanget har upphört"
+ discourse_post_event:
+ notifications:
+ before_event_reminder: "%{title} är på väg att börja."
+ after_event_reminder: "%{title} har avslutats."
+ ongoing_event_reminder: "%{title} pågår."
+ errors:
+ bulk_invite:
+ max_invitees: "De första %{max_invittes} inbjudningarna har skickats ut. Prova att dela upp filen i mindre delar."
+ error: "Det uppstod ett problem när filen skulle laddas upp. Vi ber dig försöka igen senare."
+ models:
+ event:
+ only_one_event: "Ett inlägg kan bara ha ett evenemang."
+ must_be_in_first_post: "Ett evenemang kan bara finnas i det första inlägget i ett ämne."
+ raw_invitees_length: "Ett evenemang är begränsat till %{count} användare/grupper."
+ raw_invitees:
+ only_group: "För ett evenemang godkänns endast gruppnamn."
+ ends_at_before_starts_at: "Ett evenemang kan inte avslutas innan det börjar."
+ start_must_be_present_and_a_valid_date: "Ett evenemang kräver ett giltigt startdatum."
+ end_must_be_a_valid_date: "Slutdatumet måste vara ett giltigt datum."
+ invalid_timezone: "Tidszonen känns inte igen."
+ acting_user_not_allowed_to_create_event: "Nuvarande användare får inte skapa evenemang."
+ acting_user_not_allowed_to_act_on_this_event: "Nuvarande användare får inte agera på detta evenemang."
+ custom_field_is_invalid: "Det anpassade fältet `%{field}` är inte tillåtet."
+ name:
+ length: "Namnlängden på evenemanget måste vara mellan %{minimum} och %{maximum} tecken."
diff --git a/plugins/discourse-calendar/config/locales/server.sw.yml b/plugins/discourse-calendar/config/locales/server.sw.yml
new file mode 100644
index 00000000000..f14ce7f31f3
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/server.sw.yml
@@ -0,0 +1,15 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+sw:
+ reports:
+ currently_away:
+ labels:
+ username: Jina la mtumiaji
+ discourse_post_event:
+ errors:
+ bulk_invite:
+ error: "Tatizo limetokea wakati wa kupakia faili. Tafadhali jaribu tena."
diff --git a/plugins/discourse-calendar/config/locales/server.te.yml b/plugins/discourse-calendar/config/locales/server.te.yml
new file mode 100644
index 00000000000..2669de81ebe
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/server.te.yml
@@ -0,0 +1,11 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+te:
+ reports:
+ currently_away:
+ labels:
+ username: సభ్యనామం
diff --git a/plugins/discourse-calendar/config/locales/server.th.yml b/plugins/discourse-calendar/config/locales/server.th.yml
new file mode 100644
index 00000000000..605158971c7
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/server.th.yml
@@ -0,0 +1,11 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+th:
+ reports:
+ currently_away:
+ labels:
+ username: ชื่อผู้ใช้
diff --git a/plugins/discourse-calendar/config/locales/server.tr_TR.yml b/plugins/discourse-calendar/config/locales/server.tr_TR.yml
new file mode 100644
index 00000000000..762356d8fc2
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/server.tr_TR.yml
@@ -0,0 +1,104 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+tr_TR:
+ reports:
+ currently_away:
+ title: Şu anda uzaktaki kullanıcılar
+ labels:
+ username: Kullanıcı Adı
+ discourse_automation:
+ triggerables:
+ event_started:
+ title: Etkinlik başladı
+ system_messages:
+ discourse_calendar_holiday_region_invalid: "Belirttiğiniz tatil bölgesi mevcut değil."
+ discourse_calendar_enable_holiday_failed: "Bu tatil etkinleştirilemedi, zaten etkinleştirilmiş veya devre dışı bırakılmamış."
+ discourse_post_event_bulk_invite_succeeded:
+ title: "Etkinlik - Toplu Davet Başarılı Oldu"
+ subject_template: "Toplu davet başarıyla işlendi"
+ text_body_template: "Toplu davet dosyanız işlendi, %{processed} davetli oluşturuldu."
+ discourse_post_event_bulk_invite_failed:
+ title: "Etkinlik - Toplu Davet Başarısız Oldu"
+ subject_template: "Toplu davet, hatalarla işlendi"
+ text_body_template: |
+ Toplu davet dosyanız işlendi, %{failed} hatayla %{processed} davetli oluşturuldu.
+
+ Günlük:
+
+ ```text
+ %{logs}
+ ```
+ site_settings:
+ events_max_rows: "Takvimdeki etkinlik başına maksimum metin satırı."
+ map_events_to_color: "Her etikete veya kategoriye bir renk atayın."
+ map_events_title: "Her kategori için \"Yaklaşan Etkinlikler\" kenar çubuğu başlığındaki \"Etkinlikler\"in üzerine yazar."
+ calendar_enabled: "discourse-calendar eklentisini etkinleştirin. Bu, bir konunun ilk gönderisine [calendar][/calendar] etiketi için destek ekleyecektir."
+ discourse_post_event_enabled: "Etkinlik özelliklerini etkinleştirir. Not: `takvim etkin` özelliğinin de etkinleştirilmesi gerekir."
+ displayed_invitees_limit: "Bir etkinlikte gösterilen davetlilerin sayısını sınırlar."
+ display_post_event_date_on_topic_title: "Konu başlığından sonra olayın tarihini gösterir."
+ use_local_event_date: "Konu başlığından sonra göreli zaman yerine yerel tarih kullanın."
+ discourse_post_event_allowed_on_groups: "Etkinlik oluşturmasına izin verilen gruplar."
+ discourse_post_event_allowed_custom_fields: "Her olayın özel alanların değerini ayarlamasına izin verir."
+ discourse_post_event_edit_notifications_time_extension: "Bir etkinliğin bitiminden sonraki süreyi (dakika olarak) uzatır, \"gidiyor\" davetliler orijinal gönderideki düzenlemeden yine de haberdar edilir."
+ holiday_calendar_topic_id: "Personelin tatil / izin takviminin konu kimliği."
+ holiday_status_emoji: Tatil durumu için kullanılan emoji'yi tanımlar.
+ delete_expired_event_posts_after: "Süresi dolmuş etkinlikleri olan gönderiler (n) saat sonra otomatik olarak silinecektir. Silmeyi devre dışı bırakmak için -1'e ayarlayın."
+ all_day_event_start_time: "Başlangıç zamanı belirtilmeyen etkinlikler bu saatte başlayacaktır. Biçim SS:dd'dir. Sabah 6 için 06.00 yazın"
+ all_day_event_end_time: "Bitiş saati belirtilmeyen etkinlikler bu saatte sona erecektir. Biçim SS:dd'dir. Öğleden sonra 6 için 18.00 girin"
+ all_day_event_time_error: "Geçersiz saat. Biçim SS:dd (ör: 08.00) olmalıdır."
+ calendar_categories: "Bir kategorinin en üstünde bir takvim görüntüleyin. Zorunlu ayarlar, CategoryId ve postId'dir. ör.: CategoryId=6;postId=453\n Diğer geçerli ayarlar: tzPicker, hafta sonları ve defaultView."
+ calendar_categories_outlet: "Kategori takvimini hangi çıkışın göstereceğini değiştirmeye izin verir."
+ working_days: "Çalışma günlerini ayarlayın. Bir gönderide \"saat dilimleri\" etiketini kullanarak bir grubun uygunluğunu görüntüleyebilirsiniz, ör.: \"[timezones group=admins][timezones]\""
+ working_day_start_hour: "Çalışma günü saatlerinin başlama saati."
+ working_day_end_hour: "Çalışma günü saatlerinin bitiş saati."
+ close_to_working_day_hours_extension: "Saat dilimlerini vurgulamak için çalışma günü saatlerinde uzatma süresini ayarlayın."
+ events_calendar_categories: "Bir kategorinin en üstünde bir etkinlik takvimi görüntüleyin."
+ sort_categories_by_event_start_date_enabled: "Kategori konularının etkinlik başlangıç tarihine göre sıralanmasını etkinleştirin."
+ disable_resorting_on_categories_enabled: "Kategorilerin, kullanıcıların etkinlik kategorisine göre sıralama yapma özelliğini devre dışı bırakmasına izin verin."
+ calendar_automatic_holidays_enabled: "Kullanıcı bölgesine göre tatil durumunu otomatik olarak ayarla (not: belirli otomatik tatilleri eklenti ayarlarından devre dışı bırakabilirsiniz)"
+ event_participation_buttons: "Kullanıcıların kullanabileceği etkinlik katılım düğmelerinin listesi."
+ sidebar_show_upcoming_events: "Kenar çubuğunda 'Daha Fazla' altında yaklaşan etkinlikler bağlantısını gösterin."
+ include_expired_events_on_calendar: "Kategori Takvimi ve Yaklaşan Etkinlikler görünümlerine geçmiş/süresi dolmuş etkinlikleri ekleyin."
+ discourse_calendar:
+ invite_user_notification: "%{username} sizi şuraya davet etti: %{description}"
+ calendar_must_be_in_first_post: "Takvim etiketi, bir konunun yalnızca ilk gönderisinde kullanılabilir."
+ more_than_one_calendar: "Bir gönderide birden fazla takviminiz olamaz."
+ more_than_two_dates: "Bir takvim konusunun gönderisi ikiden fazla tarih içeremez."
+ event_expired: "Etkinliğin süresi doldu"
+ holiday_status:
+ description: "Tatilde"
+ discourse_post_event:
+ notifications:
+ before_event_reminder: "%{title} başlamak üzere."
+ after_event_reminder: "%{title} sona erdi."
+ ongoing_event_reminder: "%{title} devam ediyor."
+ errors:
+ bulk_invite:
+ max_invitees: "İlk %{max_invittes} davetli oluşturuldu. Dosyayı daha küçük parçalara bölmeyi deneyin."
+ error: "Bu dosya yüklenirken bir hata oluştu. Lütfen daha sonra tekrar deneyiniz."
+ models:
+ event:
+ only_one_event: "Bir gönderide yalnızca bir etkinlik olabilir."
+ must_be_in_first_post: "Bir etkinlik yalnızca bir konunun ilk gönderisinde olabilir."
+ raw_invitees_length: "Bir etkinlik %{count} kullanıcı/grupla sınırlıdır."
+ raw_invitees:
+ only_group: "Bir etkinlik yalnızca grup adlarını kabul eder."
+ ends_at_before_starts_at: "Bir etkinlik başlamadan bitemez."
+ start_must_be_present_and_a_valid_date: "Bir etkinlik için geçerli bir başlangıç tarihi gerekir."
+ end_must_be_a_valid_date: "Bitiş tarihi geçerli bir tarih olmalıdır."
+ invalid_recurrence: "Yineleme şunlardan biri olmalıdır: her_ay, her_hafta, her_iki_hafta, her_dört_hafta, her_gün, her_hafta içi."
+ invalid_timezone: "Saat dilimi tanınmadı."
+ acting_user_not_allowed_to_create_event: "Mevcut kullanıcının etkinlik oluşturmasına izin verilmiyor."
+ acting_user_not_allowed_to_act_on_this_event: "Mevcut kullanıcının bu etkinlik üzerinde işlem yapmasına izin verilmiyor."
+ invalid_allowed_groups: "Geçersiz izin verilen gruplar."
+ acting_user_not_allowed_to_invite_these_groups: "Mevcut kullanıcının bu grupları davet etmesine izin verilmiyor."
+ custom_field_is_invalid: "\"%{field}\" özel alanına izin verilmiyor."
+ name:
+ length: "Etkinlik adı uzunluğu %{minimum} ila %{maximum} karakter olmalıdır."
+ discourse_push_notifications:
+ popup:
+ event_reminder: "Etkinlik Anımsatıcısı"
diff --git a/plugins/discourse-calendar/config/locales/server.ug.yml b/plugins/discourse-calendar/config/locales/server.ug.yml
new file mode 100644
index 00000000000..091692b4182
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/server.ug.yml
@@ -0,0 +1,15 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+ug:
+ reports:
+ currently_away:
+ labels:
+ username: ئىشلەتكۈچى ئاتى
+ discourse_post_event:
+ errors:
+ bulk_invite:
+ error: "ھۆججەتنى يۈكلەۋاتقاندا خاتالىق كۆرۈلدى. سەل تۇرۇپ قايتا سىناڭ."
diff --git a/plugins/discourse-calendar/config/locales/server.uk.yml b/plugins/discourse-calendar/config/locales/server.uk.yml
new file mode 100644
index 00000000000..46506f01175
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/server.uk.yml
@@ -0,0 +1,104 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+uk:
+ reports:
+ currently_away:
+ title: Користувачі зараз відсутні
+ labels:
+ username: Імʼя користувача
+ discourse_automation:
+ triggerables:
+ event_started:
+ title: Подія розпочалася
+ system_messages:
+ discourse_calendar_holiday_region_invalid: "Вказаного вами регіону відпочинку не існує."
+ discourse_calendar_enable_holiday_failed: "Це свято не можна ввімкнути, воно вже ввімкнено або не вимкнено."
+ discourse_post_event_bulk_invite_succeeded:
+ title: "Подія - масове запрошення виконано"
+ subject_template: "Масове запрошення успішно оброблено"
+ text_body_template: "Ваш файл масового запрошення оброблено, створено %{processed} запрошень."
+ discourse_post_event_bulk_invite_failed:
+ title: "Подія - масове запрошення не вдалося"
+ subject_template: "Масове запрошення оброблено з помилками"
+ text_body_template: |
+ Ваш файл масового запрошення оброблено, створено %{processed} запрошень із %{failed} помилками.
+
+ Ось журнал:
+
+ ```текст
+ %{logs}
+ ```
+ site_settings:
+ events_max_rows: "Максимальна кількість рядків тексту на подію в календарі."
+ map_events_to_color: "Призначити колір для кожного тегу або категорії."
+ map_events_title: "Перезаписує «Події» у заголовку бічної панелі «Майбутні події» для кожної категорії."
+ calendar_enabled: "Увімкніть плагін календаря дискурсу. Це додасть підтримку тегу [calendar][/calendar] у першій публікації теми."
+ discourse_post_event_enabled: "Вмикає функції подій. Примітка: також має бути увімкнено `календар`."
+ displayed_invitees_limit: "Обмежує кількість запрошених, що відображаються в події."
+ display_post_event_date_on_topic_title: "Відображає дату події після назви теми."
+ use_local_event_date: "Використовувати локальну дату після назви теми замість відносного часу."
+ discourse_post_event_allowed_on_groups: "Групи, яким дозволено створювати події."
+ discourse_post_event_allowed_custom_fields: "Дозволяє кожній події встановлювати значення користувацьких полів."
+ discourse_post_event_edit_notifications_time_extension: "Збільшує (у хвилинах) період після завершення події, коли запрошені, які `беруть участь` ще отримують сповіщення про редагування в оригінальному дописі."
+ holiday_calendar_topic_id: "Ідентифікатор теми календаря свят/відсутності персоналу."
+ holiday_status_emoji: Визначає емодзі, які використовуються для статусу свята.
+ delete_expired_event_posts_after: "Публікації з простроченими подіями будуть автоматично видалені через (n) годин. Встановіть -1, щоб вимкнути видалення."
+ all_day_event_start_time: "Події, у яких не вказано час початку, почнуться в цей час. Формат HH:мм. На 6:00 ранку введіть 06:00"
+ all_day_event_end_time: "Події, у яких не вказано час закінчення, закінчуються в цей час. Формат HH:мм. Для 18:00 введіть 18:00"
+ all_day_event_time_error: "Неправильний час. Формат має бути HH:mm (наприклад: 08:00)."
+ calendar_categories: "Відображати календар у верхній частині категорії. Обов’язковими параметрами є categoryId і postId. наприклад: categoryId=6;postId=453\n Інші дійсні налаштування: tzPicker, вихідні та defaultView."
+ calendar_categories_outlet: "Дозволяє змінити точку, де повинен відображатись календар категорій."
+ working_days: "Встановіть робочі дні. Ви можете відобразити доступність групи за допомогою тегу `timezones` у повідомленні, наприклад: `[timezones group=admins]`[timezones]"
+ working_day_start_hour: "Час початку робочого дня."
+ working_day_end_hour: "Час закінчення робочого дня."
+ close_to_working_day_hours_extension: "Встановіть час продовження в робочі дні з врахуванням часових поясів."
+ events_calendar_categories: "Відображати календар подій у верхній частині категорії."
+ sort_categories_by_event_start_date_enabled: "Увімкнути сортування тем категорії за датою початку події."
+ disable_resorting_on_categories_enabled: "Дозволити категоріям вимкнути можливість для користувачів сортувати за категорією події."
+ calendar_automatic_holidays_enabled: "Автоматично визначати стан свята на основі регіону користувачів (зверніть увагу: ви можете відключити певні автоматичні свята в налаштуваннях плагінів)"
+ event_participation_buttons: "Список кнопок участі у заходах, які можуть використовувати користувачі."
+ sidebar_show_upcoming_events: "Показати посилання на майбутні події на бічній панелі в розділі «Більше»."
+ include_expired_events_on_calendar: "Включати минулі/завершені події в перегляд календаря категорій і майбутніх подій."
+ discourse_calendar:
+ invite_user_notification: "%{username} запросили Вас на: %{description}"
+ calendar_must_be_in_first_post: "Тег календаря можна використовувати лише в першому дописі теми."
+ more_than_one_calendar: "Ви не можете мати більше одного календаря в публікації."
+ more_than_two_dates: "Повідомлення календаря теми не може містити більше двох дат."
+ event_expired: "Подія закінчилася"
+ holiday_status:
+ description: "У відпустці"
+ discourse_post_event:
+ notifications:
+ before_event_reminder: "%{title} ось-ось розпочнеться."
+ after_event_reminder: "%{title} закінчилась."
+ ongoing_event_reminder: "%{title} триває."
+ errors:
+ bulk_invite:
+ max_invitees: "Створено перші %{max_invittes} запрошених. Спробуйте розбити файл на менші частини."
+ error: "Під час завантаження цього файлу сталася помилка. Будь ласка, спробуйте пізніше."
+ models:
+ event:
+ only_one_event: "У дописі може бути лише одна подія."
+ must_be_in_first_post: "Подія може бути лише в першому дописі теми."
+ raw_invitees_length: "Подія обмежена %{count} користувачами/групами."
+ raw_invitees:
+ only_group: "Подія приймає лише назви груп."
+ ends_at_before_starts_at: "Подія не може закінчитися до початку."
+ start_must_be_present_and_a_valid_date: "Подія вимагає дійсної дати початку."
+ end_must_be_a_valid_date: "Дата закінчення повинна бути дійсною датою."
+ invalid_recurrence: "Періодичність повинна бути однією з наступних: every_month, every_week, every_two_weeks, every_four_weeks, every_day, every_weekday."
+ invalid_timezone: "Часовий пояс не розпізнано."
+ acting_user_not_allowed_to_create_event: "Поточний користувач не може створювати події."
+ acting_user_not_allowed_to_act_on_this_event: "Поточний користувач не має права впливати на цю подію."
+ invalid_allowed_groups: "Неправильні дозволені групи."
+ acting_user_not_allowed_to_invite_these_groups: "Поточний користувач не має права запрошувати ці групи."
+ custom_field_is_invalid: "Користувацьке поле `%{field}` не допускається."
+ name:
+ length: "Довжина назви події має бути від %{minimum} до %{maximum} символів."
+ discourse_push_notifications:
+ popup:
+ event_reminder: "Нагадування про подію"
diff --git a/plugins/discourse-calendar/config/locales/server.ur.yml b/plugins/discourse-calendar/config/locales/server.ur.yml
new file mode 100644
index 00000000000..80a29916a09
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/server.ur.yml
@@ -0,0 +1,15 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+ur:
+ reports:
+ currently_away:
+ labels:
+ username: صارف نام
+ discourse_post_event:
+ errors:
+ bulk_invite:
+ error: "یہ فائل اَپ لوڈ کرنے میں ایک خرابی کا سامنا کرنا پڑا۔ براہ مہربانی کچھ دیر بعد دوبارہ کوشش کریں۔"
diff --git a/plugins/discourse-calendar/config/locales/server.vi.yml b/plugins/discourse-calendar/config/locales/server.vi.yml
new file mode 100644
index 00000000000..843aee037b0
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/server.vi.yml
@@ -0,0 +1,15 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+vi:
+ reports:
+ currently_away:
+ labels:
+ username: Tên tài khoản
+ discourse_post_event:
+ errors:
+ bulk_invite:
+ error: "Đã xảy ra lỗi khi tải lên tệp đó. Vui lòng thử lại sau."
diff --git a/plugins/discourse-calendar/config/locales/server.zh_CN.yml b/plugins/discourse-calendar/config/locales/server.zh_CN.yml
new file mode 100644
index 00000000000..a3cb20a05cd
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/server.zh_CN.yml
@@ -0,0 +1,104 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+zh_CN:
+ reports:
+ currently_away:
+ title: 用户目前离开
+ labels:
+ username: 用户名
+ discourse_automation:
+ triggerables:
+ event_started:
+ title: 活动已开始
+ system_messages:
+ discourse_calendar_holiday_region_invalid: "您提供的假期区域不存在。"
+ discourse_calendar_enable_holiday_failed: "无法启用此假期,它已被启用或未被禁用。"
+ discourse_post_event_bulk_invite_succeeded:
+ title: "活动 - 批量邀请成功"
+ subject_template: "批量邀请处理成功"
+ text_body_template: "您的批量邀请文件已被处理,创建了 %{processed} 位受邀者。"
+ discourse_post_event_bulk_invite_failed:
+ title: "活动 - 批量邀请失败"
+ subject_template: "批量邀请已被处理,但存在错误"
+ text_body_template: |
+ 您的批量邀请文件已被处理,创建了 %{processed} 位受邀者,存在 %{failed} 个错误。
+
+ 以下为日志:
+
+ ```text
+ %{logs}
+ ```
+ site_settings:
+ events_max_rows: "日历中每个活动的最大文本行数。"
+ map_events_to_color: "为每个标签或类别分配颜色。"
+ map_events_title: "按类别覆盖“近期活动”边栏标题中的“活动”。"
+ calendar_enabled: "启用 discourse-calendar 插件。这将在话题的第一个帖子中添加对 [calendar][/calendar] 标签的支持。"
+ discourse_post_event_enabled: "启用活动功能。注意:还需要启用 `calendar enabled`。"
+ displayed_invitees_limit: "限制活动中显示的受邀者数量。"
+ display_post_event_date_on_topic_title: "在话题标题之后显示活动日期。"
+ use_local_event_date: "在话题标题后使用本地日期而不是相对时间。"
+ discourse_post_event_allowed_on_groups: "允许创建活动的群组。"
+ discourse_post_event_allowed_custom_fields: "允许让每个活动设置自定义字段的值。"
+ discourse_post_event_edit_notifications_time_extension: "活动结束后延长一段时间(以分钟为单位),让“参加”的受邀者仍可以收到原始帖子的编辑通知。"
+ holiday_calendar_topic_id: "管理人员假期/缺勤日历的话题 ID。"
+ holiday_status_emoji: 给节日状态设置表情图片。
+ delete_expired_event_posts_after: "包含过期活动的帖子将在 (n) 小时后自动删除。设置为 -1 将禁用删除。"
+ all_day_event_start_time: "未指定开始时间的活动将在此时间开始。格式为 HH:mm。如为上午 6:00,输入 06:00"
+ all_day_event_end_time: "未指定结束时间的活动将在此时间结束。格式为 HH:mm。如为下午 6:00,输入 18:00"
+ all_day_event_time_error: "时间无效。格式必须为 HH:mm(例如:08:00)。"
+ calendar_categories: "在类别顶部显示日历。强制设置为 categoryId 和 postId。例如:categoryId=6;postId=453\n 其他有效设置:tzPicker、weekends 和 defaultView。"
+ calendar_categories_outlet: "允许更改应显示类别日历的位置。"
+ working_days: "设置工作日。您可以在帖子中使用 `timezones` 标签显示群组可用性,例如:`[timezones group=admins][timezones]`"
+ working_day_start_hour: "工作日的开始时间。"
+ working_day_end_hour: "工作日的结束时间。"
+ close_to_working_day_hours_extension: "以工作日小时为单位设置延长时间,突出显示时区。"
+ events_calendar_categories: "在类别顶部显示活动日历。"
+ sort_categories_by_event_start_date_enabled: "启用按活动开始日期对类别话题进行排序。"
+ disable_resorting_on_categories_enabled: "允许类别禁用用户对活动类别进行排序的功能。"
+ calendar_automatic_holidays_enabled: "根据用户所在区域自动设置假期状态(注意:您可以在插件设置中禁用特定的自动假期)"
+ event_participation_buttons: "用户可以使用的活动参与按钮列表。"
+ sidebar_show_upcoming_events: "在边栏中的“更多”下显示近期活动链接。"
+ include_expired_events_on_calendar: "在“类别日历”和“近期活动”视图中包括过去/过期的活动。"
+ discourse_calendar:
+ invite_user_notification: "%{username} 邀请您加入:%{description}"
+ calendar_must_be_in_first_post: "日历标签仅可用于话题的第一个帖子。"
+ more_than_one_calendar: "一个帖子中不能有多个日历。"
+ more_than_two_dates: "日历话题的帖子不能包含两个以上的日期。"
+ event_expired: "活动已过期"
+ holiday_status:
+ description: "休假"
+ discourse_post_event:
+ notifications:
+ before_event_reminder: "%{title} 即将开始。"
+ after_event_reminder: "%{title} 已结束。"
+ ongoing_event_reminder: "%{title} 正在进行。"
+ errors:
+ bulk_invite:
+ max_invitees: "前 %{max_invittes} 位受邀者已创建。尝试将文件拆分成更小的部分。"
+ error: "上传文件时出错。请稍后再试。"
+ models:
+ event:
+ only_one_event: "一个帖子只能有一个活动。"
+ must_be_in_first_post: "活动只能出现在话题的第一个帖子中。"
+ raw_invitees_length: "活动仅限于 %{count} 个用户/群组。"
+ raw_invitees:
+ only_group: "活动只接受群组名称。"
+ ends_at_before_starts_at: "活动的结束日期不能早于开始日期。"
+ start_must_be_present_and_a_valid_date: "活动需要有效的开始日期。"
+ end_must_be_a_valid_date: "结束日期必须是有效日期。"
+ invalid_recurrence: "重复必须是以下值之一:every_month、every_week、every_two_weeks、every_four_weeks、every_day、every_weekday。"
+ invalid_timezone: "无法识别时区。"
+ acting_user_not_allowed_to_create_event: "当前用户不能创建活动。"
+ acting_user_not_allowed_to_act_on_this_event: "当前用户不能在此活动上执行操作。"
+ invalid_allowed_groups: "允许的群组无效。"
+ acting_user_not_allowed_to_invite_these_groups: "当前用户不能邀请这些群组。"
+ custom_field_is_invalid: "不允许自定义字段 `%{field}`。"
+ name:
+ length: "活动名称长度必须介于 %{minimum} 和 %{maximum} 个字符之间。"
+ discourse_push_notifications:
+ popup:
+ event_reminder: "活动提醒"
diff --git a/plugins/discourse-calendar/config/locales/server.zh_TW.yml b/plugins/discourse-calendar/config/locales/server.zh_TW.yml
new file mode 100644
index 00000000000..9e191369a6a
--- /dev/null
+++ b/plugins/discourse-calendar/config/locales/server.zh_TW.yml
@@ -0,0 +1,15 @@
+# WARNING: Never edit this file.
+# It will be overwritten when translations are pulled from Crowdin.
+#
+# To work with us on translations, join this project:
+# https://translate.discourse.org/
+
+zh_TW:
+ reports:
+ currently_away:
+ labels:
+ username: 使用者名稱
+ discourse_post_event:
+ errors:
+ bulk_invite:
+ error: "上傳檔案有錯誤,請再試一次。"
diff --git a/plugins/discourse-calendar/config/routes.rb b/plugins/discourse-calendar/config/routes.rb
new file mode 100644
index 00000000000..4b678d311f8
--- /dev/null
+++ b/plugins/discourse-calendar/config/routes.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+DiscoursePostEvent::Engine.routes.draw do
+ get "/discourse-post-event/events" => "events#index", :format => :json
+ get "/discourse-post-event/events/:id" => "events#show"
+ delete "/discourse-post-event/events/:id" => "events#destroy"
+ post "/discourse-post-event/events" => "events#create"
+ post "/discourse-post-event/events/:id/csv-bulk-invite" => "events#csv_bulk_invite"
+ post "/discourse-post-event/events/:id/bulk-invite" => "events#bulk_invite", :format => :json
+ post "/discourse-post-event/events/:id/invite" => "events#invite"
+ put "/discourse-post-event/events/:event_id/invitees/:invitee_id" => "invitees#update"
+ post "/discourse-post-event/events/:event_id/invitees" => "invitees#create"
+ get "/discourse-post-event/events/:post_id/invitees" => "invitees#index"
+ delete "/discourse-post-event/events/:post_id/invitees/:id" => "invitees#destroy"
+ get "/upcoming-events" => "upcoming_events#index"
+ get "/upcoming-events/mine" => "upcoming_events#index"
+end
+
+Discourse::Application.routes.draw do
+ mount ::DiscourseCalendar::Engine, at: "/"
+ mount ::DiscoursePostEvent::Engine, at: "/"
+
+ scope constraints: StaffConstraint.new do
+ get "/admin/plugins/calendar" => "admin/plugins#index"
+ get "/admin/discourse-calendar/holiday-regions/:region_code/holidays" =>
+ "admin/discourse_calendar/admin_holidays#index"
+ post "/admin/discourse-calendar/holidays/disable" =>
+ "admin/discourse_calendar/admin_holidays#disable"
+ delete "/admin/discourse-calendar/holidays/enable" =>
+ "admin/discourse_calendar/admin_holidays#enable"
+ end
+end
diff --git a/plugins/discourse-calendar/config/settings.yml b/plugins/discourse-calendar/config/settings.yml
new file mode 100644
index 00000000000..623d2aa79cf
--- /dev/null
+++ b/plugins/discourse-calendar/config/settings.yml
@@ -0,0 +1,129 @@
+discourse_calendar:
+ calendar_enabled:
+ default: false
+ client: true
+ holiday_calendar_topic_id:
+ default: ""
+ client: true
+ holiday_status_emoji:
+ client: true
+ default: "date"
+ delete_expired_event_posts_after:
+ min: -1
+ default: -1
+ all_day_event_start_time:
+ default: ""
+ client: true
+ validator: "CalendarSettingsValidator"
+ all_day_event_end_time:
+ default: ""
+ client: true
+ validator: "CalendarSettingsValidator"
+ calendar_categories:
+ type: list
+ list_type: simple
+ client: true
+ default: ""
+ calendar_categories_outlet:
+ client: true
+ default: "discovery-list-container-top"
+ type: enum
+ choices:
+ - none
+ - discovery-list-container-top
+ - before-topic-list-body
+ working_days:
+ type: list
+ list_type: simple
+ default: Monday|Tuesday|Wednesday|Thursday|Friday
+ client: true
+ working_day_start_hour:
+ default: 8
+ client: true
+ working_day_end_hour:
+ default: 17
+ client: true
+ close_to_working_day_hours_extension:
+ default: 2
+ client: true
+ calendar_automatic_holidays_enabled: true
+ enable_timezone_offset_for_calendar_events:
+ default: false
+ client: true
+ hidden: true
+ split_grouped_events_by_timezone_threshold:
+ default: 0
+ client: true
+ hidden: true
+ default_timezone_offset_user_option:
+ default: false
+ client: true
+ hidden: true
+ event_participation_buttons:
+ default: "going|interested|not going"
+ client: true
+ type: list
+ list_type: simple
+ allow_any: false
+ choices:
+ - going
+ - interested
+ - not going
+ discourse_post_event_enabled:
+ default: false
+ client: true
+ discourse_post_event_allowed_on_groups:
+ client: true
+ type: group_list
+ list_type: compact
+ default: ""
+ allow_any: false
+ refresh: true
+ displayed_invitees_limit:
+ default: 10
+ client: false
+ max: 25
+ display_post_event_date_on_topic_title:
+ default: true
+ client: true
+ use_local_event_date:
+ default: false
+ client: true
+ discourse_post_event_max_bulk_invitees:
+ default: 500
+ hidden: true
+ discourse_post_event_edit_notifications_time_extension:
+ default: 0
+ min: 0
+ discourse_post_event_allowed_custom_fields:
+ type: list
+ list_type: simple
+ client: true
+ default: ""
+ events_calendar_categories:
+ type: category_list
+ client: true
+ default: ""
+ sort_categories_by_event_start_date_enabled:
+ default: false
+ client: true
+ disable_resorting_on_categories_enabled:
+ default: false
+ client: true
+ sidebar_show_upcoming_events:
+ default: true
+ client: true
+ events_max_rows:
+ default: 2
+ client: true
+ map_events_to_color:
+ client: true
+ default: "[]"
+ json_schema: DiscourseCalendar::SiteSettings::MapEventTagColorsJsonSchema
+ map_events_title:
+ client: true
+ default: ""
+ json_schema: DiscourseCalendar::SiteSettings::MapEventsTitleJsonSchema
+ include_expired_events_on_calendar:
+ default: false
+ client: true
diff --git a/plugins/discourse-calendar/db/migrate/20190724181542_add_on_holiday_index.rb b/plugins/discourse-calendar/db/migrate/20190724181542_add_on_holiday_index.rb
new file mode 100644
index 00000000000..1d8f060ca12
--- /dev/null
+++ b/plugins/discourse-calendar/db/migrate/20190724181542_add_on_holiday_index.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+class AddOnHolidayIndex < ActiveRecord::Migration[5.2]
+ def up
+ execute <<~SQL
+ DELETE
+ FROM user_custom_fields a
+ USING user_custom_fields b
+ WHERE a.name = 'on_holiday'
+ AND a.name = b.name
+ AND a.user_id = b.user_id
+ AND a.id > b.id
+ SQL
+
+ add_index :user_custom_fields,
+ %i[name user_id],
+ unique: true,
+ name: :idx_user_custom_fields_on_holiday,
+ where: "name = 'on_holiday'"
+ end
+
+ def down
+ remove_index :user_custom_fields, name: :idx_user_custom_fields_on_holiday
+ end
+end
diff --git a/plugins/discourse-calendar/db/migrate/20200226183018_create_calendar_events.rb b/plugins/discourse-calendar/db/migrate/20200226183018_create_calendar_events.rb
new file mode 100644
index 00000000000..17531a7b1d6
--- /dev/null
+++ b/plugins/discourse-calendar/db/migrate/20200226183018_create_calendar_events.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+class CreateCalendarEvents < ActiveRecord::Migration[5.2]
+ def change
+ create_table :calendar_events do |t|
+ t.integer :topic_id, null: false
+ t.integer :post_id
+ t.integer :post_number
+ t.integer :user_id
+ t.string :username
+ t.string :description
+ t.datetime :start_date, null: false
+ t.datetime :end_date
+ t.string :recurrence
+ t.string :region
+ t.timestamps
+
+ t.index :topic_id
+ t.index :post_id
+ t.index :user_id
+ end
+
+ # Data structure stored in 'calendar-details' custom field is complex and
+ # difficult to transform using SQL only. It is safer to extract all calendar
+ # events again.
+ begin
+ calendar_topic_ids = DB.query_single(<<~SQL)
+ SELECT topic_id
+ FROM posts
+ JOIN post_custom_fields ON posts.id = post_custom_fields.post_id
+ WHERE post_custom_fields.name = 'calendar-details'
+ OR post_custom_fields.name = 'calendar-holidays'
+ SQL
+
+ # this is not ideal we should be using SQL here but this will work around bad schema
+ ActiveRecord::Base.connection.query_cache.clear
+ Post.reset_column_information # rubocop:disable Discourse/NoResetColumnInformationInMigrations
+ Post.where(topic_id: calendar_topic_ids).each { |post| CalendarEvent.update(post) }
+
+ execute "DELETE FROM post_custom_fields WHERE name = 'calendar-details' OR name = 'calendar-holidays'"
+ rescue => e
+ STDERR.puts e.message
+ STDERR.puts e.backtrace.join("\n")
+ end
+ end
+end
diff --git a/plugins/discourse-calendar/db/migrate/20200310200000_remove_timezone_custom_field.rb b/plugins/discourse-calendar/db/migrate/20200310200000_remove_timezone_custom_field.rb
new file mode 100644
index 00000000000..5f785d45ec2
--- /dev/null
+++ b/plugins/discourse-calendar/db/migrate/20200310200000_remove_timezone_custom_field.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class RemoveTimezoneCustomField < ActiveRecord::Migration[5.2]
+ def up
+ execute "DELETE FROM user_custom_fields WHERE name = 'timezone'"
+ end
+
+ def down
+ raise ActiveRecord::IrreversibleMigration
+ end
+end
diff --git a/plugins/discourse-calendar/db/migrate/20200327195549_add_topic_custom_field_post_event_date_index.rb b/plugins/discourse-calendar/db/migrate/20200327195549_add_topic_custom_field_post_event_date_index.rb
new file mode 100644
index 00000000000..4a3caa66d24
--- /dev/null
+++ b/plugins/discourse-calendar/db/migrate/20200327195549_add_topic_custom_field_post_event_date_index.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class AddTopicCustomFieldPostEventDateIndex < ActiveRecord::Migration[6.0]
+ def change
+ add_index :topic_custom_fields,
+ %i[name topic_id],
+ name: :idx_topic_custom_fields_post_event_starts_at,
+ unique: true,
+ where: "name = '#{DiscoursePostEvent::TOPIC_POST_EVENT_STARTS_AT}'"
+ end
+end
diff --git a/plugins/discourse-calendar/db/migrate/20200409102639_drop_incorrect_future_schema_migrations.rb b/plugins/discourse-calendar/db/migrate/20200409102639_drop_incorrect_future_schema_migrations.rb
new file mode 100644
index 00000000000..aa30144b51b
--- /dev/null
+++ b/plugins/discourse-calendar/db/migrate/20200409102639_drop_incorrect_future_schema_migrations.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+class DropIncorrectFutureSchemaMigrations < ActiveRecord::Migration[5.2]
+ def up
+ execute <<-SQL
+ DELETE FROM schema_migrations WHERE version = '20201303000001';
+ DELETE FROM schema_migration_details WHERE version = '20201303000001';
+ DELETE FROM schema_migrations WHERE version = '20201303000002';
+ DELETE FROM schema_migration_details WHERE version = '20201303000002';
+ SQL
+ end
+
+ def down
+ raise ActiveRecord::IrreversibleMigration
+ end
+end
diff --git a/plugins/discourse-calendar/db/migrate/20200409102640_create_post_events_table.rb b/plugins/discourse-calendar/db/migrate/20200409102640_create_post_events_table.rb
new file mode 100644
index 00000000000..257d373550a
--- /dev/null
+++ b/plugins/discourse-calendar/db/migrate/20200409102640_create_post_events_table.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+class CreatePostEventsTable < ActiveRecord::Migration[5.2]
+ def up
+ unless ActiveRecord::Base.connection.table_exists?("discourse_calendar_post_events")
+ create_table :discourse_calendar_post_events, id: false do |t|
+ t.bigint :id, null: false, primary_key: true
+ t.integer :status, default: 0, null: false
+ t.integer :display_invitees, default: 0, null: false
+ t.datetime :starts_at, null: false, default: -> { "CURRENT_TIMESTAMP" }
+ t.datetime :ends_at
+ t.datetime :deleted_at
+ t.string :raw_invitees, array: true
+ t.string :name
+ end
+ end
+ end
+
+ def down
+ drop_table :discourse_calendar_post_events
+ end
+end
diff --git a/plugins/discourse-calendar/db/migrate/20200409102641_create_invitees_table.rb b/plugins/discourse-calendar/db/migrate/20200409102641_create_invitees_table.rb
new file mode 100644
index 00000000000..6f55f5cfe81
--- /dev/null
+++ b/plugins/discourse-calendar/db/migrate/20200409102641_create_invitees_table.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+class CreateInviteesTable < ActiveRecord::Migration[5.2]
+ def up
+ unless ActiveRecord::Base.connection.table_exists?("discourse_calendar_invitees")
+ create_table :discourse_calendar_invitees do |t|
+ t.integer :post_id, null: false
+ t.integer :user_id, null: false
+ t.integer :status
+ t.timestamps null: false
+ t.boolean :notified, null: false, default: false
+ end
+
+ add_index :discourse_calendar_invitees, %i[post_id user_id], unique: true
+ end
+ end
+
+ def down
+ drop_table :discourse_calendar_invitees
+ end
+end
diff --git a/plugins/discourse-calendar/db/migrate/20200409102642_rename_setting_to_discourse_post_event.rb b/plugins/discourse-calendar/db/migrate/20200409102642_rename_setting_to_discourse_post_event.rb
new file mode 100644
index 00000000000..ddb114be5ad
--- /dev/null
+++ b/plugins/discourse-calendar/db/migrate/20200409102642_rename_setting_to_discourse_post_event.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class RenameSettingToDiscoursePostEvent < ActiveRecord::Migration[6.0]
+ def up
+ execute "UPDATE site_settings SET name = 'discourse_post_event_enabled' WHERE name = 'post_event_enabled'"
+ end
+
+ def down
+ execute "UPDATE site_settings SET name = 'post_event_enabled' WHERE name = 'discourse_post_event_enabled'"
+ end
+end
diff --git a/plugins/discourse-calendar/db/migrate/20200409102643_rename_tables_to_discourse_post_event.rb b/plugins/discourse-calendar/db/migrate/20200409102643_rename_tables_to_discourse_post_event.rb
new file mode 100644
index 00000000000..7dd1cff1a18
--- /dev/null
+++ b/plugins/discourse-calendar/db/migrate/20200409102643_rename_tables_to_discourse_post_event.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+require "migration/table_dropper"
+
+class RenameTablesToDiscoursePostEvent < ActiveRecord::Migration[6.0]
+ def up
+ unless table_exists?(:discourse_post_event_events)
+ Migration::TableDropper.read_only_table(:discourse_calendar_post_events)
+
+ execute <<~SQL
+ CREATE TABLE discourse_post_event_events
+ (LIKE discourse_calendar_post_events INCLUDING ALL);
+ SQL
+
+ execute <<~SQL
+ INSERT INTO discourse_post_event_events
+ SELECT *
+ FROM discourse_calendar_post_events
+ SQL
+
+ execute <<~SQL
+ ALTER SEQUENCE discourse_calendar_post_events_id_seq
+ RENAME TO discourse_post_event_events_id_seq
+ SQL
+
+ execute <<~SQL
+ ALTER SEQUENCE discourse_post_event_events_id_seq
+ OWNED BY discourse_post_event_events.id
+ SQL
+ end
+
+ unless table_exists?(:discourse_post_event_invitees)
+ Migration::TableDropper.read_only_table(:discourse_calendar_invitees)
+
+ execute <<~SQL
+ CREATE TABLE discourse_post_event_invitees
+ (LIKE discourse_calendar_invitees INCLUDING ALL)
+ SQL
+
+ execute <<~SQL
+ INSERT INTO discourse_post_event_invitees
+ SELECT *
+ FROM discourse_calendar_invitees
+ SQL
+
+ execute <<~SQL
+ ALTER SEQUENCE discourse_calendar_invitees_id_seq
+ RENAME TO discourse_post_event_invitees_id_seq
+ SQL
+
+ execute <<~SQL
+ ALTER SEQUENCE discourse_post_event_invitees_id_seq
+ OWNED BY discourse_post_event_invitees.id
+ SQL
+ end
+ end
+
+ def down
+ raise ActiveRecord::IrreversibleMigration
+ end
+end
diff --git a/plugins/discourse-calendar/db/migrate/20200409120815_rename_topic_custom_field_topic_post_event_starts_at_index.rb b/plugins/discourse-calendar/db/migrate/20200409120815_rename_topic_custom_field_topic_post_event_starts_at_index.rb
new file mode 100644
index 00000000000..df6799c426d
--- /dev/null
+++ b/plugins/discourse-calendar/db/migrate/20200409120815_rename_topic_custom_field_topic_post_event_starts_at_index.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class RenameTopicCustomFieldTopicPostEventStartsAtIndex < ActiveRecord::Migration[6.0]
+ def up
+ remove_index :topic_custom_fields, name: "idx_topic_custom_fields_post_event_starts_at"
+
+ add_index :topic_custom_fields,
+ %i[name topic_id],
+ name: :idx_topic_custom_fields_topic_post_event_starts_at,
+ unique: true,
+ where: "name = '#{DiscoursePostEvent::TOPIC_POST_EVENT_STARTS_AT}'"
+ end
+
+ def down
+ raise ActiveRecord::IrreversibleMigration
+ end
+end
diff --git a/plugins/discourse-calendar/db/migrate/20200409181607_remove_display_invitees.rb b/plugins/discourse-calendar/db/migrate/20200409181607_remove_display_invitees.rb
new file mode 100644
index 00000000000..a9c48e19d5a
--- /dev/null
+++ b/plugins/discourse-calendar/db/migrate/20200409181607_remove_display_invitees.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class RemoveDisplayInvitees < ActiveRecord::Migration[6.0]
+ def up
+ remove_column :discourse_post_event_events, :display_invitees
+ end
+
+ def down
+ raise ActiveRecord::IrreversibleMigration
+ end
+end
diff --git a/plugins/discourse-calendar/db/migrate/20200729094848_add_url_column_to_event.rb b/plugins/discourse-calendar/db/migrate/20200729094848_add_url_column_to_event.rb
new file mode 100644
index 00000000000..94ab176c6bf
--- /dev/null
+++ b/plugins/discourse-calendar/db/migrate/20200729094848_add_url_column_to_event.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class AddUrlColumnToEvent < ActiveRecord::Migration[6.0]
+ def up
+ add_column :discourse_post_event_events, :url, :string, limit: 1000
+ end
+
+ def down
+ remove_column :discourse_post_event_events, :url
+ end
+end
diff --git a/plugins/discourse-calendar/db/migrate/20200805073343_drop_old_discourse_calendar_tables.rb b/plugins/discourse-calendar/db/migrate/20200805073343_drop_old_discourse_calendar_tables.rb
new file mode 100644
index 00000000000..e7283d16729
--- /dev/null
+++ b/plugins/discourse-calendar/db/migrate/20200805073343_drop_old_discourse_calendar_tables.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require "migration/table_dropper"
+
+class DropOldDiscourseCalendarTables < ActiveRecord::Migration[6.0]
+ def up
+ if table_exists?(:discourse_calendar_post_events)
+ Migration::TableDropper.execute_drop(:discourse_calendar_post_events)
+ end
+
+ if table_exists?(:discourse_calendar_invitees)
+ Migration::TableDropper.execute_drop(:discourse_calendar_invitees)
+ end
+ end
+
+ def down
+ raise ActiveRecord::IrrelversibleMigration
+ end
+end
diff --git a/plugins/discourse-calendar/db/migrate/20200805133257_add_custom_fields_to_event.rb b/plugins/discourse-calendar/db/migrate/20200805133257_add_custom_fields_to_event.rb
new file mode 100644
index 00000000000..2d9eaecf96c
--- /dev/null
+++ b/plugins/discourse-calendar/db/migrate/20200805133257_add_custom_fields_to_event.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class AddCustomFieldsToEvent < ActiveRecord::Migration[6.0]
+ def up
+ add_column :discourse_post_event_events, :custom_fields, :jsonb, null: false, default: {}
+ end
+
+ def down
+ remove_column :discourse_post_event_events, :custom_fields
+ end
+end
diff --git a/plugins/discourse-calendar/db/migrate/20200809154642_create_reminders_table.rb b/plugins/discourse-calendar/db/migrate/20200809154642_create_reminders_table.rb
new file mode 100644
index 00000000000..3b8df9c0599
--- /dev/null
+++ b/plugins/discourse-calendar/db/migrate/20200809154642_create_reminders_table.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+class CreateRemindersTable < ActiveRecord::Migration[6.0]
+ def change
+ create_table :discourse_post_event_reminders do |t|
+ t.integer :post_id, null: false
+ t.integer :value, null: false, default: 0
+ t.integer :mean, null: false, default: 0
+ t.string :unit, null: false, default: "minutes"
+ end
+ end
+end
diff --git a/plugins/discourse-calendar/db/migrate/20200810185432_refactor_reminders.rb b/plugins/discourse-calendar/db/migrate/20200810185432_refactor_reminders.rb
new file mode 100644
index 00000000000..2e4d070288f
--- /dev/null
+++ b/plugins/discourse-calendar/db/migrate/20200810185432_refactor_reminders.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class RefactorReminders < ActiveRecord::Migration[6.0]
+ def up
+ add_column :discourse_post_event_events, :reminders, :string
+ end
+
+ def down
+ raise ActiveRecord::IrreversibleMigration
+ end
+end
diff --git a/plugins/discourse-calendar/db/migrate/20200810190429_drop_reminders_table.rb b/plugins/discourse-calendar/db/migrate/20200810190429_drop_reminders_table.rb
new file mode 100644
index 00000000000..56e87e7ce9d
--- /dev/null
+++ b/plugins/discourse-calendar/db/migrate/20200810190429_drop_reminders_table.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class DropRemindersTable < ActiveRecord::Migration[6.0]
+ def up
+ drop_table :discourse_post_event_reminders
+ end
+
+ def down
+ raise ActiveRecord::IrreversibleMigration
+ end
+end
diff --git a/plugins/discourse-calendar/db/migrate/20200812193122_add_recurrence_to_events.rb b/plugins/discourse-calendar/db/migrate/20200812193122_add_recurrence_to_events.rb
new file mode 100644
index 00000000000..bba8997ed09
--- /dev/null
+++ b/plugins/discourse-calendar/db/migrate/20200812193122_add_recurrence_to_events.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class AddRecurrenceToEvents < ActiveRecord::Migration[6.0]
+ def up
+ add_column :discourse_post_event_events, :recurrence, :string
+ end
+
+ def down
+ remove_column :discourse_post_event_events, :recurrence
+ end
+end
diff --git a/plugins/discourse-calendar/db/migrate/20200926144256_add_unique_index_to_topic_event_ends_at_custom_field.rb b/plugins/discourse-calendar/db/migrate/20200926144256_add_unique_index_to_topic_event_ends_at_custom_field.rb
new file mode 100644
index 00000000000..c5fd1648e60
--- /dev/null
+++ b/plugins/discourse-calendar/db/migrate/20200926144256_add_unique_index_to_topic_event_ends_at_custom_field.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class AddUniqueIndexToTopicEventEndsAtCustomField < ActiveRecord::Migration[6.0]
+ def up
+ add_index :topic_custom_fields,
+ %i[name topic_id],
+ name: :idx_topic_custom_fields_topic_post_event_ends_at,
+ unique: true,
+ where: "name = '#{DiscoursePostEvent::TOPIC_POST_EVENT_ENDS_AT}'"
+ end
+
+ def down
+ raise ActiveRecord::IrreversibleMigration
+ end
+end
diff --git a/plugins/discourse-calendar/db/migrate/20201110225115_create_post_event_dates_table.rb b/plugins/discourse-calendar/db/migrate/20201110225115_create_post_event_dates_table.rb
new file mode 100644
index 00000000000..7aaa18c67c6
--- /dev/null
+++ b/plugins/discourse-calendar/db/migrate/20201110225115_create_post_event_dates_table.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+class CreatePostEventDatesTable < ActiveRecord::Migration[6.0]
+ def up
+ create_table :discourse_calendar_post_event_dates do |t|
+ t.integer :event_id
+ t.datetime :starts_at
+ t.datetime :ends_at
+ t.integer :reminder_counter, default: 0
+ t.datetime :event_will_start_sent_at
+ t.datetime :event_started_sent_at
+ t.datetime :finished_at
+ t.timestamps
+ end
+ add_index :discourse_calendar_post_event_dates, :event_id
+ add_index :discourse_calendar_post_event_dates, :finished_at
+ end
+
+ def down
+ raise ActiveRecord::IrreversibleMigration
+ end
+end
diff --git a/plugins/discourse-calendar/db/migrate/20201111005205_move_data_to_event_dates.rb b/plugins/discourse-calendar/db/migrate/20201111005205_move_data_to_event_dates.rb
new file mode 100644
index 00000000000..a9fadab48ba
--- /dev/null
+++ b/plugins/discourse-calendar/db/migrate/20201111005205_move_data_to_event_dates.rb
@@ -0,0 +1,137 @@
+# frozen_string_literal: true
+
+class MoveDataToEventDates < ActiveRecord::Migration[6.0]
+ VALID_OPTIONS = [:start, :end, :status, :"allowed-groups", :url, :name, :reminders, :recurrence]
+
+ def extract_events(post)
+ valid_options = VALID_OPTIONS.map { |o| "data-#{o}" }
+
+ valid_custom_fields = []
+
+ allowed_custom_fields =
+ DB
+ .query(
+ "SELECT * FROM site_settings WHERE name = 'discourse_post_event_allowed_custom_fields' LIMIT 1",
+ )
+ .first
+ &.value || ""
+ allowed_custom_fields
+ .split("|")
+ .each do |setting|
+ valid_custom_fields << {
+ original: "data-#{setting}",
+ normalized: "data-#{setting.gsub(/_/, "-")}",
+ }
+ end
+
+ Nokogiri
+ .HTML(post.cooked)
+ .css("div.discourse-post-event")
+ .map do |doc|
+ event = nil
+ doc.attributes.values.each do |attribute|
+ name = attribute.name
+ value = attribute.value
+
+ if value && valid_options.include?(name)
+ event ||= {}
+ event[name.sub("data-", "").to_sym] = CGI.escapeHTML(value)
+ end
+
+ valid_custom_fields.each do |valid_custom_field|
+ if value && valid_custom_field[:normalized] == name
+ event ||= {}
+ event[valid_custom_field[:original].sub("data-", "").to_sym] = CGI.escapeHTML(value)
+ end
+ end
+ end
+ event
+ end
+ .compact
+ end
+
+ def due_reminders(event)
+ return [] if event.reminders.blank?
+ event
+ .reminders
+ .split(",")
+ .map do |reminder|
+ value, unit = reminder.split(".")
+
+ allowed = %w[years months weeks days hours minutes seconds]
+ next if !allowed.include?(unit)
+ date = event.original_starts_at - value.to_i.public_send(unit)
+ { description: reminder, date: date }
+ end
+ .compact
+ .select { |reminder| reminder[:date] <= Time.current }
+ .sort_by { |reminder| reminder[:date] }
+ end
+
+ def up
+ rename_column :discourse_post_event_events, :starts_at, :original_starts_at
+ rename_column :discourse_post_event_events, :ends_at, :original_ends_at
+
+ query = <<~SQL
+ SELECT * FROM discourse_post_event_events
+ WHERE original_ends_at IS NOT NULL
+ SQL
+
+ DB
+ .query(query)
+ .each do |event|
+ post = DB.query("SELECT * FROM posts WHERE id = #{event.id}").first
+ next if !post
+ extracted_event = extract_events(post).first
+ next if !extracted_event
+
+ finished_at = (event.original_ends_at < Time.current) && event.original_ends_at
+ event_will_start_sent_at = event.original_starts_at - 1.hours
+ event_started_sent_at = event.original_starts_at
+ reminder_counter = due_reminders(event).length
+
+ DB.exec <<~SQL
+ INSERT INTO discourse_calendar_post_event_dates(event_id, starts_at, ends_at, event_will_start_sent_at, event_started_sent_at, #{finished_at ? "finished_at ," : ""} reminder_counter, created_at, updated_at)
+ VALUES (#{event.id},
+ '#{event.original_starts_at}',
+ '#{event.original_ends_at}',
+ '#{event_will_start_sent_at}',
+ '#{event_started_sent_at}',
+ #{finished_at ? ("'" + finished_at.to_s + "'" + ", ") : ""}
+ #{reminder_counter},
+ now(),
+ now())
+ SQL
+ DB.exec <<~SQL
+ UPDATE discourse_post_event_events
+ SET original_starts_at = '#{extracted_event[:start]}', original_ends_at = '#{extracted_event[:end]}'
+ WHERE id = #{event.id}
+ SQL
+ end
+
+ begin
+ Jobs.cancel_scheduled_job(:discourse_post_event_send_reminder)
+ rescue StandardError
+ nil
+ end
+ begin
+ Jobs.cancel_scheduled_job(:discourse_post_event_event_started)
+ rescue StandardError
+ nil
+ end
+ begin
+ Jobs.cancel_scheduled_job(:discourse_post_event_event_will_start)
+ rescue StandardError
+ nil
+ end
+ begin
+ Jobs.cancel_scheduled_job(:discourse_post_event_event_ended)
+ rescue StandardError
+ nil
+ end
+ end
+
+ def down
+ raise ActiveRecord::IrreversibleMigration
+ end
+end
diff --git a/plugins/discourse-calendar/db/migrate/20211216124303_add_timezone_to_calendar_events.rb b/plugins/discourse-calendar/db/migrate/20211216124303_add_timezone_to_calendar_events.rb
new file mode 100644
index 00000000000..073558de793
--- /dev/null
+++ b/plugins/discourse-calendar/db/migrate/20211216124303_add_timezone_to_calendar_events.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class AddTimezoneToCalendarEvents < ActiveRecord::Migration[6.1]
+ def change
+ add_column :calendar_events, :timezone, :string
+ end
+end
diff --git a/plugins/discourse-calendar/db/migrate/20220228163400_adds_timezone_to_discourse_post_event_event.rb b/plugins/discourse-calendar/db/migrate/20220228163400_adds_timezone_to_discourse_post_event_event.rb
new file mode 100644
index 00000000000..7997eff2b10
--- /dev/null
+++ b/plugins/discourse-calendar/db/migrate/20220228163400_adds_timezone_to_discourse_post_event_event.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class AddsTimezoneToDiscoursePostEventEvent < ActiveRecord::Migration[6.1]
+ def change
+ add_column :discourse_post_event_events, :timezone, :string
+ end
+end
diff --git a/plugins/discourse-calendar/db/migrate/20220604200919_create_disabled_holidays.rb b/plugins/discourse-calendar/db/migrate/20220604200919_create_disabled_holidays.rb
new file mode 100644
index 00000000000..5d4503ccebd
--- /dev/null
+++ b/plugins/discourse-calendar/db/migrate/20220604200919_create_disabled_holidays.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class CreateDisabledHolidays < ActiveRecord::Migration[7.0]
+ def change
+ create_table :discourse_calendar_disabled_holidays do |t|
+ t.string :holiday_name, null: false
+ t.string :region_code, null: false
+ t.boolean :disabled, null: false, default: true
+
+ t.timestamps
+ end
+
+ add_index :discourse_calendar_disabled_holidays,
+ %i[holiday_name region_code],
+ name: "index_disabled_holidays_on_holiday_name_and_region_code"
+ end
+end
diff --git a/plugins/discourse-calendar/db/migrate/20220613073844_unescape_event_name.rb b/plugins/discourse-calendar/db/migrate/20220613073844_unescape_event_name.rb
new file mode 100644
index 00000000000..24e21df0dcb
--- /dev/null
+++ b/plugins/discourse-calendar/db/migrate/20220613073844_unescape_event_name.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+class UnescapeEventName < ActiveRecord::Migration[6.1]
+ disable_ddl_transaction!
+
+ TEMP_INDEX_NAME = "_temp_discourse_calendar_unescape_event_name_migration"
+
+ def up
+ # event notifications
+ DB.exec(
+ "CREATE INDEX CONCURRENTLY #{TEMP_INDEX_NAME} ON notifications(id) WHERE notification_type IN (27, 28)",
+ )
+ start, limit =
+ DB.query_single(
+ "SELECT MIN(id), MAX(id) FROM notifications WHERE notification_type IN (27, 28)",
+ )
+
+ return if !start
+
+ notifications_query = <<~SQL
+ SELECT id, data
+ FROM notifications
+ WHERE
+ id >= :start AND
+ notification_type IN (27, 28) AND
+ data::json ->> 'topic_title' LIKE '%&%'
+ ORDER BY id ASC
+ LIMIT 10000
+ SQL
+
+ while true
+ break if start > limit
+
+ max_seen = -1
+
+ DB
+ .query(notifications_query, start: start)
+ .each do |record|
+ id = record.id
+
+ max_seen = id if id > max_seen
+
+ data = JSON.parse(record.data)
+ unescaped = CGI.unescapeHTML(data["topic_title"])
+ next if unescaped == data["topic_title"]
+ data["topic_title"] = unescaped
+
+ DB.exec(<<~SQL, data: data.to_json, id: id)
+ UPDATE notifications SET data = :data WHERE id = :id
+ SQL
+ end
+
+ start += 10_000
+
+ start = max_seen + 1 if max_seen > start
+ end
+
+ # event names
+ events_query = <<~SQL
+ SELECT id, name
+ FROM discourse_post_event_events
+ WHERE name LIKE '%&%'
+ ORDER BY id ASC
+ SQL
+
+ DB
+ .query(events_query)
+ .each do |event|
+ unescaped_name = CGI.unescapeHTML(event.name)
+ next if unescaped_name == event.name
+ DB.exec(<<~SQL, unescaped_name: unescaped_name, id: event.id)
+ UPDATE discourse_post_event_events SET name = :unescaped_name WHERE id = :id
+ SQL
+ end
+ ensure
+ DB.exec("DROP INDEX IF EXISTS #{TEMP_INDEX_NAME}")
+ end
+
+ def down
+ raise ActiveRecord::IrreversibleMigration
+ end
+end
diff --git a/plugins/discourse-calendar/db/migrate/20220724130519_fix_post_event_timezones.rb b/plugins/discourse-calendar/db/migrate/20220724130519_fix_post_event_timezones.rb
new file mode 100644
index 00000000000..58574b987a2
--- /dev/null
+++ b/plugins/discourse-calendar/db/migrate/20220724130519_fix_post_event_timezones.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+class FixPostEventTimezones < ActiveRecord::Migration[7.0]
+ def up
+ execute <<~SQL
+ UPDATE discourse_post_event_events
+ SET
+ original_starts_at = (original_starts_at::timestamp AT TIME ZONE timezone),
+ original_ends_at = (original_ends_at::timestamp AT TIME ZONE timezone)
+ WHERE timezone IS NOT NULL;
+ SQL
+
+ execute <<~SQL
+ UPDATE discourse_calendar_post_event_dates
+ SET
+ starts_at = (starts_at::timestamp AT TIME ZONE timezone),
+ ends_at = (ends_at::timestamp AT TIME ZONE timezone)
+ FROM discourse_post_event_events
+ WHERE discourse_post_event_events.id = discourse_calendar_post_event_dates.event_id
+ AND discourse_post_event_events.timezone IS NOT NULL
+ SQL
+
+ execute <<~SQL
+ UPDATE topic_custom_fields
+ SET value = (value::timestamp AT TIME ZONE discourse_post_event_events.timezone) AT TIME ZONE 'UTC'
+ FROM discourse_post_event_events
+ JOIN posts ON discourse_post_event_events.id = posts.id
+ WHERE discourse_post_event_events.timezone IS NOT NULL
+ AND topic_custom_fields.topic_id = posts.topic_id
+ AND topic_custom_fields.name IN ('TopicEventStartsAt', 'TopicEventEndsAt')
+ SQL
+ end
+
+ def down
+ raise ActiveRecord::IrreversibleMigration
+ end
+end
diff --git a/plugins/discourse-calendar/db/migrate/20221121165352_add_type_field_to_events_reminders.rb b/plugins/discourse-calendar/db/migrate/20221121165352_add_type_field_to_events_reminders.rb
new file mode 100644
index 00000000000..a13dff56006
--- /dev/null
+++ b/plugins/discourse-calendar/db/migrate/20221121165352_add_type_field_to_events_reminders.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+class AddTypeFieldToEventsReminders < ActiveRecord::Migration[7.0]
+ def up
+ reminders_query = <<~SQL
+ SELECT id, reminders
+ FROM discourse_post_event_events
+ WHERE reminders IS NOT NULL
+ SQL
+
+ DB
+ .query(reminders_query)
+ .each do |event|
+ refactored_reminders = []
+ event
+ .reminders
+ .split(",") { |reminder| refactored_reminders.push(reminder.prepend("notification.")) }
+
+ event_reminders = refactored_reminders.join(",")
+
+ DB.exec(<<~SQL, id: event.id, reminders: event_reminders)
+ UPDATE discourse_post_event_events
+ SET reminders = :reminders
+ WHERE id = :id
+ SQL
+ end
+ end
+
+ def down
+ raise ActiveRecord::IrreversibleMigration
+ end
+end
diff --git a/plugins/discourse-calendar/db/migrate/20221223210225_add_minimal_option_to_calendar_event.rb b/plugins/discourse-calendar/db/migrate/20221223210225_add_minimal_option_to_calendar_event.rb
new file mode 100644
index 00000000000..56035d47484
--- /dev/null
+++ b/plugins/discourse-calendar/db/migrate/20221223210225_add_minimal_option_to_calendar_event.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class AddMinimalOptionToCalendarEvent < ActiveRecord::Migration[7.0]
+ def change
+ add_column :discourse_post_event_events, :minimal, :boolean
+ end
+end
diff --git a/plugins/discourse-calendar/db/migrate/20231123233308_delete_duplicated_holidays.rb b/plugins/discourse-calendar/db/migrate/20231123233308_delete_duplicated_holidays.rb
new file mode 100644
index 00000000000..99ea8a203b8
--- /dev/null
+++ b/plugins/discourse-calendar/db/migrate/20231123233308_delete_duplicated_holidays.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class DeleteDuplicatedHolidays < ActiveRecord::Migration[7.0]
+ def up
+ execute <<~SQL
+ DELETE
+ FROM calendar_events ce
+ WHERE
+ ce.id IN (SELECT ce2.id FROM calendar_events ce2
+ INNER JOIN users ON users.id = ce2.user_id
+ WHERE ce2.post_id IS NULL
+ AND ce2.username != users.username)
+ SQL
+ end
+
+ def down
+ raise ActiveRecord::IrreversibleMigration
+ end
+end
diff --git a/plugins/discourse-calendar/db/migrate/20231124021939_delete_similar_holidays.rb b/plugins/discourse-calendar/db/migrate/20231124021939_delete_similar_holidays.rb
new file mode 100644
index 00000000000..4f02700e35a
--- /dev/null
+++ b/plugins/discourse-calendar/db/migrate/20231124021939_delete_similar_holidays.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+class DeleteSimilarHolidays < ActiveRecord::Migration[7.0]
+ def up
+ execute <<~SQL
+ DELETE
+ FROM calendar_events ce
+ WHERE
+ ce.id IN (SELECT DISTINCT(ce3.id) FROM calendar_events ce2
+ LEFT JOIN calendar_events ce3 ON ce3.user_id = ce2.user_id AND ce3.description = ce2.description
+ WHERE ce2.start_date >= (ce3.start_date - INTERVAL '1 days')
+ AND ce2.start_date <= (ce3.start_date + INTERVAL '1 days')
+ AND ce2.timezone IS NOT NULL
+ AND ce3.timezone IS NULL
+ AND ce3.id != ce2.id
+ AND ce2.post_id IS NULL
+ AND ce3.post_id IS NULL
+ )
+ SQL
+ end
+
+ def down
+ raise ActiveRecord::IrreversibleMigration
+ end
+end
diff --git a/plugins/discourse-calendar/db/migrate/20240513140542_add_closed_to_discourse_post_event.rb b/plugins/discourse-calendar/db/migrate/20240513140542_add_closed_to_discourse_post_event.rb
new file mode 100644
index 00000000000..9abf973372a
--- /dev/null
+++ b/plugins/discourse-calendar/db/migrate/20240513140542_add_closed_to_discourse_post_event.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class AddClosedToDiscoursePostEvent < ActiveRecord::Migration[7.0]
+ def change
+ add_column :discourse_post_event_events, :closed, :boolean, default: false, null: false
+ end
+end
diff --git a/plugins/discourse-calendar/db/migrate/20250520042223_add_chat_fields_to_events.rb b/plugins/discourse-calendar/db/migrate/20250520042223_add_chat_fields_to_events.rb
new file mode 100644
index 00000000000..3d479864b15
--- /dev/null
+++ b/plugins/discourse-calendar/db/migrate/20250520042223_add_chat_fields_to_events.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+class AddChatFieldsToEvents < ActiveRecord::Migration[7.2]
+ def change
+ add_column :discourse_post_event_events, :chat_enabled, :boolean, default: false, null: false
+ add_column :discourse_post_event_events, :chat_channel_id, :bigint
+ end
+end
diff --git a/plugins/discourse-calendar/db/migrate/20250526154632_add_recurrence_until.rb b/plugins/discourse-calendar/db/migrate/20250526154632_add_recurrence_until.rb
new file mode 100644
index 00000000000..5b029c6d258
--- /dev/null
+++ b/plugins/discourse-calendar/db/migrate/20250526154632_add_recurrence_until.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+#
+class AddRecurrenceUntil < ActiveRecord::Migration[7.2]
+ def change
+ add_column :discourse_post_event_events, :recurrence_until, :datetime
+ end
+end
diff --git a/plugins/discourse-calendar/db/migrate/20250602114410_add_show_local_time.rb b/plugins/discourse-calendar/db/migrate/20250602114410_add_show_local_time.rb
new file mode 100644
index 00000000000..3534d412db4
--- /dev/null
+++ b/plugins/discourse-calendar/db/migrate/20250602114410_add_show_local_time.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class AddShowLocalTime < ActiveRecord::Migration[7.2]
+ def change
+ add_column :discourse_post_event_events, :show_local_time, :boolean, default: false, null: false
+ end
+end
diff --git a/plugins/discourse-calendar/db/migrate/20250616101944_add_location_to_event.rb b/plugins/discourse-calendar/db/migrate/20250616101944_add_location_to_event.rb
new file mode 100644
index 00000000000..be208d022a2
--- /dev/null
+++ b/plugins/discourse-calendar/db/migrate/20250616101944_add_location_to_event.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class AddLocationToEvent < ActiveRecord::Migration[7.2]
+ def change
+ add_column :discourse_post_event_events, :location, :string, limit: 1000
+ end
+end
diff --git a/plugins/discourse-calendar/db/migrate/20250616101945_add_description_to_event.rb b/plugins/discourse-calendar/db/migrate/20250616101945_add_description_to_event.rb
new file mode 100644
index 00000000000..58814d0c246
--- /dev/null
+++ b/plugins/discourse-calendar/db/migrate/20250616101945_add_description_to_event.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class AddDescriptionToEvent < ActiveRecord::Migration[7.2]
+ def change
+ add_column :discourse_post_event_events, :description, :string, limit: 1000
+ end
+end
diff --git a/plugins/discourse-calendar/jobs/regular/discourse_post_event/bulk_invite.rb b/plugins/discourse-calendar/jobs/regular/discourse_post_event/bulk_invite.rb
new file mode 100644
index 00000000000..38b6c90abaa
--- /dev/null
+++ b/plugins/discourse-calendar/jobs/regular/discourse_post_event/bulk_invite.rb
@@ -0,0 +1,135 @@
+# frozen_string_literal: true
+
+module Jobs
+ class DiscoursePostEventBulkInvite < ::Jobs::Base
+ sidekiq_options retry: false
+
+ def initialize
+ super
+
+ @logs = []
+ @processed = 0
+ @failed = 0
+ end
+
+ def execute(args)
+ invitees = args[:invitees]
+ raise Discourse::InvalidParameters.new(:invitees) if invitees.blank?
+
+ @current_user = User.find_by(id: args[:current_user_id])
+ raise Discourse::InvalidParameters.new(:current_user_id) unless @current_user
+
+ @event = DiscoursePostEvent::Event.find_by(id: args[:event_id])
+ raise Discourse::InvalidParameters.new(:event_id) unless @event
+
+ @guardian = Guardian.new(@current_user)
+ @guardian.ensure_can_edit!(@event.post)
+
+ process_invitees(invitees)
+ ensure
+ notify_user
+ end
+
+ private
+
+ def process_invitees(invitees)
+ invitees = filter_out_unavailable_groups(invitees)
+
+ max_bulk_invitees = SiteSetting.discourse_post_event_max_bulk_invitees
+
+ invitees.each do |invitee|
+ break if @processed >= max_bulk_invitees
+ process_invitee(invitee)
+ end
+
+ if @processed > 0
+ @event.publish_update!
+ @event.notify_invitees!(predefined_attendance: true)
+ end
+ rescue Exception => e
+ save_log "Bulk Invite Process Failed -- '#{e.message}'"
+ @failed += 1
+ end
+
+ def process_invitee(invitee)
+ if @event.public?
+ users = User.where(username: invitee["identifier"]).pluck(:id)
+ else
+ group = Group.find_by(name: invitee["identifier"])
+ if group
+ users = group.users.pluck(:id)
+ @event.update_with_params!(
+ raw_invitees: (@event.raw_invitees || []).push(group.name).uniq,
+ )
+ end
+ end
+
+ if users.blank?
+ save_log "Couldn't find user or group: '#{invitee["identifier"]}' or the groups provided contained no users. Note that public events can't bulk invite groups. And other events can't bulk invite usernames."
+ @failed += 1
+ return
+ end
+
+ users.each do |user_id|
+ create_attendance(user_id, @event.post.id, invitee["attendance"] || "going")
+ end
+
+ @processed += 1
+ rescue Exception => e
+ save_log "Bulk Invite Process Failed -- '#{e.message}'"
+ @failed += 1
+ end
+
+ def create_attendance(user_id, post_id, attendance)
+ unknown = DiscoursePostEvent::Invitee::UNKNOWN_ATTENDANCE
+
+ if attendance == unknown
+ DiscoursePostEvent::Invitee.where(user_id: user_id, post_id: post_id).destroy_all
+ else
+ status = DiscoursePostEvent::Invitee.statuses[attendance.to_sym]
+ invitee =
+ DiscoursePostEvent::Invitee.find_or_initialize_by(user_id: user_id, post_id: post_id)
+ invitee.notified = false
+ invitee.status = status
+ invitee.save!
+ end
+ end
+
+ def save_log(message)
+ @logs << "[#{Time.zone.now}] #{message}"
+ end
+
+ def notify_user
+ if @current_user
+ if @processed > 0 && @failed == 0
+ SystemMessage.create_from_system_user(
+ @current_user,
+ :discourse_post_event_bulk_invite_succeeded,
+ processed: @processed,
+ )
+ else
+ SystemMessage.create_from_system_user(
+ @current_user,
+ :discourse_post_event_bulk_invite_failed,
+ processed: @processed,
+ failed: @failed,
+ logs: @logs.join("\n"),
+ )
+ end
+ end
+ end
+
+ def invitee_groups(invitees)
+ Group.where(name: invitees.map { |i| i[:identifier] })
+ end
+
+ def filter_out_unavailable_groups(invitees)
+ groups = invitee_groups(invitees)
+ invitees.filter do |i|
+ group = groups.find { |g| g.name === i[:identifier] }
+
+ !group || (@guardian.can_see_group?(group) && @guardian.can_see_group_members?(group))
+ end
+ end
+ end
+end
diff --git a/plugins/discourse-calendar/jobs/regular/discourse_post_event/bump_topic.rb b/plugins/discourse-calendar/jobs/regular/discourse_post_event/bump_topic.rb
new file mode 100644
index 00000000000..530baaec141
--- /dev/null
+++ b/plugins/discourse-calendar/jobs/regular/discourse_post_event/bump_topic.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module Jobs
+ class DiscoursePostEventBumpTopic < ::Jobs::Base
+ sidekiq_options retry: false
+
+ def execute(args)
+ return unless topic = Topic.find_by(id: args[:topic_id].to_i)
+ return if args[:date].blank?
+
+ event_user = User.find_by(id: topic.user_id)
+ timer = TopicTimer.find_by(topic_id: args[:topic_id].to_i)
+
+ topic.set_or_create_timer(TopicTimer.types[:bump], args[:date], by_user: event_user)
+ end
+ end
+end
diff --git a/plugins/discourse-calendar/jobs/regular/discourse_post_event/event_started.rb b/plugins/discourse-calendar/jobs/regular/discourse_post_event/event_started.rb
new file mode 100644
index 00000000000..21273be7f76
--- /dev/null
+++ b/plugins/discourse-calendar/jobs/regular/discourse_post_event/event_started.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module Jobs
+ class DiscoursePostEventEventStarted < ::Jobs::Base
+ sidekiq_options retry: false
+
+ def execute(args)
+ raise Discourse::InvalidParameters.new(:event_id) if args[:event_id].blank?
+ event = DiscoursePostEvent::Event.find(args[:event_id])
+ MessageBus.publish("/topic/#{event.post.topic_id}", reload_topic: true, refresh_stream: true)
+ DiscourseEvent.trigger(:discourse_post_event_event_started, event)
+ end
+ end
+end
diff --git a/plugins/discourse-calendar/jobs/regular/discourse_post_event/send_reminder.rb b/plugins/discourse-calendar/jobs/regular/discourse_post_event/send_reminder.rb
new file mode 100644
index 00000000000..a0774efccdf
--- /dev/null
+++ b/plugins/discourse-calendar/jobs/regular/discourse_post_event/send_reminder.rb
@@ -0,0 +1,90 @@
+# frozen_string_literal: true
+
+module Jobs
+ class DiscoursePostEventSendReminder < ::Jobs::Base
+ sidekiq_options retry: false
+
+ def execute(args)
+ raise Discourse::InvalidParameters.new(:event_id) if args[:event_id].blank?
+ raise Discourse::InvalidParameters.new(:reminder) if args[:reminder].blank?
+
+ event =
+ DiscoursePostEvent::Event.includes(post: [:topic], invitees: [:user]).find(args[:event_id])
+
+ return unless event.post
+
+ invitees =
+ event.invitees.where(
+ status: [
+ DiscoursePostEvent::Invitee.statuses[:going],
+ DiscoursePostEvent::Invitee.statuses[:interested],
+ ],
+ )
+
+ already_notified_users =
+ Notification.where(
+ read: false,
+ notification_type: Notification.types[:event_reminder] || Notification.types[:custom],
+ topic_id: event.post.topic_id,
+ post_number: 1,
+ )
+
+ event_started = Time.now > event.starts_at
+
+ # we remove users who have been visiting the topic since event started
+ if event_started
+ invitees =
+ invitees.where.not(
+ user_id:
+ TopicUser
+ .where(
+ "topic_users.topic_id = ? AND topic_users.last_visited_at >= ? AND topic_users.last_read_post_number >= ?",
+ event.post.topic_id,
+ event.starts_at,
+ 1,
+ )
+ .pluck(:user_id)
+ .concat(already_notified_users.pluck(:user_id)),
+ )
+ else
+ invitees = invitees.where.not(user_id: already_notified_users.pluck(:user_id))
+ end
+
+ event_ended = event.ends_at && Time.now > event.ends_at
+ prefix = "before"
+ if event_ended
+ prefix = "after"
+ elsif event_started && !event_ended
+ prefix = "ongoing"
+ end
+
+ invitees.find_each do |invitee|
+ attrs = {
+ notification_type: Notification.types[:event_reminder] || Notification.types[:custom],
+ topic_id: event.post.topic_id,
+ post_number: event.post.post_number,
+ data: {
+ topic_title: event.name || event.post.topic.title,
+ display_username: invitee.user.username,
+ message: "discourse_post_event.notifications.#{prefix}_event_reminder",
+ }.to_json,
+ }
+
+ invitee.user.notifications.consolidate_or_create!(attrs)
+
+ PostAlerter.new(event.post).create_notification_alert(
+ user: invitee.user,
+ post: event.post,
+ username: invitee.user.username,
+ notification_type: Notification.types[:event_reminder] || Notification.types[:custom],
+ excerpt:
+ I18n.t(
+ "discourse_post_event.notifications.#{prefix}_event_reminder",
+ title: event.name || event.post.topic.title,
+ locale: invitee.user.effective_locale,
+ ),
+ )
+ end
+ end
+ end
+end
diff --git a/plugins/discourse-calendar/jobs/scheduled/create_holiday_events.rb b/plugins/discourse-calendar/jobs/scheduled/create_holiday_events.rb
new file mode 100644
index 00000000000..c76439965d9
--- /dev/null
+++ b/plugins/discourse-calendar/jobs/scheduled/create_holiday_events.rb
@@ -0,0 +1,122 @@
+# frozen_string_literal: true
+
+module Jobs
+ class ::DiscourseCalendar::CreateHolidayEvents < ::Jobs::Scheduled
+ every 10.minutes
+
+ def execute(args)
+ return if !SiteSetting.calendar_enabled
+ return if !SiteSetting.calendar_automatic_holidays_enabled
+
+ return unless topic_id = SiteSetting.holiday_calendar_topic_id.presence
+
+ require "holidays" if !defined?(Holidays)
+
+ today = Date.today
+
+ regions_and_user_ids = Hash.new { |h, k| h[k] = [] }
+
+ UserCustomField
+ .where(name: ::DiscourseCalendar::REGION_CUSTOM_FIELD)
+ .pluck(:user_id, :value)
+ .each { |user_id, region| regions_and_user_ids[region] << user_id if region.present? }
+
+ usernames =
+ User
+ .real
+ .activated
+ .not_suspended
+ .not_silenced
+ .where(id: regions_and_user_ids.values.flatten)
+ .pluck(:id, :username)
+ .to_h
+
+ timezones =
+ UserOption
+ .where(user_id: usernames.keys)
+ .where.not(timezone: nil)
+ .pluck(:user_id, :timezone)
+ .map do |user_id, timezone|
+ [
+ user_id,
+ (
+ begin
+ TZInfo::Timezone.get(timezone)
+ rescue StandardError
+ nil
+ end
+ ),
+ ]
+ end
+ .to_h
+
+ # Remove holidays for deactivated/suspended/silenced users
+ CalendarEvent.where(post_id: nil).where.not(user_id: usernames.keys).destroy_all
+
+ # Remove future holidays when users changed their region
+ CalendarEvent
+ .joins(user: :_custom_fields)
+ .where(post_id: nil)
+ .where("start_date > ?", today)
+ .where("user_custom_fields.name = ?", ::DiscourseCalendar::REGION_CUSTOM_FIELD)
+ .where("LENGTH(COALESCE(user_custom_fields.value, '')) > 0")
+ .where("user_custom_fields.value != calendar_events.region")
+ .destroy_all
+
+ regions_and_user_ids.each do |region, user_ids|
+ DiscourseCalendar::Holiday
+ .find_holidays_for(
+ region_code: region,
+ start_date: today,
+ end_date: 6.months.from_now,
+ show_holiday_observed_on_dates: true,
+ )
+ .filter { |holiday| (1..5) === holiday[:date].wday && holiday[:disabled] === false }
+ .each do |holiday|
+ user_ids.each do |user_id|
+ next unless usernames[user_id]
+
+ date = holiday[:date]
+
+ if tz = timezones[user_id]
+ date = holiday[:date].in_time_zone(tz)
+ date = date.change(hour_adjustment) if hour_adjustment
+ end
+
+ event =
+ CalendarEvent
+ .where(topic_id: topic_id, user_id: user_id, description: holiday[:name])
+ .where(
+ "start_date >= :from AND start_date <= :to",
+ from: date - 1.day,
+ to: date + 1.day,
+ )
+ .first_or_initialize
+
+ event.update!(
+ topic_id: topic_id,
+ user_id: user_id,
+ description: holiday[:name],
+ start_date: date,
+ region: region,
+ username: usernames[user_id],
+ timezone: tz&.name,
+ )
+ end
+ end
+ end
+ end
+
+ def hour_adjustment
+ if SiteSetting.all_day_event_start_time.empty? || SiteSetting.all_day_event_end_time.empty?
+ return
+ end
+
+ @holiday_hour ||=
+ begin
+ split = SiteSetting.all_day_event_start_time.split(":")
+ { hour: split.first, min: split.second }
+ end
+ end
+ end
+end
diff --git a/plugins/discourse-calendar/jobs/scheduled/delete_expired_event_posts.rb b/plugins/discourse-calendar/jobs/scheduled/delete_expired_event_posts.rb
new file mode 100644
index 00000000000..b386a158c21
--- /dev/null
+++ b/plugins/discourse-calendar/jobs/scheduled/delete_expired_event_posts.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+module Jobs
+ class ::DiscourseCalendar::DeleteExpiredEventPosts < ::Jobs::Scheduled
+ every 10.minutes
+
+ def execute(args)
+ return unless SiteSetting.calendar_enabled
+
+ delay = SiteSetting.delete_expired_event_posts_after
+ return if delay < 0
+
+ calendar_topic_ids =
+ Post
+ .joins(:_custom_fields)
+ .where(post_custom_fields: { name: DiscourseCalendar::CALENDAR_CUSTOM_FIELD })
+ .pluck(:topic_id)
+
+ post_events =
+ CalendarEvent
+ .joins(:post, :topic)
+ .where(topic_id: calendar_topic_ids)
+ .where("TRIM(COALESCE(calendar_events.recurrence, '')) = ''")
+ .where("NOT topics.closed AND NOT topics.archived")
+
+ event_post_ids = post_events.pluck(:post_id).to_set
+
+ post_events.each do |event|
+ end_date = event.end_date.presence || event.start_date + 24.hours
+ next if end_date + delay.hour > Time.current
+
+ # get all the replies to the post
+ reply_ids = event.post.reply_ids(system_guardian)
+ replies = Post.where(id: reply_ids.map { |r| r[:id] })
+
+ # only delete replies that have no event
+ replies.each { |reply| destroy_post(reply) if !event_post_ids.include?(reply.id) }
+
+ # delete the post
+ destroy_post(event.post)
+ end
+ end
+
+ def destroy_post(post)
+ PostDestroyer.new(
+ Discourse.system_user,
+ post,
+ context: I18n.t("discourse_calendar.event_expired"),
+ ).destroy
+ end
+
+ def system_guardian
+ @system_guardian ||= Guardian.new(Discourse.system_user)
+ end
+ end
+end
diff --git a/plugins/discourse-calendar/jobs/scheduled/monitor_event_dates.rb b/plugins/discourse-calendar/jobs/scheduled/monitor_event_dates.rb
new file mode 100644
index 00000000000..a500264d8cb
--- /dev/null
+++ b/plugins/discourse-calendar/jobs/scheduled/monitor_event_dates.rb
@@ -0,0 +1,80 @@
+# frozen_string_literal: true
+module Jobs
+ class ::DiscourseCalendar::MonitorEventDates < ::Jobs::Scheduled
+ every 1.minute
+
+ def execute(args)
+ DiscoursePostEvent::EventDate.pending.find_each do |event_date|
+ send_reminder(event_date)
+ trigger_events(event_date)
+ finish(event_date)
+ end
+ end
+
+ def send_reminder(event_date)
+ due_reminders(event_date).each do |reminder|
+ ::Jobs.enqueue(
+ :discourse_post_event_send_reminder,
+ event_id: event_date.event.id,
+ reminder: reminder[:description],
+ )
+ event_date.update!(reminder_counter: event_date.reminder_counter + 1)
+ end
+ end
+
+ def trigger_events(event_date)
+ if event_date.starts_at - 1.hour <= Time.current && event_date.event_will_start_sent_at.blank?
+ event_date.update!(event_will_start_sent_at: DateTime.now)
+ DiscourseEvent.trigger(:discourse_post_event_event_will_start, event_date.event)
+ end
+
+ if event_date.started? && event_date.event_started_sent_at.blank?
+ event_date.update!(event_started_sent_at: DateTime.now)
+ DiscourseEvent.trigger(:discourse_post_event_event_started, event_date.event)
+ end
+ end
+
+ def finish(event_date)
+ return if !event_date.ended?
+ event_date.update!(finished_at: Time.current)
+
+ DiscourseEvent.trigger(:discourse_post_event_event_ended, event_date.event)
+ MessageBus.publish(
+ "/topic/#{event_date.event.post.topic_id}",
+ reload_topic: true,
+ refresh_stream: true,
+ )
+
+ return if event_date.event.recurrence.blank?
+ event_date.event.set_next_date
+ event_date.event.set_topic_bump
+ end
+
+ def due_reminders(event_date)
+ return [] if event_date.event.reminders.blank?
+ event_date
+ .event
+ .reminders
+ .split(",")
+ .map do |reminder|
+ unit, value, type = reminder.split(".").reverse
+
+ next if type === "bumpTopic" || !validate_reminder_unit(unit)
+ reminder = "notification.#{value}.#{unit}" if type.blank?
+
+ date = event_date.starts_at - value.to_i.public_send(unit)
+ { description: reminder, date: date }
+ end
+ .compact
+ .select { |reminder| reminder[:date] <= Time.current }
+ .sort_by { |reminder| reminder[:date] }
+ .drop(event_date.reminder_counter)
+ end
+
+ private
+
+ def validate_reminder_unit(input)
+ ActiveSupport::Duration::PARTS.any? { |part| part.to_s == input }
+ end
+ end
+end
diff --git a/plugins/discourse-calendar/jobs/scheduled/update_holiday_usernames.rb b/plugins/discourse-calendar/jobs/scheduled/update_holiday_usernames.rb
new file mode 100644
index 00000000000..b7c4b9a22bc
--- /dev/null
+++ b/plugins/discourse-calendar/jobs/scheduled/update_holiday_usernames.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+module Jobs
+ class ::DiscourseCalendar::UpdateHolidayUsernames < ::Jobs::Scheduled
+ every 10.minutes
+
+ def execute(args)
+ return unless SiteSetting.calendar_enabled
+ return unless topic_id = SiteSetting.holiday_calendar_topic_id.presence
+
+ events = CalendarEvent.where(topic_id: topic_id)
+ users_on_holiday = DiscourseCalendar::UsersOnHoliday.from(events)
+
+ DiscourseCalendar.users_on_holiday = users_on_holiday.values.map { |u| u[:username] }
+ synchronize_user_custom_fields(users_on_holiday)
+ set_holiday_statuses(users_on_holiday)
+ end
+
+ private
+
+ def synchronize_user_custom_fields(users_on_holiday)
+ custom_field_name = DiscourseCalendar::HOLIDAY_CUSTOM_FIELD
+
+ if users_on_holiday.present?
+ user_ids = users_on_holiday.keys
+ values = user_ids.map { |id| "(#{id}, '#{custom_field_name}', 't', now(), now())" }
+
+ DB.exec <<~SQL, custom_field_name
+ INSERT INTO user_custom_fields (user_id, name, value, created_at, updated_at)
+ VALUES #{values.join(",")}
+ ON CONFLICT (user_id, name) WHERE (name = ?) DO NOTHING
+ SQL
+
+ DB.exec <<~SQL, custom_field_name, user_ids
+ DELETE FROM user_custom_fields
+ WHERE name = ?
+ AND user_id NOT IN (?)
+ SQL
+ else
+ DB.exec("DELETE FROM user_custom_fields WHERE name = ?", custom_field_name)
+ end
+ end
+
+ def set_holiday_statuses(users_on_holiday)
+ return if !SiteSetting.enable_user_status
+
+ User
+ .where(id: users_on_holiday.keys)
+ .includes(:user_status)
+ .each { |u| DiscourseCalendar::HolidayStatus.set!(u, users_on_holiday[u.id][:ends_at]) }
+ end
+ end
+end
diff --git a/plugins/discourse-calendar/lib/calendar.rb b/plugins/discourse-calendar/lib/calendar.rb
new file mode 100644
index 00000000000..cde41ddb583
--- /dev/null
+++ b/plugins/discourse-calendar/lib/calendar.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+module DiscourseCalendar
+ class Calendar
+ DATA_PREFIX = "data-calendar-"
+
+ def self.extract(post)
+ cooked = PrettyText.cook(post.raw, topic_id: post.topic_id, user_id: post.user_id)
+
+ Nokogiri
+ .HTML(cooked)
+ .css("div.calendar")
+ .map do |cooked_calendar|
+ calendar = {}
+
+ cooked_calendar.attributes.values.each do |attribute|
+ if attribute.name.start_with?(DATA_PREFIX)
+ calendar[attribute.name[DATA_PREFIX.length..-1]] = CGI.escapeHTML(
+ attribute.value || "",
+ )
+ end
+ end
+
+ calendar
+ end
+ end
+
+ def self.update(post)
+ calendar = extract(post)
+ return destroy(post) if calendar.size != 1
+ calendar = calendar.first
+
+ post.custom_fields[DiscourseCalendar::CALENDAR_CUSTOM_FIELD] = calendar.delete("type") ||
+ "dynamic"
+ post.save_custom_fields
+
+ Post.where(topic_id: post.topic_id).each { |p| CalendarEvent.update(p) }
+ end
+
+ def self.destroy(post)
+ return if post.custom_fields[DiscourseCalendar::CALENDAR_CUSTOM_FIELD].blank?
+
+ post.custom_fields.delete(DiscourseCalendar::CALENDAR_CUSTOM_FIELD)
+ post.save_custom_fields
+
+ CalendarEvent.where(topic_id: post.topic_id).destroy_all
+ end
+ end
+end
diff --git a/plugins/discourse-calendar/lib/calendar_settings_validator.rb b/plugins/discourse-calendar/lib/calendar_settings_validator.rb
new file mode 100644
index 00000000000..ebb43200411
--- /dev/null
+++ b/plugins/discourse-calendar/lib/calendar_settings_validator.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+class CalendarSettingsValidator
+ def initialize(opts = {})
+ @opts = opts
+ end
+
+ def valid_value?(val)
+ return true if val == ""
+
+ split = val.split(":")
+ return false if split.count != 2
+
+ hour = split.first
+ return false if hour.length != 2
+ return false if hour.to_i < 0 || hour.to_i >= 24
+
+ minutes = split.second
+ return false if minutes.length != 2
+ return false if minutes.to_i < 0 || minutes.to_i >= 60
+ true
+ end
+
+ def error_message
+ I18n.t("site_settings.all_day_event_time_error")
+ end
+end
diff --git a/plugins/discourse-calendar/lib/calendar_validator.rb b/plugins/discourse-calendar/lib/calendar_validator.rb
new file mode 100644
index 00000000000..032255eb719
--- /dev/null
+++ b/plugins/discourse-calendar/lib/calendar_validator.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module DiscourseCalendar
+ class CalendarValidator
+ def initialize(post)
+ @post = post
+ end
+
+ def validate_calendar
+ extracted_calendars = DiscourseCalendar::Calendar.extract(@post)
+
+ return false if extracted_calendars.count == 0
+
+ if extracted_calendars.count > 1
+ @post.errors.add(:base, I18n.t("discourse_calendar.more_than_one_calendar"))
+ return false
+ end
+
+ if !@post.is_first_post?
+ @post.errors.add(:base, I18n.t("discourse_calendar.calendar_must_be_in_first_post"))
+ return false
+ end
+
+ true
+ end
+ end
+end
diff --git a/plugins/discourse-calendar/lib/discourse_calendar/engine.rb b/plugins/discourse-calendar/lib/discourse_calendar/engine.rb
new file mode 100644
index 00000000000..281db7e2c45
--- /dev/null
+++ b/plugins/discourse-calendar/lib/discourse_calendar/engine.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+module ::DiscourseCalendar
+ class Engine < ::Rails::Engine
+ engine_name PLUGIN_NAME
+ isolate_namespace DiscourseCalendar
+ end
+end
diff --git a/plugins/discourse-calendar/lib/discourse_calendar/site_settings/map_event_tag_colors_json_schema.rb b/plugins/discourse-calendar/lib/discourse_calendar/site_settings/map_event_tag_colors_json_schema.rb
new file mode 100644
index 00000000000..9d5918933d6
--- /dev/null
+++ b/plugins/discourse-calendar/lib/discourse_calendar/site_settings/map_event_tag_colors_json_schema.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+module DiscourseCalendar
+ module SiteSettings
+ class MapEventTagColorsJsonSchema
+ def self.schema
+ @schema ||= {
+ type: "array",
+ uniqueItems: true,
+ items: {
+ type: "object",
+ title: "Color Mapping",
+ properties: {
+ type: {
+ type: "string",
+ description: "Type of mapping (tag or category)",
+ enum: %w[tag category],
+ },
+ slug: {
+ type: "string",
+ description: "Slug of the tag or category",
+ },
+ color: {
+ type: "string",
+ format: "color",
+ default: "#FFFFFF",
+ description: "Color associated with the tag or category",
+ },
+ },
+ required: %w[slug type color],
+ },
+ }
+ end
+ end
+ end
+end
diff --git a/plugins/discourse-calendar/lib/discourse_calendar/site_settings/map_events_title_json_schema.rb b/plugins/discourse-calendar/lib/discourse_calendar/site_settings/map_events_title_json_schema.rb
new file mode 100644
index 00000000000..0c439e7dc2b
--- /dev/null
+++ b/plugins/discourse-calendar/lib/discourse_calendar/site_settings/map_events_title_json_schema.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+module DiscourseCalendar
+ module SiteSettings
+ class MapEventsTitleJsonSchema
+ def self.schema
+ @schema ||= {
+ type: "array",
+ uniqueItems: true,
+ items: {
+ type: "object",
+ title: "Title Mapping",
+ properties: {
+ category_slug: {
+ type: "string",
+ description: "Slug of the category",
+ },
+ custom_title: {
+ type: "string",
+ default: "Upcoming events",
+ description:
+ "The words you want to replace 'Upcoming Events' with for the sidebar calendar",
+ },
+ },
+ required: %w[category_slug custom_title],
+ },
+ }
+ end
+ end
+ end
+end
diff --git a/plugins/discourse-calendar/lib/discourse_post_event/engine.rb b/plugins/discourse-calendar/lib/discourse_post_event/engine.rb
new file mode 100644
index 00000000000..1fb8cc92a95
--- /dev/null
+++ b/plugins/discourse-calendar/lib/discourse_post_event/engine.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+module DiscoursePostEvent
+ class Engine < ::Rails::Engine
+ engine_name PLUGIN_NAME
+ isolate_namespace DiscoursePostEvent
+ end
+end
diff --git a/plugins/discourse-calendar/lib/discourse_post_event/event_finder.rb b/plugins/discourse-calendar/lib/discourse_post_event/event_finder.rb
new file mode 100644
index 00000000000..10f91a6b618
--- /dev/null
+++ b/plugins/discourse-calendar/lib/discourse_post_event/event_finder.rb
@@ -0,0 +1,95 @@
+# frozen_string_literal: true
+
+module DiscoursePostEvent
+ class EventFinder
+ def self.search(user, params = {})
+ guardian = Guardian.new(user)
+ topics = listable_topics(guardian)
+ pms = private_messages(user)
+
+ dates_join = <<~SQL
+ LEFT JOIN (
+ SELECT
+ finished_at,
+ event_id,
+ starts_at,
+ ROW_NUMBER() OVER (PARTITION BY event_id ORDER BY finished_at DESC NULLS FIRST) as row_num
+ FROM discourse_calendar_post_event_dates
+ ) dcped ON dcped.event_id = discourse_post_event_events.id AND dcped.row_num = 1
+
+ SQL
+ events =
+ DiscoursePostEvent::Event
+ .select("discourse_post_event_events.*, dcped.starts_at")
+ .joins(post: :topic)
+ .merge(Post.secured(guardian))
+ .merge(topics.or(pms).distinct)
+ .joins(dates_join)
+ .order("dcped.starts_at ASC")
+
+ include_expired = params[:include_expired].to_s == "true"
+
+ events = events.where("dcped.finished_at IS NULL") unless include_expired
+
+ events = events.where(id: Array(params[:post_id])) if params[:post_id]
+
+ if params[:attending_user].present?
+ attending_user = User.find_by(username_lower: params[:attending_user].downcase)
+ if attending_user
+ events =
+ events.joins(:invitees).where(
+ discourse_post_event_invitees: {
+ user_id: attending_user.id,
+ status: DiscoursePostEvent::Invitee.statuses[:going],
+ },
+ )
+
+ if !guardian.is_admin?
+ events =
+ events.where(
+ "discourse_post_event_events.status != ? OR discourse_post_event_events.status = ? AND EXISTS (
+ SELECT 1 FROM discourse_post_event_invitees dpoei
+ WHERE dpoei.post_id = discourse_post_event_events.id
+ AND dpoei.user_id = ?
+ )",
+ DiscoursePostEvent::Event.statuses[:private],
+ DiscoursePostEvent::Event.statuses[:private],
+ user&.id,
+ )
+ end
+ end
+ end
+
+ if params[:before].present?
+ events = events.where("dcped.starts_at < ?", params[:before].to_datetime)
+ end
+
+ if params[:category_id].present?
+ if params[:include_subcategories].present?
+ events =
+ events.where(
+ topics: {
+ category_id: Category.subcategory_ids(params[:category_id].to_i),
+ },
+ )
+ else
+ events = events.where(topics: { category_id: params[:category_id].to_i })
+ end
+ end
+
+ events = events.limit(params[:limit].to_i) if params[:limit].present?
+
+ events
+ end
+
+ private
+
+ def self.listable_topics(guardian)
+ Topic.listable_topics.secured(guardian)
+ end
+
+ def self.private_messages(user)
+ user ? Topic.private_messages_for_user(user) : Topic.none
+ end
+ end
+end
diff --git a/plugins/discourse-calendar/lib/discourse_post_event/event_parser.rb b/plugins/discourse-calendar/lib/discourse_post_event/event_parser.rb
new file mode 100644
index 00000000000..b1b742c2031
--- /dev/null
+++ b/plugins/discourse-calendar/lib/discourse_post_event/event_parser.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+module DiscoursePostEvent
+ class EventParser
+ VALID_OPTIONS = [
+ :start,
+ :end,
+ :status,
+ :"allowed-groups",
+ :url,
+ :location,
+ :name,
+ :reminders,
+ :recurrence,
+ :"recurrence-until",
+ :timezone,
+ :"show-local-time",
+ :minimal,
+ :closed,
+ :"chat-enabled",
+ ]
+
+ def self.extract_events(post)
+ cooked = PrettyText.cook(post.raw, topic_id: post.topic_id, user_id: post.user_id)
+ valid_options = VALID_OPTIONS.map { |o| "data-#{o}" }
+
+ valid_custom_fields = []
+ SiteSetting
+ .discourse_post_event_allowed_custom_fields
+ .split("|")
+ .each do |setting|
+ valid_custom_fields << {
+ original: "data-#{setting}",
+ normalized: "data-#{setting.gsub(/_/, "-")}",
+ }
+ end
+
+ Nokogiri
+ .HTML(cooked)
+ .css("div.discourse-post-event")
+ .map do |doc|
+ event = nil
+ doc.attributes.values.each do |attribute|
+ name = attribute.name
+ value = attribute.value
+
+ if value && valid_options.include?(name)
+ event ||= {}
+ event[name.sub("data-", "").to_sym] = if %w[data-name data-url].include?(name)
+ value
+ else
+ CGI.escapeHTML(value)
+ end
+ end
+
+ valid_custom_fields.each do |valid_custom_field|
+ if value && valid_custom_field[:normalized] == name
+ event ||= {}
+ event[valid_custom_field[:original].sub("data-", "").to_sym] = CGI.escapeHTML(value)
+ end
+ end
+ end
+ event[:description] = doc.text.strip if event
+ event
+ end
+ .compact
+ end
+ end
+end
diff --git a/plugins/discourse-calendar/lib/discourse_post_event/event_validator.rb b/plugins/discourse-calendar/lib/discourse_post_event/event_validator.rb
new file mode 100644
index 00000000000..233334d45a5
--- /dev/null
+++ b/plugins/discourse-calendar/lib/discourse_post_event/event_validator.rb
@@ -0,0 +1,178 @@
+# frozen_string_literal: true
+
+module DiscoursePostEvent
+ class EventValidator
+ VALID_RECURRENCES = %w[
+ every_month
+ every_week
+ every_two_weeks
+ every_four_weeks
+ every_day
+ every_weekday
+ ]
+
+ def initialize(post)
+ @post = post
+ end
+
+ def validate_event
+ extracted_events = DiscoursePostEvent::EventParser.extract_events(@post)
+
+ return false if extracted_events.count == 0
+
+ if extracted_events.count > 1
+ @post.errors.add(:base, I18n.t("discourse_post_event.errors.models.event.only_one_event"))
+ return false
+ end
+
+ if !@post.is_first_post?
+ @post.errors.add(
+ :base,
+ I18n.t("discourse_post_event.errors.models.event.must_be_in_first_post"),
+ )
+ return false
+ end
+
+ extracted_event = extracted_events.first
+
+ return false unless can_invite_groups?(extracted_event)
+
+ if @post.acting_user && @post.event
+ if !@post.acting_user.can_act_on_discourse_post_event?(@post.event)
+ @post.errors.add(
+ :base,
+ I18n.t(
+ "discourse_post_event.errors.models.event.acting_user_not_allowed_to_act_on_this_event",
+ ),
+ )
+ return false
+ end
+ else
+ if !@post.acting_user || !@post.acting_user.can_create_discourse_post_event?
+ @post.errors.add(
+ :base,
+ I18n.t(
+ "discourse_post_event.errors.models.event.acting_user_not_allowed_to_create_event",
+ ),
+ )
+ return false
+ end
+ end
+
+ if extracted_event[:start].blank? ||
+ (
+ begin
+ DateTime.parse(extracted_event[:start])
+ rescue StandardError
+ nil
+ end
+ ).nil?
+ @post.errors.add(
+ :base,
+ I18n.t("discourse_post_event.errors.models.event.start_must_be_present_and_a_valid_date"),
+ )
+ return false
+ end
+
+ if extracted_event[:end].present? &&
+ (
+ begin
+ DateTime.parse(extracted_event[:end])
+ rescue StandardError
+ nil
+ end
+ ).nil?
+ @post.errors.add(
+ :base,
+ I18n.t("discourse_post_event.errors.models.event.end_must_be_a_valid_date"),
+ )
+ return false
+ end
+
+ if extracted_event[:start].present? && extracted_event[:end].present?
+ if Time.parse(extracted_event[:start]) > Time.parse(extracted_event[:end])
+ @post.errors.add(
+ :base,
+ I18n.t("discourse_post_event.errors.models.event.ends_at_before_starts_at"),
+ )
+ return false
+ end
+ end
+
+ if extracted_event[:name].present?
+ if !(Event::MIN_NAME_LENGTH..Event::MAX_NAME_LENGTH).cover?(extracted_event[:name].length)
+ @post.errors.add(
+ :base,
+ I18n.t(
+ "discourse_post_event.errors.models.event.name.length",
+ minimum: Event::MIN_NAME_LENGTH,
+ maximum: Event::MAX_NAME_LENGTH,
+ ),
+ )
+ return false
+ end
+ end
+
+ if extracted_event[:recurrence].present?
+ if !VALID_RECURRENCES.include?(extracted_event[:recurrence].to_s)
+ @post.errors.add(
+ :base,
+ I18n.t("discourse_post_event.errors.models.event.invalid_recurrence"),
+ )
+ end
+ end
+
+ if extracted_event[:timezone].present?
+ if !ActiveSupport::TimeZone[extracted_event[:timezone]].present?
+ @post.errors.add(
+ :base,
+ I18n.t(
+ "discourse_post_event.errors.models.event.invalid_timezone",
+ timezone: extracted_event[:timezone],
+ ),
+ )
+ end
+ end
+
+ true
+ end
+
+ private
+
+ def can_invite_groups?(event)
+ guardian = Guardian.new(@post.acting_user)
+ return true unless event[:"allowed-groups"]
+
+ event[:"allowed-groups"]
+ .split(",")
+ .each do |group_name|
+ group =
+ begin
+ Group.lookup_group(group_name.to_sym)
+ rescue ArgumentError
+ nil
+ end
+
+ if !group || !guardian.can_see_group?(group)
+ @post.errors.add(
+ :base,
+ I18n.t("discourse_post_event.errors.models.event.invalid_allowed_groups"),
+ )
+ return false
+ end
+
+ if !guardian.can_see_group_members?(group)
+ @post.errors.add(
+ :base,
+ I18n.t(
+ "discourse_post_event.errors.models.event.acting_user_not_allowed_to_invite_these_groups",
+ ),
+ )
+ return false
+ end
+ end
+
+ true
+ end
+ end
+end
diff --git a/plugins/discourse-calendar/lib/discourse_post_event/export_csv_controller_extension.rb b/plugins/discourse-calendar/lib/discourse_post_event/export_csv_controller_extension.rb
new file mode 100644
index 00000000000..0a18302c540
--- /dev/null
+++ b/plugins/discourse-calendar/lib/discourse_post_event/export_csv_controller_extension.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+module DiscoursePostEvent
+ module ExportCsvControllerExtension
+ def export_entity
+ if post_event_export? && ensure_can_export_post_event
+ Jobs.enqueue(
+ :export_csv_file,
+ entity: export_params[:entity],
+ user_id: current_user.id,
+ args: export_params[:args],
+ )
+ StaffActionLogger.new(current_user).log_entity_export(export_params[:entity])
+ render json: success_json
+ else
+ super
+ end
+ end
+
+ private
+
+ def export_params
+ if post_event_export?
+ @_export_params ||=
+ begin
+ params.require(:entity)
+ params.permit(:entity, args: %i[id]).to_h
+ end
+ else
+ super
+ end
+ end
+
+ def post_event_export?
+ params[:entity] === "post_event"
+ end
+
+ def ensure_can_export_post_event
+ return if !SiteSetting.discourse_post_event_enabled
+
+ post_event = DiscoursePostEvent::Event.find(export_params[:args][:id])
+ post_event && guardian.can_act_on_discourse_post_event?(post_event)
+ end
+ end
+end
diff --git a/plugins/discourse-calendar/lib/discourse_post_event/export_csv_file_extension.rb b/plugins/discourse-calendar/lib/discourse_post_event/export_csv_file_extension.rb
new file mode 100644
index 00000000000..41c7cbf8db9
--- /dev/null
+++ b/plugins/discourse-calendar/lib/discourse_post_event/export_csv_file_extension.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+module DiscoursePostEvent
+ module ExportPostEventCsvReportExtension
+ def post_event_export(&block)
+ return enum_for(:post_event_export) unless block_given?
+
+ guardian = Guardian.new(current_user)
+
+ event = DiscoursePostEvent::Event.includes(invitees: :user).find(@extra[:id])
+
+ guardian.ensure_can_act_on_discourse_post_event!(event)
+
+ event
+ .invitees
+ .order(:id)
+ .each do |invitee|
+ yield(
+ [
+ invitee.user.username,
+ DiscoursePostEvent::Invitee.statuses[invitee.status],
+ invitee.created_at,
+ invitee.updated_at,
+ ]
+ )
+ end
+ end
+
+ def get_header(entity)
+ if SiteSetting.discourse_post_event_enabled && entity === "post_event"
+ %w[username status first_answered_at last_updated_at]
+ else
+ super
+ end
+ end
+ end
+end
diff --git a/plugins/discourse-calendar/lib/discourse_post_event/post_extension.rb b/plugins/discourse-calendar/lib/discourse_post_event/post_extension.rb
new file mode 100644
index 00000000000..3cba38c4e1a
--- /dev/null
+++ b/plugins/discourse-calendar/lib/discourse_post_event/post_extension.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+module DiscoursePostEvent
+ module PostExtension
+ extend ActiveSupport::Concern
+
+ prepended do
+ has_one :event, dependent: :destroy, class_name: "DiscoursePostEvent::Event", foreign_key: :id
+
+ validate :valid_event
+ end
+
+ def valid_event
+ return unless self.raw_changed?
+
+ validator = DiscoursePostEvent::EventValidator.new(self)
+ validator.validate_event
+ end
+ end
+end
diff --git a/plugins/discourse-calendar/lib/discourse_post_event/rrule_configurator.rb b/plugins/discourse-calendar/lib/discourse_post_event/rrule_configurator.rb
new file mode 100644
index 00000000000..ed2c32808d2
--- /dev/null
+++ b/plugins/discourse-calendar/lib/discourse_post_event/rrule_configurator.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+class RRuleConfigurator
+ def self.rule(recurrence:, starts_at:, recurrence_until: nil)
+ rule =
+ case recurrence
+ when "every_day"
+ "FREQ=DAILY"
+ when "every_month"
+ start_date = starts_at.beginning_of_month.to_date
+ end_date = starts_at.end_of_month.to_date
+ weekday = starts_at.strftime("%A")
+
+ count = 0
+ (start_date..end_date).each do |date|
+ count += 1 if date.strftime("%A") == weekday
+ break if date.day == starts_at.day
+ end
+
+ "FREQ=MONTHLY;BYDAY=#{count}#{weekday.upcase[0, 2]}"
+ when "every_weekday"
+ "FREQ=DAILY;BYDAY=MO,TU,WE,TH,FR"
+ when "every_two_weeks"
+ "FREQ=WEEKLY;INTERVAL=2;"
+ when "every_four_weeks"
+ "FREQ=WEEKLY;INTERVAL=4;"
+ else
+ byday = starts_at.strftime("%A").upcase[0, 2]
+ "FREQ=WEEKLY;BYDAY=#{byday}"
+ end
+
+ rule += ";UNTIL=#{recurrence_until.strftime("%Y%m%dT%H%M%SZ")}" if recurrence_until
+ rule
+ end
+
+ def self.how_many_recurring_events(recurrence:, max_years: nil)
+ return 1 if !max_years
+ per_year =
+ case recurrence
+ when "every_month"
+ 12
+ when "every_four_weeks"
+ 13
+ when "every_two_weeks"
+ 26
+ when "every_weekday"
+ 260
+ when "every_week"
+ 52
+ when "every_day"
+ 365
+ end
+ per_year * max_years
+ end
+end
diff --git a/plugins/discourse-calendar/lib/discourse_post_event/rrule_generator.rb b/plugins/discourse-calendar/lib/discourse_post_event/rrule_generator.rb
new file mode 100644
index 00000000000..b4568628dc9
--- /dev/null
+++ b/plugins/discourse-calendar/lib/discourse_post_event/rrule_generator.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+require "rrule"
+
+class RRuleGenerator
+ def self.generate(
+ starts_at:,
+ timezone: "UTC",
+ max_years: nil,
+ recurrence: "every_week",
+ recurrence_until: nil
+ )
+ rrule = generate_hash(RRuleConfigurator.rule(recurrence_until:, recurrence:, starts_at:))
+ rrule = set_mandatory_options(rrule, starts_at)
+
+ ::RRule::Rule
+ .new(stringify(rrule), dtstart: starts_at, tzid: timezone)
+ .between(Time.current, Time.current + 14.months)
+ .first(RRuleConfigurator.how_many_recurring_events(recurrence:, max_years:))
+ end
+
+ private
+
+ def self.stringify(rrule)
+ rrule.map { |k, v| "#{k}=#{v}" }.join(";")
+ end
+
+ def self.generate_hash(rrule)
+ rrule
+ .split(";")
+ .each_with_object({}) do |rr, h|
+ key, value = rr.split("=")
+ h[key] = value
+ end
+ end
+
+ def self.set_mandatory_options(rrule, time)
+ rrule["BYHOUR"] = time.strftime("%H")
+ rrule["BYMINUTE"] = time.strftime("%M")
+ rrule["INTERVAL"] ||= 1
+ rrule["WKST"] = "MO" # considers Monday as the first day of the week
+ rrule
+ end
+end
diff --git a/plugins/discourse-calendar/lib/event_validator.rb b/plugins/discourse-calendar/lib/event_validator.rb
new file mode 100644
index 00000000000..92127cf5cc6
--- /dev/null
+++ b/plugins/discourse-calendar/lib/event_validator.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+module DiscourseCalendar
+ class EventValidator
+ def initialize(post)
+ @post = post
+ @first_post = post.topic.first_post
+ end
+
+ def validate_event
+ dates_count = count_dates(@post)
+ calendar_type = @first_post.custom_fields[DiscourseCalendar::CALENDAR_CUSTOM_FIELD]
+
+ if calendar_type == "dynamic" && dates_count > 2
+ @post.errors.add(:base, I18n.t("discourse_calendar.more_than_two_dates"))
+ return false
+ end
+
+ return false if calendar_type == "static" && dates_count > 0
+
+ dates_count > 0
+ end
+
+ private
+
+ def count_dates(post)
+ cooked = PrettyText.cook(post.raw, topic_id: post.topic_id, user_id: post.user_id)
+ Nokogiri.HTML(cooked).css("span.discourse-local-date").count
+ end
+ end
+end
diff --git a/plugins/discourse-calendar/lib/group_timezones.rb b/plugins/discourse-calendar/lib/group_timezones.rb
new file mode 100644
index 00000000000..449a14777e7
--- /dev/null
+++ b/plugins/discourse-calendar/lib/group_timezones.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module DiscourseCalendar
+ class GroupTimezones
+ def self.update(post)
+ groups = []
+
+ Nokogiri
+ .HTML(post.cooked)
+ .css("div.group-timezones")
+ .map do |group_timezones|
+ group_timezones.attributes.values.each do |attribute|
+ if attribute.name == "data-group"
+ group_name = CGI.escapeHTML(attribute.value || "")
+ groups << group_name if group_name.present?
+ end
+ end
+ end
+
+ post.group_timezones = groups.present? ? { groups: groups } : nil
+ post.save_custom_fields
+ end
+ end
+end
diff --git a/plugins/discourse-calendar/lib/holiday_status.rb b/plugins/discourse-calendar/lib/holiday_status.rb
new file mode 100644
index 00000000000..c9dc6300b99
--- /dev/null
+++ b/plugins/discourse-calendar/lib/holiday_status.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module DiscourseCalendar
+ class HolidayStatus
+ def self.set!(user, ends_at)
+ status = user.user_status
+ if status.blank? || status.expired? ||
+ (is_holiday_status?(status) && status.ends_at != ends_at)
+ user.set_status!(
+ I18n.t("discourse_calendar.holiday_status.description"),
+ emoji_name,
+ ends_at,
+ )
+ end
+ end
+
+ def self.clear!(user)
+ user.clear_status! if user&.user_status && is_holiday_status?(user.user_status)
+ end
+
+ private
+
+ def self.is_holiday_status?(status)
+ status.emoji == emoji_name &&
+ status.description == I18n.t("discourse_calendar.holiday_status.description")
+ end
+
+ def self.emoji_name
+ emoji = SiteSetting.holiday_status_emoji
+ emoji.blank? ? "date" : emoji
+ end
+ end
+end
diff --git a/plugins/discourse-calendar/lib/tasks/javascript.rake b/plugins/discourse-calendar/lib/tasks/javascript.rake
new file mode 100644
index 00000000000..28f3f2cd434
--- /dev/null
+++ b/plugins/discourse-calendar/lib/tasks/javascript.rake
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+require "open-uri"
+
+task "javascript:update_constants" => :environment do
+ timezone_definitions =
+ "https://raw.githubusercontent.com/moment/moment-timezone/develop/data/meta/latest.json"
+
+ unused_regions = %w[
+ ecbtarget
+ federalreserve
+ federalreservebanks
+ fedex
+ nerc
+ unitednations
+ ups
+ nyse
+ ]
+
+ holidays_country_overrides = { "gr" => "el" }
+
+ require "holidays" if !defined?(Holidays)
+
+ holiday_regions = Holidays.available_regions.map(&:to_s) - unused_regions
+
+ time_zone_to_region = {}
+ data = JSON.parse(URI.parse(timezone_definitions).open.read)
+ data["zones"].sort.each do |timezone, timezone_data|
+ country_code = timezone_data["countries"].first.downcase
+
+ if holidays_country_overrides.include?(country_code)
+ country_code = holidays_country_overrides[country_code]
+ end
+
+ next if !holiday_regions.include?(country_code)
+ time_zone_to_region[timezone] = country_code
+ end
+
+ write_template(
+ "../../../plugins/discourse-calendar/assets/javascripts/discourse/lib/regions.js",
+ "update_constants",
+ <<~JS,
+ export const HOLIDAY_REGIONS = #{holiday_regions.to_json};
+
+ export const TIME_ZONE_TO_REGION = #{time_zone_to_region.to_json};
+ JS
+ )
+end
diff --git a/plugins/discourse-calendar/lib/time_sniffer.rb b/plugins/discourse-calendar/lib/time_sniffer.rb
new file mode 100644
index 00000000000..5be385ed0d4
--- /dev/null
+++ b/plugins/discourse-calendar/lib/time_sniffer.rb
@@ -0,0 +1,322 @@
+# frozen_string_literal: true
+
+class TimeSniffer
+ Interval = Struct.new(:from, :to)
+ Event = Struct.new(:at)
+
+ Context = Struct.new(:at, :timezone, :date_order)
+
+ class SniffedTime
+ attr_reader :year
+ attr_reader :month
+ attr_reader :day
+ attr_reader :hours
+ attr_reader :minutes
+ attr_reader :seconds
+ attr_reader :zone
+
+ def initialize(year:, month:, day:, hours: 0, minutes: 0, seconds: 0, zone:)
+ @year = year
+ @month = month
+ @day = day
+ @hours = hours
+ @minutes = minutes
+ @seconds = seconds
+ @zone = zone
+ end
+
+ def self.from_datetime(obj, zone)
+ new(
+ year: obj.year,
+ month: obj.month,
+ day: obj.day,
+ hours: obj.hour,
+ minutes: obj.minute,
+ seconds: obj.second,
+ zone: zone,
+ )
+ end
+
+ def to_time
+ Time.use_zone(self.zone) do
+ Time.zone.parse(
+ "#{self.year}-#{self.month}-#{self.day} #{self.hours}:#{self.minutes}:#{self.seconds}",
+ )
+ end
+ end
+
+ def with(**args)
+ SniffedTime.new(**to_hash.merge(args))
+ end
+
+ def to_hash
+ {
+ year: self.year,
+ month: self.month,
+ day: self.day,
+ hours: self.hours,
+ minutes: self.minutes,
+ seconds: self.seconds,
+ zone: self.zone,
+ }
+ end
+
+ def ==(other)
+ return false unless other.kind_of?(SniffedTime)
+ return false if @year != other.year
+ return false if @month != other.month
+ return false if @day != other.day
+ return false if @hours != other.hours
+ return false if @minutes != other.minutes
+ return false if @seconds != other.seconds
+ return false if @zone != other.zone
+ true
+ end
+ end
+
+ class << self
+ def matchers
+ @matchers ||= {}
+ end
+
+ def matcher(name, regex, &blk)
+ matchers[name] = { regex: regex, blk: blk }
+ end
+ end
+
+ class Parser
+ UTC_REGEX = / ?(Z|UTC)/
+
+ def initialize(input, context)
+ @input = input
+ @context = context
+ @offset = 0
+ end
+
+ def parse_timezone
+ m = input_from_offset.match(UTC_REGEX)
+ if m && m.offset(0)[0] == 0
+ self.offset += m.offset(0)[1]
+ "UTC"
+ end
+ end
+
+ def parse_space
+ if input[offset] == " "
+ self.offset += 1
+ true
+ else
+ false
+ end
+ end
+
+ def parse_time(relative_to, immediate:)
+ time, start_offset, stop_offset = peek_time(relative_to)
+ if time && (!immediate || start_offset == 0)
+ self.offset += stop_offset
+ time
+ end
+ end
+
+ def parse_date
+ date_match = DATE_REGEX.match(input_from_offset)
+ if date_match
+ day, month =
+ case @context.date_order
+ when :us
+ [date_match[2], date_match[1]]
+ when :sane
+ [date_match[1], date_match[2]]
+ end
+
+ year = date_match[3]
+ year =
+ case year.size
+ when 2
+ century = @context.at.year - (@context.at.year % 100)
+ last_century = century - 100
+
+ choices = [century + year.to_i, last_century + year.to_i]
+
+ choices.sort_by { |x| (@context.at.year - x).abs }[0]
+ when 4
+ year.to_i
+ end
+
+ result =
+ SniffedTime.new(year: year, month: month.to_i, day: day.to_i, zone: @context.timezone)
+
+ self.offset += date_match.offset(0)[1]
+ result
+ end
+ end
+
+ def parse_time_with_timezone(relative_to, immediate:)
+ result = parse_time(relative_to, immediate: immediate)
+ if result
+ zone = parse_timezone
+
+ result = result.with(zone: zone) if zone
+
+ result
+ end
+ end
+
+ def parse_date_time(relative_to)
+ date = parse_date
+ if date
+ if parse_space
+ datetime = parse_time_with_timezone(date, immediate: true)
+ datetime ? [false, datetime] : [true, date]
+ else
+ [true, date]
+ end
+ elsif relative_to
+ datetime = parse_time_with_timezone(relative_to, immediate: false)
+ datetime ? [false, datetime] : [true, nil]
+ end
+ end
+
+ def parse_range
+ if x = parse_date_time(nil)
+ from_is_date, from = x
+ to_is_date, to = parse_date_time(from)
+
+ if to
+ if to_is_date
+ Interval.new(from.to_time, to.to_time + 1.day)
+ else
+ Interval.new(from.to_time, to.to_time)
+ end
+ else
+ from_is_date ? Interval.new(from.to_time, from.to_time + 1.day) : Event.new(from.to_time)
+ end
+ end
+ end
+
+ def input_from_offset
+ self.input[self.offset..-1]
+ end
+
+ def peek_time(relative_to)
+ m = self.input_from_offset.match(TIME_REGEX)
+ if m
+ parsed =
+ relative_to.with(
+ hours: m[1].to_i,
+ minutes: m[2].to_i,
+ seconds: 0,
+ zone: @context.timezone,
+ )
+
+ [parsed, *m.offset(0)]
+ end
+ end
+
+ attr_reader :input
+ attr_accessor :offset
+ end
+
+ matcher(:yesterday, /yesterday/) do |m|
+ today = at.to_date
+ yesterday = today - 1
+
+ Interval.new(
+ SniffedTime.from_datetime(yesterday.to_datetime, timezone).to_time,
+ SniffedTime.from_datetime(today.to_datetime, timezone).to_time,
+ )
+ end
+
+ matcher(:tomorrow, /tomorrow/i) do |_|
+ tomorrow = at.to_date + 1
+ the_day_after_tomorrow = tomorrow + 1
+
+ Interval.new(
+ SniffedTime.from_datetime(tomorrow.to_datetime, timezone).to_time,
+ SniffedTime.from_datetime(the_day_after_tomorrow.to_datetime, timezone).to_time,
+ )
+ end
+
+ TIME_REGEX = /(\d{1,2}):(\d{2})/
+
+ matcher(:time, TIME_REGEX) do |m|
+ times = input.scan(TIME_REGEX).to_a
+ from, to = times[0..2]
+ if to
+ Interval.new(
+ SniffedTime.new(
+ year: at.year,
+ month: at.month,
+ day: at.day,
+ hours: from[0].to_i,
+ minutes: from[1].to_i,
+ seconds: 0,
+ zone: timezone,
+ ).to_time,
+ SniffedTime.new(
+ year: at.year,
+ month: at.month,
+ day: at.day,
+ hours: to[0].to_i,
+ minutes: to[1].to_i,
+ seconds: 0,
+ zone: timezone,
+ ).to_time,
+ )
+ else
+ Event.new(
+ SniffedTime.new(
+ year: at.year,
+ month: at.month,
+ day: at.day,
+ hours: from[0].to_i,
+ minutes: from[1].to_i,
+ seconds: 0,
+ zone: timezone,
+ ).to_time,
+ )
+ end
+ end
+
+ DATE_SEPARATOR = %r{[-/]}
+ DATE_REGEX = /((?:^|\s)\d{1,2})#{DATE_SEPARATOR}(\d{1,2})#{DATE_SEPARATOR}(\d{2,4})/
+
+ matcher(:date, DATE_REGEX) { |m| Parser.new(input, @context).parse_range }
+
+ def initialize(input, at: DateTime.now, timezone:, date_order:, matchers:, raise_errors: false)
+ @input = input
+ @at = at
+ @timezone = timezone
+ @date_order = date_order
+ @context = Context.new(@at, @timezone, @date_order)
+ @matchers = matchers
+ @raise_errors = raise_errors
+ end
+
+ def sniff
+ @matchers.each do |matcher_name|
+ matcher = self.class.matchers[matcher_name]
+ regex, blk = matcher.values_at(:regex, :blk)
+
+ match = regex.match(@input)
+ if match
+ begin
+ result = instance_exec(match, &blk)
+ rescue Exception => e
+ raise if @raise_errors
+ else
+ return result if result
+ end
+ end
+ end
+
+ nil
+ end
+
+ private
+
+ attr_reader :input
+ attr_reader :at
+ attr_reader :timezone
+ attr_reader :date_order
+end
diff --git a/plugins/discourse-calendar/lib/users_on_holiday.rb b/plugins/discourse-calendar/lib/users_on_holiday.rb
new file mode 100644
index 00000000000..8221e94d49e
--- /dev/null
+++ b/plugins/discourse-calendar/lib/users_on_holiday.rb
@@ -0,0 +1,70 @@
+# frozen_string_literal: true
+
+module DiscourseCalendar
+ class UsersOnHoliday
+ def self.from(calendar_events)
+ calendar_events
+ .filter { |e| e.user_id.present? && e.username.present? }
+ .filter { |e| e.underway? || e.in_future? }
+ .group_by(&:user_id)
+ .map { |_, events| current_holiday(events) }
+ .compact
+ .to_h
+ end
+
+ private
+
+ def self.current_holiday(user_events)
+ ends_at = holiday_ends_at(user_events)
+ return nil unless ends_at
+
+ [user_events[0].user_id, { username: user_events[0].username, ends_at: ends_at }]
+ end
+
+ # If a user has several holidays one after another
+ # we want to show the farthest end date.
+ #
+ # Let's say today is Monday and I am sick,
+ # and I also have days off from Tuesday to Friday:
+ #
+ # sick ▭
+ # days off ▭▭▭▭
+ #
+ # We want to show Friday as an end date of my holiday.
+ #
+ # This algorithm also works in case the holidays intersect,
+ # like this:
+ #
+ # event_1 ▭▭▭▭
+ # event_2 ▭▭▭▭
+ # event_3 ▭▭▭▭
+ #
+ # or like this:
+ #
+ # event_1 ▭▭▭▭
+ # event_2 ▭▭▭▭▭▭▭▭
+ # event_3 ▭▭▭▭▭▭▭▭▭▭
+ #
+ # or like this:
+ #
+ # event_1 ▭▭▭▭▭▭▭▭▭▭
+ # event_2 ▭
+ #
+ def self.holiday_ends_at(events)
+ sorted_events = events.sort_by(&:start_date)
+ return nil if sorted_events.first.in_future?
+ return sorted_events.first.ends_at if events.count == 1
+
+ result = sorted_events.first.ends_at
+ sorted_events.each_cons(2) do |pair|
+ if pair[0].ends_at < pair[1].start_date
+ return result
+ elsif pair[1].ends_at > result
+ result = pair[1].ends_at
+ end
+ end
+
+ result
+ end
+ end
+end
diff --git a/plugins/discourse-calendar/plugin.rb b/plugins/discourse-calendar/plugin.rb
new file mode 100644
index 00000000000..1514f13f26e
--- /dev/null
+++ b/plugins/discourse-calendar/plugin.rb
@@ -0,0 +1,608 @@
+# frozen_string_literal: true
+
+# name: discourse-calendar
+# about: Adds the ability to create a dynamic calendar with events in a topic.
+# meta_topic_id: 97376
+# version: 0.5
+# author: Daniel Waterworth, Joffrey Jaffeux
+# url: https://github.com/discourse/discourse/tree/main/plugins/discourse-calendar
+
+libdir = File.join(File.dirname(__FILE__), "vendor/holidays/lib")
+$LOAD_PATH.unshift(libdir) if $LOAD_PATH.exclude?(libdir)
+
+require_relative "lib/calendar_settings_validator.rb"
+
+enabled_site_setting :calendar_enabled
+
+register_asset "stylesheets/vendor/fullcalendar.min.css"
+register_asset "stylesheets/common/discourse-calendar.scss"
+register_asset "stylesheets/common/discourse-calendar-holidays.scss"
+register_asset "stylesheets/common/upcoming-events-calendar.scss"
+register_asset "stylesheets/common/discourse-post-event.scss"
+register_asset "stylesheets/common/discourse-post-event-preview.scss"
+register_asset "stylesheets/common/post-event-builder.scss"
+register_asset "stylesheets/common/discourse-post-event-invitees.scss"
+register_asset "stylesheets/common/discourse-post-event-upcoming-events.scss"
+register_asset "stylesheets/common/discourse-post-event-core-ext.scss"
+register_asset "stylesheets/mobile/discourse-post-event-core-ext.scss", :mobile
+register_asset "stylesheets/common/discourse-post-event-bulk-invite-modal.scss"
+register_asset "stylesheets/mobile/discourse-calendar.scss", :mobile
+register_asset "stylesheets/mobile/discourse-post-event.scss", :mobile
+register_asset "stylesheets/desktop/discourse-calendar.scss", :desktop
+register_asset "stylesheets/colors.scss", :color_definitions
+register_asset "stylesheets/common/user-preferences.scss"
+register_asset "stylesheets/common/upcoming-events-list.scss"
+register_svg_icon "calendar-day"
+register_svg_icon "clock"
+register_svg_icon "file-csv"
+register_svg_icon "star"
+register_svg_icon "file-arrow-up"
+register_svg_icon "location-pin"
+
+module ::DiscourseCalendar
+ PLUGIN_NAME = "discourse-calendar"
+
+ # Type of calendar ('static' or 'dynamic')
+ CALENDAR_CUSTOM_FIELD = "calendar"
+
+ # User custom field set when user is on holiday
+ HOLIDAY_CUSTOM_FIELD = "on_holiday"
+
+ # List of all users on holiday
+ USERS_ON_HOLIDAY_KEY = "users_on_holiday"
+
+ # User region used in finding holidays
+ REGION_CUSTOM_FIELD = "holidays-region"
+
+ # List of groups
+ GROUP_TIMEZONES_CUSTOM_FIELD = "group-timezones"
+
+ def self.users_on_holiday
+ PluginStore.get(PLUGIN_NAME, USERS_ON_HOLIDAY_KEY) || []
+ end
+
+ def self.users_on_holiday=(usernames)
+ PluginStore.set(PLUGIN_NAME, USERS_ON_HOLIDAY_KEY, usernames)
+ end
+end
+
+module ::DiscoursePostEvent
+ PLUGIN_NAME = "discourse-post-event"
+
+ # Topic where op has a post event custom field
+ TOPIC_POST_EVENT_STARTS_AT = "TopicEventStartsAt"
+ TOPIC_POST_EVENT_ENDS_AT = "TopicEventEndsAt"
+end
+
+require_relative "lib/discourse_calendar/engine"
+
+Dir
+ .glob(File.expand_path("../lib/discourse_calendar/site_settings/*.rb", __FILE__))
+ .each { |f| require(f) }
+
+after_initialize do
+ reloadable_patch do
+ Category.register_custom_field_type("sort_topics_by_event_start_date", :boolean)
+ Category.register_custom_field_type("disable_topic_resorting", :boolean)
+ if respond_to?(:register_preloaded_category_custom_fields)
+ register_preloaded_category_custom_fields("sort_topics_by_event_start_date")
+ register_preloaded_category_custom_fields("disable_topic_resorting")
+ else
+ # TODO: Drop the if-statement and this if-branch in Discourse v3.2
+ Site.preloaded_category_custom_fields << "sort_topics_by_event_start_date"
+ Site.preloaded_category_custom_fields << "disable_topic_resorting"
+ end
+ end
+
+ add_to_serializer :basic_category, :sort_topics_by_event_start_date do
+ object.custom_fields["sort_topics_by_event_start_date"]
+ end
+
+ add_to_serializer :basic_category, :disable_topic_resorting do
+ object.custom_fields["disable_topic_resorting"]
+ end
+
+ reloadable_patch do
+ TopicQuery.add_custom_filter(:order_by_event_date) do |results, topic_query|
+ if SiteSetting.sort_categories_by_event_start_date_enabled &&
+ topic_query.options[:category_id]
+ category = Category.find_by(id: topic_query.options[:category_id])
+ if category && category.custom_fields &&
+ category.custom_fields["sort_topics_by_event_start_date"]
+ reorder_sql = <<~SQL
+ CASE WHEN COALESCE(custom_fields.value::timestamptz, topics.bumped_at) > NOW() THEN 0 ELSE 1 END,
+ CASE WHEN COALESCE(custom_fields.value::timestamptz, topics.bumped_at) > NOW() THEN COALESCE(custom_fields.value::timestamptz, topics.bumped_at) ELSE NULL END,
+ CASE WHEN COALESCE(custom_fields.value::timestamptz, topics.bumped_at) < NOW() THEN COALESCE(custom_fields.value::timestamptz, topics.bumped_at) ELSE NULL END DESC
+ SQL
+ results =
+ results.joins(
+ "LEFT JOIN topic_custom_fields AS custom_fields on custom_fields.topic_id = topics.id
+ AND custom_fields.name = '#{DiscoursePostEvent::TOPIC_POST_EVENT_STARTS_AT}'
+ ",
+ ).reorder(reorder_sql)
+ end
+ end
+ results
+ end
+ end
+
+ # DISCOURSE CALENDAR HOLIDAYS
+
+ add_admin_route "admin.calendar", "calendar"
+
+ # DISCOURSE POST EVENT
+
+ require_relative "jobs/regular/discourse_post_event/bulk_invite"
+ require_relative "jobs/regular/discourse_post_event/bump_topic"
+ require_relative "jobs/regular/discourse_post_event/send_reminder"
+ require_relative "lib/discourse_post_event/engine"
+ require_relative "lib/discourse_post_event/event_finder"
+ require_relative "lib/discourse_post_event/event_parser"
+ require_relative "lib/discourse_post_event/event_validator"
+ require_relative "lib/discourse_post_event/export_csv_controller_extension"
+ require_relative "lib/discourse_post_event/export_csv_file_extension"
+ require_relative "lib/discourse_post_event/post_extension"
+ require_relative "lib/discourse_post_event/rrule_generator"
+ require_relative "lib/discourse_post_event/rrule_configurator"
+
+ ::ActionController::Base.prepend_view_path File.expand_path("../app/views", __FILE__)
+
+ reloadable_patch do
+ ExportCsvController.prepend(DiscoursePostEvent::ExportCsvControllerExtension)
+ Jobs::ExportCsvFile.prepend(DiscoursePostEvent::ExportPostEventCsvReportExtension)
+ Post.prepend(DiscoursePostEvent::PostExtension)
+ end
+
+ add_to_class(:user, :can_create_discourse_post_event?) do
+ return @can_create_discourse_post_event if defined?(@can_create_discourse_post_event)
+ @can_create_discourse_post_event =
+ begin
+ return true if staff?
+ allowed_groups = SiteSetting.discourse_post_event_allowed_on_groups.to_s.split("|").compact
+ allowed_groups.present? &&
+ (
+ allowed_groups.include?(Group::AUTO_GROUPS[:everyone].to_s) ||
+ groups.where(id: allowed_groups).exists?
+ )
+ rescue StandardError
+ false
+ end
+ end
+
+ add_to_class(:guardian, :can_act_on_invitee?) do |invitee|
+ user && (user.staff? || user.id == invitee.user_id)
+ end
+
+ add_to_class(:guardian, :can_create_discourse_post_event?) do
+ user && user.can_create_discourse_post_event?
+ end
+
+ add_to_serializer(:current_user, :can_create_discourse_post_event) do
+ object.can_create_discourse_post_event?
+ end
+
+ add_to_class(:user, :can_act_on_discourse_post_event?) do |event|
+ return @can_act_on_discourse_post_event if defined?(@can_act_on_discourse_post_event)
+ @can_act_on_discourse_post_event =
+ begin
+ return true if staff?
+ can_create_discourse_post_event? && Guardian.new(self).can_edit_post?(event.post)
+ rescue StandardError
+ false
+ end
+ end
+
+ add_to_class(:guardian, :can_act_on_discourse_post_event?) do |event|
+ user && user.can_act_on_discourse_post_event?(event)
+ end
+
+ add_class_method(:group, :discourse_post_event_allowed_groups) do
+ where(id: SiteSetting.discourse_post_event_allowed_on_groups.split("|").compact)
+ end
+
+ TopicView.on_preload do |topic_view|
+ if SiteSetting.discourse_post_event_enabled
+ topic_view.instance_variable_set(:@posts, topic_view.posts.includes(:event))
+ end
+ end
+
+ add_to_serializer(
+ :post,
+ :event,
+ include_condition: -> do
+ SiteSetting.discourse_post_event_enabled && !object.nil? && !object.deleted_at.present?
+ end,
+ ) { DiscoursePostEvent::EventSerializer.new(object.event, scope: scope, root: false) }
+
+ on(:post_created) { |post| DiscoursePostEvent::Event.update_from_raw(post) }
+
+ on(:post_edited) { |post| DiscoursePostEvent::Event.update_from_raw(post) }
+
+ on(:post_destroyed) do |post|
+ if SiteSetting.discourse_post_event_enabled && post.event
+ post.event.update!(deleted_at: Time.now)
+ end
+ end
+
+ on(:post_recovered) do |post|
+ post.event.update!(deleted_at: nil) if SiteSetting.discourse_post_event_enabled && post.event
+ end
+
+ add_preloaded_topic_list_custom_field DiscoursePostEvent::TOPIC_POST_EVENT_STARTS_AT
+
+ add_to_serializer(
+ :topic_view,
+ :event_starts_at,
+ include_condition: -> do
+ SiteSetting.discourse_post_event_enabled &&
+ SiteSetting.display_post_event_date_on_topic_title &&
+ object.topic.custom_fields.keys.include?(DiscoursePostEvent::TOPIC_POST_EVENT_STARTS_AT)
+ end,
+ ) { object.topic.custom_fields[DiscoursePostEvent::TOPIC_POST_EVENT_STARTS_AT] }
+
+ add_to_class(:topic, :event_starts_at) do
+ @event_starts_at ||= custom_fields[DiscoursePostEvent::TOPIC_POST_EVENT_STARTS_AT]
+ end
+
+ add_to_serializer(
+ :topic_list_item,
+ :event_starts_at,
+ include_condition: -> do
+ SiteSetting.discourse_post_event_enabled &&
+ SiteSetting.display_post_event_date_on_topic_title && object.event_starts_at
+ end,
+ ) { object.event_starts_at }
+
+ add_preloaded_topic_list_custom_field DiscoursePostEvent::TOPIC_POST_EVENT_ENDS_AT
+
+ add_to_serializer(
+ :topic_view,
+ :event_ends_at,
+ include_condition: -> do
+ SiteSetting.discourse_post_event_enabled &&
+ SiteSetting.display_post_event_date_on_topic_title &&
+ object.topic.custom_fields.keys.include?(DiscoursePostEvent::TOPIC_POST_EVENT_ENDS_AT)
+ end,
+ ) { object.topic.custom_fields[DiscoursePostEvent::TOPIC_POST_EVENT_ENDS_AT] }
+
+ add_to_class(:topic, :event_ends_at) do
+ @event_ends_at ||= custom_fields[DiscoursePostEvent::TOPIC_POST_EVENT_ENDS_AT]
+ end
+
+ add_to_serializer(
+ :topic_list_item,
+ :event_ends_at,
+ include_condition: -> do
+ SiteSetting.discourse_post_event_enabled &&
+ SiteSetting.display_post_event_date_on_topic_title && object.event_ends_at
+ end,
+ ) { object.event_ends_at }
+
+ # DISCOURSE CALENDAR
+
+ require_relative "jobs/scheduled/create_holiday_events"
+ require_relative "jobs/scheduled/delete_expired_event_posts"
+ require_relative "jobs/scheduled/monitor_event_dates"
+ require_relative "jobs/scheduled/update_holiday_usernames"
+ require_relative "lib/calendar_validator"
+ require_relative "lib/calendar"
+ require_relative "lib/event_validator"
+ require_relative "lib/group_timezones"
+ require_relative "lib/holiday_status"
+ require_relative "lib/time_sniffer"
+ require_relative "lib/users_on_holiday"
+
+ register_post_custom_field_type(DiscourseCalendar::CALENDAR_CUSTOM_FIELD, :string)
+ register_post_custom_field_type(DiscourseCalendar::GROUP_TIMEZONES_CUSTOM_FIELD, :json)
+ TopicView.default_post_custom_fields << DiscourseCalendar::GROUP_TIMEZONES_CUSTOM_FIELD
+
+ register_user_custom_field_type(DiscourseCalendar::HOLIDAY_CUSTOM_FIELD, :boolean)
+
+ allow_staff_user_custom_field(DiscourseCalendar::HOLIDAY_CUSTOM_FIELD)
+ DiscoursePluginRegistry.serialized_current_user_fields << DiscourseCalendar::REGION_CUSTOM_FIELD
+ register_editable_user_custom_field(DiscourseCalendar::REGION_CUSTOM_FIELD)
+ register_user_custom_field_type(DiscourseCalendar::REGION_CUSTOM_FIELD, :string, max_length: 40)
+
+ on(:site_setting_changed) do |name, old_value, new_value|
+ next if %i[all_day_event_start_time all_day_event_end_time].exclude? name
+
+ Post
+ .where(id: CalendarEvent.select(:post_id).distinct)
+ .each { |post| CalendarEvent.update(post) }
+ end
+
+ on(:post_process_cooked) do |doc, post|
+ DiscourseCalendar::Calendar.update(post)
+ DiscourseCalendar::GroupTimezones.update(post)
+ CalendarEvent.update(post)
+ end
+
+ on(:post_recovered) do |post, _, _|
+ DiscourseCalendar::Calendar.update(post)
+ DiscourseCalendar::GroupTimezones.update(post)
+ CalendarEvent.update(post)
+ end
+
+ on(:post_destroyed) do |post, _, _|
+ DiscourseCalendar::Calendar.destroy(post)
+ CalendarEvent.where(post_id: post.id).destroy_all
+ end
+
+ validate(:post, :validate_calendar) do |force = nil|
+ return unless self.raw_changed? || force
+
+ validator = DiscourseCalendar::CalendarValidator.new(self)
+ validator.validate_calendar
+ end
+
+ validate(:post, :validate_event) do |force = nil|
+ return unless self.raw_changed? || force
+ return if self.is_first_post?
+
+ # Skip if not a calendar topic
+ return if !self.topic&.first_post&.custom_fields&.[](DiscourseCalendar::CALENDAR_CUSTOM_FIELD)
+
+ validator = DiscourseCalendar::EventValidator.new(self)
+ validator.validate_event
+ end
+
+ add_to_class(:post, :has_group_timezones?) do
+ custom_fields[DiscourseCalendar::GROUP_TIMEZONES_CUSTOM_FIELD].present?
+ end
+
+ add_to_class(:post, :group_timezones) do
+ custom_fields[DiscourseCalendar::GROUP_TIMEZONES_CUSTOM_FIELD] || {}
+ end
+
+ add_to_class(:post, :group_timezones=) do |val|
+ if val.present?
+ custom_fields[DiscourseCalendar::GROUP_TIMEZONES_CUSTOM_FIELD] = val
+ else
+ custom_fields.delete(DiscourseCalendar::GROUP_TIMEZONES_CUSTOM_FIELD)
+ end
+ end
+
+ add_to_serializer(:post, :calendar_details, include_condition: -> { object.is_first_post? }) do
+ start_date = 6.months.ago
+
+ standalone_sql = <<~SQL
+ SELECT post_number, description, start_date, end_date, username, recurrence, timezone
+ FROM calendar_events
+ WHERE topic_id = :topic_id
+ AND post_id IS NOT NULL
+ ORDER BY start_date, end_date
+ SQL
+
+ standalones =
+ DB
+ .query(standalone_sql, topic_id: object.topic_id)
+ .map do |row|
+ {
+ type: :standalone,
+ post_number: row.post_number,
+ message: row.description,
+ from: row.start_date,
+ to: row.end_date,
+ username: row.username,
+ recurring: row.recurrence,
+ post_url: Post.url("-", object.topic_id, row.post_number),
+ timezone: row.timezone,
+ }
+ end
+
+ timezones =
+ UserOption
+ .where(
+ user_id:
+ CalendarEvent.where(
+ topic_id: object.topic_id,
+ post_id: nil,
+ start_date: start_date..,
+ ).select(:user_id),
+ )
+ .where("LENGTH(COALESCE(timezone, '')) > 0")
+ .pluck(:user_id, :timezone)
+ .to_h
+
+ grouped = {}
+
+ grouped_sql = <<~SQL
+ SELECT region, start_date, timezone, user_id, username, description
+ FROM calendar_events
+ WHERE topic_id = :topic_id
+ AND post_id IS NULL
+ AND start_date >= :start_date
+ ORDER BY region, start_date
+ SQL
+
+ DB
+ .query(grouped_sql, topic_id: object.topic_id, start_date: start_date)
+ .each do |row|
+ identifier = "#{row.region.split("_").first}-#{row.start_date.strftime("%Y-%j")}"
+
+ grouped[identifier] ||= {
+ type: :grouped,
+ from: row.start_date,
+ timezone: row.timezone,
+ name: [],
+ users: [],
+ }
+
+ grouped[identifier][:name] << row.description
+ grouped[identifier][:users] << { username: row.username, timezone: timezones[row.user_id] }
+ end
+
+ grouped.each do |_, v|
+ v[:name].uniq!
+ v[:name].sort!
+ v[:name] = v[:name].join(", ")
+ v[:users].uniq! { |u| u[:username] }
+ v[:users].sort! { |a, b| a[:username] <=> b[:username] }
+ end
+
+ standalones + grouped.values
+ end
+
+ add_to_serializer(
+ :post,
+ :group_timezones,
+ include_condition: -> do
+ post_custom_fields[DiscourseCalendar::GROUP_TIMEZONES_CUSTOM_FIELD].present?
+ end,
+ ) do
+ result = {}
+ group_timezones = post_custom_fields[DiscourseCalendar::GROUP_TIMEZONES_CUSTOM_FIELD] || {}
+ group_names = group_timezones["groups"] || []
+
+ if group_names.present?
+ users =
+ User
+ .human_users
+ .joins(:groups, :user_option)
+ .where("groups.name": group_names)
+ .select("users.*", "groups.name AS group_name", "user_options.timezone")
+
+ usernames_on_holiday = DiscourseCalendar.users_on_holiday
+
+ users.each do |u|
+ result[u.group_name] ||= []
+ result[u.group_name] << UserTimezoneSerializer.new(
+ u,
+ root: false,
+ on_holiday: usernames_on_holiday&.include?(u.username),
+ ).as_json
+ end
+ end
+
+ result
+ end
+
+ add_to_serializer(:site, :users_on_holiday, include_condition: -> { scope.is_staff? }) do
+ DiscourseCalendar.users_on_holiday
+ end
+
+ on(:reduce_cooked) do |fragment, post|
+ if SiteSetting.discourse_post_event_enabled
+ fragment
+ .css(".discourse-post-event")
+ .each do |event_node|
+ starts_at = event_node["data-start"]
+ ends_at = event_node["data-end"]
+ dates = "#{starts_at} (#{event_node["data-timezone"] || "UTC"})"
+ dates = "#{dates} → #{ends_at} (#{event_node["data-timezone"] || "UTC"})" if ends_at
+
+ event_name = event_node["data-name"] || post.topic.title
+ event_node.replace <<~TXT
+
+ TXT
+ end
+ end
+ end
+
+ on(:user_destroyed) { |user| DiscoursePostEvent::Invitee.where(user_id: user.id).destroy_all }
+
+ if respond_to?(:add_post_revision_notifier_recipients)
+ add_post_revision_notifier_recipients do |post_revision|
+ # next if no modifications
+ next if !post_revision.modifications.present?
+
+ # do no notify recipients when only updating tags
+ next if post_revision.modifications.keys == ["tags"]
+
+ ids = []
+ post = post_revision.post
+
+ if post && post.is_first_post? && post.event
+ ids.concat(post.event.on_going_event_invitees.pluck(:user_id))
+ end
+
+ ids
+ end
+ end
+
+ on(:site_setting_changed) do |name, old_val, new_val|
+ next if name != :discourse_post_event_allowed_custom_fields
+
+ previous_fields = old_val.split("|")
+ new_fields = new_val.split("|")
+ removed_fields = previous_fields - new_fields
+
+ next if removed_fields.empty?
+
+ DiscoursePostEvent::Event.all.find_each do |event|
+ removed_fields.each { |field| event.custom_fields.delete(field) }
+ event.save
+ end
+ end
+
+ if defined?(DiscourseAutomation)
+ on(:discourse_post_event_event_started) do |event|
+ DiscourseAutomation::Automation
+ .where(enabled: true, trigger: "event_started")
+ .each do |automation|
+ fields = automation.serialized_fields
+ topic_id = fields.dig("topic_id", "value")
+
+ next unless event.post.topic.id.to_s == topic_id
+
+ automation.trigger!(
+ "kind" => "event_started",
+ "event" => event,
+ "placeholders" => {
+ "event_url" => event.url,
+ },
+ )
+ end
+ end
+
+ add_triggerable_to_scriptable("event_started", "send_chat_message")
+
+ add_automation_triggerable("event_started") do
+ placeholder :event_url
+
+ field :topic_id, component: :text
+ end
+ end
+
+ query =
+ Proc.new do |notifications, data|
+ notifications.where("data::json ->> 'topic_title' = ?", data[:topic_title].to_s).where(
+ "data::json ->> 'message' = ?",
+ data[:message].to_s,
+ )
+ end
+
+ reminders_consolidation_plan =
+ Notifications::DeletePreviousNotifications.new(
+ type: Notification.types[:event_reminder],
+ previous_query_blk: query,
+ )
+
+ invitation_consolidation_plan =
+ Notifications::DeletePreviousNotifications.new(
+ type: Notification.types[:event_invitation],
+ previous_query_blk: query,
+ )
+
+ register_notification_consolidation_plan(reminders_consolidation_plan)
+ register_notification_consolidation_plan(invitation_consolidation_plan)
+
+ Report.add_report("currently_away") do |report|
+ group_filter = report.filters.dig(:group) || Group::AUTO_GROUPS[:staff]
+ report.add_filter("group", type: "group", default: group_filter)
+
+ break unless group = Group.find_by(id: group_filter)
+
+ report.labels = [
+ { property: :username, title: I18n.t("reports.currently_away.labels.username") },
+ ]
+
+ group_usernames = group.users.pluck(:username)
+ on_holiday_usernames = DiscourseCalendar.users_on_holiday
+ report.data = (group_usernames & on_holiday_usernames).map { |username| { username: username } }
+ report.total = report.data.count
+ end
+end
diff --git a/plugins/discourse-calendar/public/javascripts/fullcalendar-with-moment-timezone.min.js b/plugins/discourse-calendar/public/javascripts/fullcalendar-with-moment-timezone.min.js
new file mode 100644
index 00000000000..a44eb903650
--- /dev/null
+++ b/plugins/discourse-calendar/public/javascripts/fullcalendar-with-moment-timezone.min.js
@@ -0,0 +1,21 @@
+/* eslint-disable */
+/*!
+ * FullCalendar v4.0.0-alpha.3
+ * Docs & License: https://fullcalendar.io/
+ * (c) 2018 Adam Shaw
+ */
+!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("superagent")):"function"==typeof define&&define.amd?define(["superagent"],t):"object"==typeof exports?exports.FullCalendar=t(require("superagent")):e.FullCalendar=t(e.superagent)}("undefined"!=typeof self?self:this,function(e){return function(e){function t(r){if(n[r])return n[r].exports;var i=n[r]={i:r,l:!1,exports:{}};return e[r].call(i.exports,i,i.exports,t),i.l=!0,i.exports}var n={};return t.m=e,t.c=n,t.d=function(e,n,r){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:r})},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="",t(t.s=184)}([,function(e,t){var n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n])};t.__extends=function(e,t){function r(){this.constructor=e}n(e,t),e.prototype=null===t?Object.create(t):(r.prototype=t.prototype,new r)},t.__assign=Object.assign||function(e){for(var t,n=1,r=arguments.length;nt&&(t=r)}}),t++,e.forEach(function(e){e.style.width=t+"px"}),t}function d(e,t){var n={position:"relative",left:-1};k.applyStyle(e,n),k.applyStyle(t,n);var r=e.offsetHeight-t.offsetHeight,i={position:"",left:""};return k.applyStyle(e,i),k.applyStyle(t,i),r}function c(e){e.classList.add("fc-unselectable"),e.addEventListener("selectstart",z.preventDefault)}function p(e){e.classList.remove("fc-unselectable"),e.removeEventListener("selectstart",z.preventDefault)}function f(e){e.addEventListener("contextmenu",z.preventDefault)}function h(e){e.removeEventListener("contextmenu",z.preventDefault)}function g(e){var t,n,r=[],i=[];for("string"==typeof e?i=e.split(/\s*,\s*/):"function"==typeof e?i=[e]:Array.isArray(e)&&(i=e),t=0;t=L.asRoughMs(t)&&(i=N.addDays(i,1)),i<=n&&(i=N.addDays(n,1)),{start:n,end:i}}function O(e){var t=I(e);return N.diffDays(t.start,t.end)>1}function H(e,t,n,r){return"year"===r?L.createDuration(n.diffWholeYears(e,t),"year"):"month"===r?L.createDuration(n.diffWholeMonths(e,t),"month"):N.diffDayAndTime(e,t)}Object.defineProperty(t,"__esModule",{value:!0});var k=n(3),x=n(15),z=n(23),N=n(4),L=n(9);t.compensateScroll=r,t.uncompensateScroll=i,t.disableCursor=o,t.enableCursor=a,t.distributeHeight=s,t.undistributeHeight=l,t.matchCellWidths=u,t.subtractInnerElHeight=d,t.preventSelection=c,t.allowSelection=p,t.preventContextMenu=f,t.allowContextMenu=h,t.parseFieldSpecs=g,t.compareByFieldSpecs=v,t.compareByFieldSpec=m,t.flexibleCompare=y,t.log=E,t.warn=S,t.capitaliseFirstLetter=b,t.padStart=D,t.compareNumbers=w,t.isInt=T,t.applyAll=_,t.firstDefined=R,t.debounce=C,t.refineProps=M,t.computeAlignedDayRange=P,t.computeVisibleDayRange=I,t.isMultiDayRange=O,t.diffDates=H},function(e,t){function n(e,t,n){var r=document.createElement(e);if(t)for(var i in t)"style"===i?m(r,t[i]):E[i]?r[i]=t[i]:r.setAttribute(i,t[i]);return"string"==typeof n?r.innerHTML=n:null!=n&&s(r,n),r}function r(e){e=e.trim();var t=document.createElement(a(e));return t.innerHTML=e,t.firstChild}function i(e){return Array.prototype.slice.call(o(e))}function o(e){e=e.trim();var t=document.createElement(a(e));return t.innerHTML=e,t.childNodes}function a(e){return S[e.substr(0,3)]||"div"}function s(e,t){for(var n=d(t),r=0;r=1?Math.min(i,o):i}function y(e,t,n,r){var i=w([t,0,1+E(t,n,r)]),o=f(e),s=Math.round(a(i,o));return Math.floor(s/7)+1}function E(e,t,n){var r=7+t-n;return-(7+w([e,0,r]).getUTCDay()-t)%7+r-1}function S(e){return[e.getFullYear(),e.getMonth(),e.getDate(),e.getHours(),e.getMinutes(),e.getSeconds(),e.getMilliseconds()]}function b(e){return new Date(e[0],e[1]||0,null==e[2]?1:e[2],e[3]||0,e[4]||0,e[5]||0)}function D(e){return[e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds()]}function w(e){return new Date(Date.UTC.apply(Date,e))}function T(e){return!isNaN(e.valueOf())}function _(e){return 1e3*e.getUTCHours()*60*60+1e3*e.getUTCMinutes()*60+1e3*e.getUTCSeconds()+e.getUTCMilliseconds()}Object.defineProperty(t,"__esModule",{value:!0}),t.DAY_IDS=["sun","mon","tue","wed","thu","fri","sat"],t.addWeeks=n,t.addDays=r,t.addMs=i,t.diffWeeks=o,t.diffDays=a,t.diffHours=s,t.diffMinutes=l,t.diffSeconds=u,t.diffDayAndTime=d,t.diffWholeWeeks=c,t.diffWholeDays=p,t.startOfDay=f,t.startOfHour=h,t.startOfMinute=g,t.startOfSecond=v,t.weekOfYear=m,t.dateToLocalArray=S,t.arrayToLocalDate=b,t.dateToUtcArray=D,t.arrayToUtcDate=w,t.isValidDate=T,t.timeAsMs=_},,,function(e,t,n){function r(e,t){return"object"==typeof e&&e?("string"==typeof t&&(e=u.__assign({separator:t},e)),new c.NativeFormatter(e)):"string"==typeof e?new p.CmdFormatter(e,t):"function"==typeof e?new f.FuncFormatter(e):void 0}function i(e,t,n){void 0===n&&(n=!1);var r=e.toISOString();return r=r.replace(".000",""),n&&(r=r.replace("T00:00:00Z","")),r.length>10&&(null==t?r=r.replace("Z",""):0!==t&&(r=r.replace("Z",a(t,!0)))),r}function o(e){return d.padStart(e.getUTCHours(),2)+":"+d.padStart(e.getUTCMinutes(),2)+":"+d.padStart(e.getUTCSeconds(),2)}function a(e,t){void 0===t&&(t=!1);var n=e<0?"-":"+",r=Math.abs(e),i=Math.floor(r/60),o=Math.round(r%60);return t?n+d.padStart(i,2)+":"+d.padStart(o,2):"GMT"+n+i+(o?":"+d.padStart(o,2):"")}function s(e,t,n,r){var i=l(e,n.calendarSystem);return{date:i,start:i,end:t?l(t,n.calendarSystem):null,timeZone:n.timeZone,localeCodes:n.locale.codes,separator:r}}function l(e,t){var n=t.markerToArray(e.marker);return{marker:e.marker,timeZoneOffset:e.timeZoneOffset,array:n,year:n[0],month:n[1],day:n[2],hour:n[3],minute:n[4],second:n[5],millisecond:n[6]}}Object.defineProperty(t,"__esModule",{value:!0});var u=n(1),d=n(2),c=n(187),p=n(57),f=n(188);t.createFormatter=r,t.buildIsoString=i,t.formatIsoTimeString=o,t.formatTimeZoneOffset=a,t.createVerboseFormattingArg=s},function(e,t){function n(e,t){var n=null,r=null;return e.start&&(n=t.createMarker(e.start)),e.end&&(r=t.createMarker(e.end)),n||r?n&&r&&ra&&o.push({start:a,end:r.start}),r.end>a&&(a=r.end);return at.start)&&(null===e.start||null===t.end||e.start=e.start)&&(null===e.end||null!==t.end&&t.end<=e.end)}function u(e,t){return(null===e.start||t>=e.start)&&(null===e.end||t=t.end?new Date(t.end.valueOf()-1):e}Object.defineProperty(t,"__esModule",{value:!0}),t.parseRange=n,t.invertRanges=r,t.intersectRanges=o,t.rangesEqual=a,t.rangesIntersect=s,t.rangeContainsRange=l,t.rangeContainsMarker=u,t.constrainMarkerToRange=d},function(e,t,n){function r(e,t){var n;return"string"==typeof e?i(e):"object"==typeof e&&e?o(e):"number"==typeof e?o((n={},n[t||"milliseconds"]=e,n)):null}function i(e){var t=w.exec(e);if(t){var n=t[1]?-1:1;return{years:0,months:0,days:n*(t[2]?parseInt(t[2],10):0),milliseconds:n*(60*(t[3]?parseInt(t[3],10):0)*60*1e3+60*(t[4]?parseInt(t[4],10):0)*1e3+1e3*(t[5]?parseInt(t[5],10):0)+(t[6]?parseInt(t[6],10):0))}}return null}function o(e){return{years:e.years||e.year||0,months:e.months||e.month||0,days:(e.days||e.day||0)+7*a(e),milliseconds:60*(e.hours||e.hour||0)*60*1e3+60*(e.minutes||e.minute||0)*1e3+1e3*(e.seconds||e.second||0)+(e.milliseconds||e.millisecond||e.ms||0)}}function a(e){return e.weeks||e.week||0}function s(e,t){return e.years===t.years&&e.months===t.months&&e.days===t.days&&e.milliseconds===t.milliseconds}function l(e){return 0===e.years&&0===e.months&&1===e.days&&0===e.milliseconds}function u(e,t){return{years:e.years+t.years,months:e.months+t.months,days:e.days+t.days,milliseconds:e.milliseconds+t.milliseconds}}function d(e,t){return{years:e.years-t.years,months:e.months-t.months,days:e.days-t.days,milliseconds:e.milliseconds-t.milliseconds}}function c(e,t){return{years:e.years*t,months:e.months*t,days:e.days*t,milliseconds:e.milliseconds*t}}function p(e){return h(e)/365}function f(e){return h(e)/30}function h(e){return y(e)/864e5}function g(e){return y(e)/36e5}function v(e){return y(e)/6e4}function m(e){return y(e)/1e3}function y(e){return 31536e6*e.years+2592e6*e.months+864e5*e.days+e.milliseconds}function E(e,t){for(var n=null,r=0;r/g,">").replace(/'/g,"'").replace(/"/g,""").replace(/\n/g,"
")}function r(e){var t=[];for(var n in e){var r=e[n];null!=r&&""!==r&&t.push(n+":"+r)}return t.join(";")}function i(e){var t=[];for(var r in e){var i=e[r];null!=i&&t.push(r+'="'+n(i)+'"')}return t.join(" ")}function o(e){return Array.isArray(e)?e:"string"==typeof e?e.split(/\s+/):[]}Object.defineProperty(t,"__esModule",{value:!0}),t.htmlEscape=n,t.cssToStr=r,t.attrsToStr=i,t.parseClassName=o},function(e,t){function n(e,t){var r,i,o,a,s,l,u={};if(t)for(r=0;r=0;a--)if("object"==typeof(s=e[a][i])&&s)o.unshift(s);else if(void 0!==s){u[i]=s;break}o.length&&(u[i]=n(o))}for(r=e.length-1;r>=0;r--){l=e[r];for(i in l)i in u||(u[i]=l[i])}return u}function r(e,t){var n={};for(var r in e)t(e[r],r)&&(n[r]=e[r]);return n}function i(e,t){var n={};for(var r in e)n[r]=t(e[r],r);return n}function o(e){for(var t={},n=0,r=e;n'+r+"":""+r+""}function i(e){return e.opt("allDayHtml")||a.htmlEscape(e.opt("allDayText"))}function o(e,t,n,r){var i,o,a=n.calendar,u=n.view,d=n.theme,c=n.dateEnv,p=[];return l.rangeContainsMarker(t.activeRange,e)?(p.push("fc-"+s.DAY_IDS[e.getUTCDay()]),u.opt("monthMode")&&c.getMonth(e)!==c.getMonth(t.currentRange.start)&&p.push("fc-other-month"),i=s.startOfDay(a.getNow()),o=s.addDays(i,1),e=o?p.push("fc-future"):(p.push("fc-today"),!0!==r&&p.push(d.getClass("today")))):p.push("fc-disabled-day"),p}Object.defineProperty(t,"__esModule",{value:!0});var a=n(11),s=n(4),l=n(8);t.buildGotoAnchorHtml=r,t.getAllDayHtml=i,t.getDayClasses=o},function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0});var r=n(1),i=n(33),o=n(8),a=n(60),s=n(3),l=n(42),u=n(16),d=function(e){function t(t,n,r){var i=e.call(this,t,r)||this;return i.needHitsDepth=0,i.el=n,i.isInteractable&&a.default.registerComponent(i),i}return r.__extends(t,e),t.prototype.destroy=function(){e.prototype.destroy.call(this),s.removeElement(this.el),this.isInteractable&&a.default.unregisterComponent(this)},t.prototype.requestPrepareHits=function(){this.needHitsDepth++||this.prepareHits()},t.prototype.requestReleaseHits=function(){--this.needHitsDepth||this.releaseHits()},t.prototype.prepareHits=function(){},t.prototype.releaseHits=function(){},t.prototype.queryHit=function(e,t){return null},t.prototype.isInteractionValid=function(e){var t=this.calendar,n=this.props.dateProfile,r=e.mutatedEvents.instances;if(n)for(var i in r)if(!o.rangeContainsRange(n.validRange,r[i].range))return!1;return l.isInteractionValid(e,t)},t.prototype.isDateSelectionValid=function(e){var t=this.props.dateProfile;return!(t&&!o.rangeContainsRange(t.validRange,e.range))&&l.isDateSelectionValid(e,this.calendar)},t.prototype.publiclyTrigger=function(e,t){return this.calendar.publiclyTrigger(e,t)},t.prototype.publiclyTriggerAfterSizing=function(e,t){return this.calendar.publiclyTriggerAfterSizing(e,t)},t.prototype.hasPublicHandlers=function(e){return this.calendar.hasPublicHandlers(e)},t.prototype.triggerRenderedSegs=function(e,t){var n=this.calendar
+;if(this.hasPublicHandlers("eventPositioned"))for(var r=0,i=e;r *",d.prototype.bgSegSelector=".fc-bgevent:not(.fc-nonbusiness)"},function(e,t,n){function r(e){e.preventDefault()}function i(e,t,n,r){function i(e){var t=s.elementClosest(e.target,n);t&&r.call(t,e,t)}return e.addEventListener(t,i),function(){e.removeEventListener(t,i)}}function o(e,t,n,r){var o;return i(e,"mouseover",t,function(e,t){if(t!==o){o=t,n(e,t);var i=function(e){o=null,r(e,t),t.removeEventListener("mouseleave",i)};t.addEventListener("mouseleave",i)}})}function a(e,t){var n=function(r){t(r),l.forEach(function(t){e.removeEventListener(t,n)})};l.forEach(function(t){e.addEventListener(t,n)})}Object.defineProperty(t,"__esModule",{value:!0});var s=n(3);t.preventDefault=r,t.listenBySelector=i,t.listenToHoverBySelector=o;var l=["webkitTransitionEnd","otransitionend","oTransitionEnd","msTransitionEnd","transitionend"];t.whenTransitionDone=a},function(e,t,n){function r(e,n,r){var i=u.refineProps(e,t.UNSCOPED_EVENT_UI_PROPS,{},r),o=s.normalizeConstraint(i.constraint,n);return{startEditable:null!=i.startEditable?i.startEditable:i.editable,durationEditable:null!=i.durationEditable?i.durationEditable:i.editable,constraints:null!=o?[o]:[],overlap:i.overlap,allows:null!=i.allow?[i.allow]:[],backgroundColor:i.backgroundColor||i.color,borderColor:i.borderColor||i.color,textColor:i.textColor,classNames:i.classNames.concat(i.className)}}function i(e,n,i,o){var a={},s={};for(var l in t.UNSCOPED_EVENT_UI_PROPS){var d=e+u.capitaliseFirstLetter(l);a[l]=n[d],s[d]=!0}if("event"===e&&(a.editable=n.editable),o)for(var l in n)s[l]||(o[l]=n[l]);return r(a,i)}function o(e){return e.reduce(a,d)}function a(e,t){return{startEditable:null!=t.startEditable?t.startEditable:e.startEditable,durationEditable:null!=t.durationEditable?t.durationEditable:e.durationEditable,constraints:e.constraints.concat(t.constraints),overlap:"boolean"==typeof t.overlap?t.overlap:e.overlap,allows:e.allows.concat(t.allows),backgroundColor:t.backgroundColor||e.backgroundColor,borderColor:t.borderColor||e.borderColor,textColor:t.textColor||e.textColor,classNames:e.classNames.concat(t.classNames)}}Object.defineProperty(t,"__esModule",{value:!0});var s=n(42),l=n(11),u=n(2);t.UNSCOPED_EVENT_UI_PROPS={editable:Boolean,startEditable:Boolean,durationEditable:Boolean,constraint:null,overlap:null,allow:null,className:l.parseClassName,classNames:l.parseClassName,color:String,backgroundColor:String,borderColor:String,textColor:String},t.processUnscopedUiProps=r,t.processScopedUiProps=i;var d={startEditable:null,durationEditable:null,constraints:[],overlap:null,allows:[],backgroundColor:"",borderColor:"",textColor:"",classNames:[]};t.combineEventUis=o},function(e,t,n){function r(e,t,n,r){var s=u(t,n),l={},d=p.parseRecurring(e,s,n.dateEnv,l);if(d){var c=i(l,t,d.allDay,Boolean(d.duration),n);return c.recurringDef={typeId:d.typeId,typeData:d.typeData,duration:d.duration},{def:c,instance:null}}var f={},h=a(e,s,n,f,r);if(h){var c=i(f,t,h.allDay,h.hasEnd,n);return{def:c,instance:o(c.defId,h.range,h.forcedStartTzo,h.forcedEndTzo)}}return null}function i(e,t,n,r,i){var o={},a=l(e,i,o);a.defId=String(g++),a.sourceId=t,a.allDay=n,a.hasEnd=r;for(var s=0,u=i.pluginSystem.hooks.eventDefParsers;sr.layer)||(r=a)}return r},e}();t.default=d,t.isHitsEqual=r},function(e,t,n){function r(e){c.push(e)}function i(e){return c[e]}function o(e){return!c[e.sourceDefId].ignoreRange}function a(e,t){for(var n=c.length-1;n>=0;n--){var r=c[n],i=r.parseMeta(e);if(i)return s("object"==typeof e?e:{},i,n,t)}return null}function s(e,t,n,r){var i={},o=l.refineProps(e,d,{},i),a={},s=u.processUnscopedUiProps(i,r,a);return o.isFetching=!1,o.latestFetchId="",o.fetchRange=null,o.publicId=String(e.id||""),o.sourceId=String(p++),o.sourceDefId=n,o.meta=t,o.ui=s,o.extendedProps=a,o}Object.defineProperty(t,"__esModule",{value:!0});var l=n(2),u=n(24),d={id:String,allDayDefault:Boolean,eventDataTransform:Function,success:Function,failure:Function},c=[],p=0;t.registerEventSourceDef=r,t.getEventSourceDef=i,t.doesSourceNeedRange=o,t.parseEventSource=a},,,function(e,t){function n(e,t){return e.left>=t.left&&e.left=t.top&&e.top=i*i&&n.handleDistanceSurpassed(e)}n.isDragging&&("scroll"!==e.origEvent.type&&(n.mirror.handleMove(e.pageX,e.pageY),n.autoScroller.handleMove(e.pageX,e.pageY)),n.emitter.trigger("dragmove",e))}},n.onPointerUp=function(e){n.isInteracting&&(n.isInteracting=!1,o.allowSelection(document.body),o.allowContextMenu(document.body),n.emitter.trigger("pointerup",e),n.isDragging&&(n.autoScroller.stop(),n.tryStopDrag(e)),n.delayTimeoutId&&(clearTimeout(n.delayTimeoutId),n.delayTimeoutId=null))};var r=n.pointer=new i.default(t);return r.emitter.on("pointerdown",n.onPointerDown),r.emitter.on("pointermove",n.onPointerMove),r.emitter.on("pointerup",n.onPointerUp),n.mirror=new a.default,n.autoScroller=new l.default,n}return r.__extends(t,e),t.prototype.destroy=function(){this.pointer.destroy()},t.prototype.startDelay=function(e){var t=this;"number"==typeof this.delay?this.delayTimeoutId=setTimeout(function(){t.delayTimeoutId=null,t.handleDelayEnd(e)},this.delay):this.handleDelayEnd(e)},t.prototype.handleDelayEnd=function(e){this.isDelayEnded=!0,this.tryStartDrag(e)},t.prototype.handleDistanceSurpassed=function(e){this.isDistanceSurpassed=!0,this.tryStartDrag(e)},t.prototype.tryStartDrag=function(e){this.isDelayEnded&&this.isDistanceSurpassed&&(this.pointer.wasTouchScroll&&!this.touchScrollAllowed||(this.isDragging=!0,this.mirrorNeedsRevert=!1,this.autoScroller.start(e.pageX,e.pageY),this.emitter.trigger("dragstart",e),!1===this.touchScrollAllowed&&this.pointer.cancelTouchScroll()))},t.prototype.tryStopDrag=function(e){this.mirror.stop(this.mirrorNeedsRevert,this.stopDrag.bind(this,e))},t.prototype.stopDrag=function(e){this.isDragging=!1,this.emitter.trigger("dragend",e)},t.prototype.setIgnoreMove=function(e){this.pointer.shouldIgnoreMove=e},t.prototype.setMirrorIsVisible=function(e){this.mirror.setIsVisible(e)},t.prototype.setMirrorNeedsRevert=function(e){this.mirrorNeedsRevert=e},t}(s.default);t.default=u},function(e,t,n){function r(e){return i.mergeProps(e,o)}Object.defineProperty(t,"__esModule",{value:!0});var i=n(12);t.globalDefaults={defaultRangeSeparator:" - ",titleRangeSeparator:" – ",cmdFormatter:null,defaultTimedEventDuration:"01:00:00",defaultAllDayEventDuration:{day:1},forceEventDuration:!1,nextDayThreshold:"00:00:00",columnHeader:!0,defaultView:"month",aspectRatio:1.35,header:{left:"title",center:"",right:"today prev,next"},weekends:!0,weekNumbers:!1,weekNumberCalculation:"local",editable:!1,scrollTime:"06:00:00",minTime:"00:00:00",maxTime:"24:00:00",showNonCurrentDates:!0,lazyFetching:!0,startParam:"start",endParam:"end",timeZoneParam:"timeZone",timeZone:"local",timeZoneImpl:null,locale:"en",agendaEventMinHeight:0,theme:!1,dragRevertDuration:500,dragScroll:!0,allDayMaintainDuration:!1,unselectAuto:!0,dropAccept:"*",eventOrder:"start,-duration,allDay,title",eventLimit:!1,eventLimitClick:"popover",dayPopoverFormat:{month:"long",day:"numeric",year:"numeric"},handleWindowResize:!0,windowResizeDelay:100,longPressDelay:1e3,eventDragMinDistance:5},t.rtlDefaults={header:{left:"next,prev today",center:"",right:"title"},buttonIcons:{prev:"right-single-arrow",next:"left-single-arrow",prevYear:"right-double-arrow",nextYear:"left-double-arrow"},themeButtonIcons:{prev:"circle-triangle-e",next:"circle-triangle-w",nextYear:"seek-prev",prevYear:"seek-next"}};var o=["header","footer","buttonText","buttonIcons","themeButtonIcons"];t.mergeOptions=r},function(e,t,n){function r(e){return{id:String(a++),deps:e.deps||[],reducers:e.reducers||[],eventDefParsers:e.eventDefParsers||[],eventDragMutationMassagers:e.eventDragMutationMassagers||[],eventDefMutationAppliers:e.eventDefMutationAppliers||[],dateSelectionTransformers:e.dateSelectionTransformers||[],datePointTransforms:e.datePointTransforms||[],dateSpanTransforms:e.dateSpanTransforms||[],viewConfigs:e.viewConfigs||{},viewSpecTransformers:e.viewSpecTransformers||[],viewPropsTransformers:e.viewPropsTransformers||[],isPropsValid:e.isPropsValid||null,externalDefTransforms:e.externalDefTransforms||[],eventResizeJoinTransforms:e.eventResizeJoinTransforms||[],viewContainerModifiers:e.viewContainerModifiers||[]}}function i(e,t){return{reducers:e.reducers.concat(t.reducers),eventDefParsers:e.eventDefParsers.concat(t.eventDefParsers),eventDragMutationMassagers:e.eventDragMutationMassagers.concat(t.eventDragMutationMassagers),eventDefMutationAppliers:e.eventDefMutationAppliers.concat(t.eventDefMutationAppliers),dateSelectionTransformers:e.dateSelectionTransformers.concat(t.dateSelectionTransformers),datePointTransforms:e.datePointTransforms.concat(t.datePointTransforms),dateSpanTransforms:e.dateSpanTransforms.concat(t.dateSpanTransforms),viewConfigs:o.__assign({},e.viewConfigs,t.viewConfigs),viewSpecTransformers:e.viewSpecTransformers.concat(t.viewSpecTransformers),viewPropsTransformers:e.viewPropsTransformers.concat(t.viewPropsTransformers),isPropsValid:t.isPropsValid||e.isPropsValid,externalDefTransforms:e.externalDefTransforms.concat(t.externalDefTransforms),eventResizeJoinTransforms:e.eventResizeJoinTransforms.concat(t.eventResizeJoinTransforms),viewContainerModifiers:e.viewContainerModifiers.concat(t.viewContainerModifiers)}}Object.defineProperty(t,"__esModule",{value:!0});var o=n(1),a=0;t.createPlugin=r;var s=function(){function e(){this.hooks={reducers:[],eventDefParsers:[],eventDragMutationMassagers:[],eventDefMutationAppliers:[],dateSelectionTransformers:[],datePointTransforms:[],dateSpanTransforms:[],viewConfigs:{},viewSpecTransformers:[],viewPropsTransformers:[],isPropsValid:null,externalDefTransforms:[],eventResizeJoinTransforms:[],viewContainerModifiers:[]},this.addedHash={}}return e.prototype.add=function(e){if(!this.addedHash[e.id]){this.addedHash[e.id]=!0;for(var t=0,n=e.deps;t0;r--){var i=n.slice(0,r).join("-");if(u[i])return u[i]}return null}function o(e,t){u[e]=t}function a(){return Object.keys(u)}Object.defineProperty(t,"__esModule",{value:!0});var s=n(12),l={week:{dow:0,doy:4},dir:"ltr",buttonText:{prev:"prev",next:"next",prevYear:"prev year",nextYear:"next year",year:"year",today:"today",month:"month",
+week:"week",day:"day",list:"list"},weekLabel:"W",allDayText:"all-day",eventLimitText:"more",noEventsMessage:"No events to display"},u={};t.getLocale=r,t.defineLocale=o,t.getLocaleCodes=a,o("en",l)},function(e,t,n){function r(e,t){return a.rangesEqual(e.activeRange,t.activeRange)&&a.rangesEqual(e.validRange,t.validRange)}Object.defineProperty(t,"__esModule",{value:!0});var i=n(4),o=n(9),a=n(8),s=function(){function e(e,t){this.viewSpec=e,this.options=e.options,this.dateEnv=t.dateEnv,this.calendar=t,this.initHiddenDays()}return e.prototype.buildPrev=function(e){var t=this.dateEnv,n=t.subtract(t.startOf(e.currentDate,e.currentRangeUnit),e.dateIncrement);return this.build(n,-1)},e.prototype.buildNext=function(e){var t=this.dateEnv,n=t.add(t.startOf(e.currentDate,e.currentRangeUnit),e.dateIncrement);return this.build(n,1)},e.prototype.build=function(e,t,n){void 0===n&&(n=!1);var r,i,s,l,u,d,c=null,p=null;return r=this.buildValidRange(),r=this.trimHiddenDays(r),n&&(e=a.constrainMarkerToRange(e,r)),i=this.buildCurrentRangeInfo(e,t),s=/^(year|month|week|day)$/.test(i.unit),l=this.buildRenderRange(this.trimHiddenDays(i.range),i.unit,s),l=this.trimHiddenDays(l),u=l,this.options.showNonCurrentDates||(u=a.intersectRanges(u,i.range)),c=o.createDuration(this.options.minTime),p=o.createDuration(this.options.maxTime),u=this.adjustActiveRange(u,c,p),u=a.intersectRanges(u,r),u&&(e=a.constrainMarkerToRange(e,u)),d=a.rangesIntersect(i.range,r),{validRange:r,currentDate:e,currentRange:i.range,currentRangeUnit:i.unit,isRangeAllDay:s,activeRange:u,renderRange:l,minTime:c,maxTime:p,isValid:d,dateIncrement:this.buildDateIncrement(i.duration)}},e.prototype.buildValidRange=function(){return this.getRangeOption("validRange",this.calendar.getNow())||{start:null,end:null}},e.prototype.buildCurrentRangeInfo=function(e,t){var n,r=this,i=r.viewSpec,a=r.dateEnv,s=null,l=null,u=null;return i.duration?(s=i.duration,l=i.durationUnit,u=this.buildRangeFromDuration(e,t,s,l)):(n=this.options.dayCount)?(l="day",u=this.buildRangeFromDayCount(e,t,n)):(u=this.buildCustomVisibleRange(e))?l=a.greatestWholeUnit(u.start,u.end).unit:(s=this.getFallbackDuration(),l=o.greatestDurationDenominator(s).unit,u=this.buildRangeFromDuration(e,t,s,l)),{duration:s,unit:l,range:u}},e.prototype.getFallbackDuration=function(){return o.createDuration({day:1})},e.prototype.adjustActiveRange=function(e,t,n){var r=this.dateEnv,a=e.start,s=e.end;return this.viewSpec.class.prototype.usesMinMaxTime&&(o.asRoughDays(t)<0&&(a=i.startOfDay(a),a=r.add(a,t)),o.asRoughDays(n)>1&&(s=i.startOfDay(s),s=i.addDays(s,-1),s=r.add(s,n))),{start:a,end:s}},e.prototype.buildRangeFromDuration=function(e,t,n,r){function a(){u=p.startOf(e,f),d=p.add(u,n),c={start:u,end:d}}var s,l,u,d,c,p=this.dateEnv,f=this.options.dateAlignment;return f||(s=this.options.dateIncrement,s?(l=o.createDuration(s),f=o.asRoughMs(l)=n[t]&&e=n[t]&&e"+this.fillSegTag+">"},e.prototype.detachSegs=function(e,t){var n=this.containerElsByType[e];n&&(n.forEach(i.removeElement),delete this.containerElsByType[e])},e.prototype.computeSizes=function(e){for(var t in this.segsByType)(e||this.dirtySizeFlags[t])&&this.computeSegSizes(this.segsByType[t])},e.prototype.assignSizes=function(e){for(var t in this.segsByType)(e||this.dirtySizeFlags[t])&&this.assignSegSizes(this.segsByType[t]);this.dirtySizeFlags={}},e.prototype.computeSegSizes=function(e){},e.prototype.assignSegSizes=function(e){},e}();t.default=a},function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0});var r=n(1),i=n(3),o=n(15),a=n(59),s=n(203),l=n(164),u=n(204),d=n(205),c=n(4),p=n(7),f=n(22),h=n(206),g=n(8),v=n(21),m=n(163),y=n(20),E=p.createFormatter({day:"numeric"}),S=p.createFormatter({week:"numeric"}),b=function(e){function t(t,n,r){var i=e.call(this,t,n)||this;i.bottomCoordPadding=0,i.isCellSizesDirty=!1;var o=i.eventRenderer=new l.default(i),a=i.fillRenderer=new d.default(i);i.mirrorRenderer=new u.default(i);var s=i.renderCells=y.memoizeRendering(i._renderCells,i._unrenderCells);return i.renderBusinessHours=y.memoizeRendering(a.renderSegs.bind(a,"businessHours"),a.unrender.bind(a,"businessHours"),[s]),i.renderDateSelection=y.memoizeRendering(a.renderSegs.bind(a,"highlight"),a.unrender.bind(a,"highlight"),[s]),i.renderBgEvents=y.memoizeRendering(a.renderSegs.bind(a,"bgEvent"),a.unrender.bind(a,"bgEvent"),[s]),i.renderFgEvents=y.memoizeRendering(o.renderSegs.bind(o),o.unrender.bind(o),[s]),i.renderEventSelection=y.memoizeRendering(o.selectByInstanceId.bind(o),o.unselectByInstanceId.bind(o),[i.renderFgEvents]),i.renderEventDrag=y.memoizeRendering(i._renderEventDrag,i._unrenderEventDrag,[s]),i.renderEventResize=y.memoizeRendering(i._renderEventResize,i._unrenderEventResize,[s]),i.renderProps=r,i}return r.__extends(t,e),t.prototype.render=function(e){var t=e.cells;this.rowCnt=t.length,this.colCnt=t[0].length,this.renderCells(t,e.isRigid),this.renderBusinessHours(e.businessHourSegs),this.renderDateSelection(e.dateSelectionSegs),this.renderBgEvents(e.bgEventSegs),this.renderFgEvents(e.fgEventSegs),this.renderEventSelection(e.eventSelection),this.renderEventDrag(e.eventDrag),this.renderEventResize(e.eventResize),this.segPopoverTile&&this.updateSegPopoverTile()},t.prototype.destroy=function(){e.prototype.destroy.call(this),this.renderCells.unrender()},t.prototype.getCellRange=function(e,t){var n=this.props.cells[e][t].date;return{start:n,end:c.addDays(n,1)}},t.prototype.updateSegPopoverTile=function(e,t){var n=this.props;this.segPopoverTile.receiveProps({date:e||this.segPopoverTile.props.date,fgSegs:t||this.segPopoverTile.props.fgSegs,eventSelection:n.eventSelection,eventDragInstances:n.eventDrag?n.eventDrag.affectedInstances:null,eventResizeInstances:n.eventResize?n.eventResize.affectedInstances:null})},t.prototype._renderCells=function(e,t){var n,r,o=this,s=o.view,l=o.dateEnv,u=this,d=u.rowCnt,c=u.colCnt,p="";for(n=0;n'+i.renderHtml({cells:this.props.cells[e],dateProfile:this.props.dateProfile,renderIntroHtml:this.renderProps.renderBgIntroHtml})+'
'+(this.getIsNumbersVisible()?""+this.renderNumberTrHtml(e)+"":"")+"
"},t.prototype.getIsNumbersVisible=function(){return this.getIsDayNumbersVisible()||this.renderProps.cellWeekNumbersVisible||this.renderProps.colWeekNumbersVisible},t.prototype.getIsDayNumbersVisible=function(){return this.rowCnt>1},t.prototype.renderNumberTrHtml=function(e){var t=this.renderProps.renderNumberIntroHtml(e,this);return""+(this.isRtl?"":t)+this.renderNumberCellsHtml(e)+(this.isRtl?t:"")+"
"},t.prototype.renderNumberCellsHtml=function(e){var t,n,r=[];for(t=0;t",this.renderProps.cellWeekNumbersVisible&&e.getUTCDay()===n&&(a+=v.buildGotoAnchorHtml(i,{date:e,type:"week"},{class:"fc-week-number"},o.format(e,S))),l&&(a+=v.buildGotoAnchorHtml(i,e,{class:"fc-day-number"},o.format(e,E))),a+=""):" | "},t.prototype.updateSize=function(e){var t=this,n=t.fillRenderer,r=t.eventRenderer,i=t.mirrorRenderer;(e||this.isCellSizesDirty)&&(this.buildColPositions(),this.buildRowPositions(),this.isCellSizesDirty=!1),n.computeSizes(e),r.computeSizes(e),i.computeSizes(e),n.assignSizes(e),r.assignSizes(e),i.assignSizes(e)},t.prototype.buildColPositions=function(){this.colPositions.build()},t.prototype.buildRowPositions=function(){this.rowPositions.build(),this.rowPositions.bottoms[this.rowCnt-1]+=this.bottomCoordPadding},t.prototype.positionToHit=function(e,t){var n=this,r=n.colPositions,i=n.rowPositions,o=r.leftToIndex(e),a=i.topToIndex(t);if(null!=a&&null!=o)return{row:a,col:o,dateSpan:{range:this.getCellRange(a,o),allDay:!0},dayEl:this.getCellEl(a,o),relativeRect:{left:r.lefts[o],right:r.rights[o],top:i.tops[a],bottom:i.bottoms[a]}}},t.prototype.getCellEl=function(e,t){return this.cellEls[e*this.colCnt+t]},t.prototype._renderEventDrag=function(e){e&&(this.eventRenderer.hideByHash(e.affectedInstances),this.fillRenderer.renderSegs("highlight",e.segs))},t.prototype._unrenderEventDrag=function(e){e&&(this.eventRenderer.showByHash(e.affectedInstances),this.fillRenderer.unrender("highlight"))},t.prototype._renderEventResize=function(e){e&&(this.eventRenderer.hideByHash(e.affectedInstances),this.fillRenderer.renderSegs("highlight",e.segs),this.mirrorRenderer.renderSegs(e.segs,{isResizing:!0,sourceSeg:e.sourceSeg}))},t.prototype._unrenderEventResize=function(e){e&&(this.eventRenderer.showByHash(e.affectedInstances),this.fillRenderer.unrender("highlight"),this.mirrorRenderer.unrender())},t.prototype.removeSegPopover=function(){this.segPopover&&this.segPopover.hide()},t.prototype.limitRows=function(e){var t,n,r=this.eventRenderer.rowStructs||[];for(t=0;to)return t;return!1},t.prototype.limitRow=function(e,t){var n,r,o,a,s,l,u,d,c,p,f,h,g,v,m,y=this,E=this,S=E.colCnt,b=E.isRtl,D=this.eventRenderer.rowStructs[e],w=[],T=0,_=function(n){for(;T=t.length?t[t.length-1]+1:t[n]},e}();t.default=i},function(e,t){Object.defineProperty(t,"__esModule",{value:!0});var n=function(){function e(e,t){var n,r,i,o=e.dates;if(t){for(r=o[0].getUTCDay(),n=1;n0},e.prototype.canScrollHorizontally=function(){return this.getMaxScrollLeft()>0},e.prototype.canScrollUp=function(){return this.getScrollTop()>0},e.prototype.canScrollDown=function(){return this.getScrollTop()0},e.prototype.canScrollRight=function(){return this.getScrollLeft()1?{year:"numeric",month:"short",day:"numeric"}:{year:"numeric",month:"long",day:"numeric"}}function o(e){return e.map(function(e){return new e})}Object.defineProperty(t,"__esModule",{value:!0});var a=n(1),s=n(33),l=n(200),u=n(3),d=n(8),c=n(143),p=n(17),f=n(15),h=n(7),g=n(4),v=n(20),m=n(1),y=function(e){function t(t,n){var i=e.call(this,t)||this;i._renderToolbars=v.memoizeRendering(i.renderToolbars),i.buildViewPropTransformers=p.memoize(o),i.el=n,u.prependToElement(n,i.contentEl=u.createElement("div",{className:"fc-view-container"}));for(var a=i.calendar,s=0,l=a.pluginSystem.hooks.viewContainerModifiers;s"+f.buildGotoAnchorHtml(l,{date:o.start,type:"week",forceOff:a>1},i.htmlEscape(e))+""):' | "},l.renderTimeGridBgIntroHtml=function(){return' | "},l.renderTimeGridIntroHtml=function(){return' | "},l.renderDayGridBgIntroHtml=function(){return'"+f.getAllDayHtml(l)+" | "},
+l.renderDayGridIntroHtml=function(){return' | "},l.el.classList.add("fc-agenda-view"),l.el.innerHTML=l.renderSkeletonHtml(),l.scroller=new s.default("hidden","auto");var c=l.scroller.el;l.el.querySelector(".fc-body > tr > td").appendChild(c),c.classList.add("fc-time-grid-container");var p=o.createElement("div",{className:"fc-time-grid"});return c.appendChild(p),l.timeGrid=new u.default(l.context,p,{renderBgIntroHtml:l.renderTimeGridBgIntroHtml,renderIntroHtml:l.renderTimeGridIntroHtml}),l.opt("allDaySlot")&&(l.dayGrid=new d.default(l.context,l.el.querySelector(".fc-day-grid"),{renderNumberIntroHtml:l.renderDayGridIntroHtml,renderBgIntroHtml:l.renderDayGridBgIntroHtml,renderIntroHtml:l.renderDayGridIntroHtml,colWeekNumbersVisible:!1,cellWeekNumbersVisible:!1}),l.dayGrid.bottomCoordPadding=l.el.querySelector(".fc-divider").offsetHeight),l}return r.__extends(t,e),t.prototype.destroy=function(){e.prototype.destroy.call(this),this.timeGrid.destroy(),this.dayGrid&&this.dayGrid.destroy(),this.scroller.destroy()},t.prototype.renderSkeletonHtml=function(){var e=this.theme;return''+(this.opt("columnHeader")?'| |
':"")+''+(this.opt("allDaySlot")?' ':"")+" |
"},t.prototype.getNowIndicatorUnit=function(){return this.timeGrid.getNowIndicatorUnit()},t.prototype.unrenderNowIndicator=function(){this.timeGrid.unrenderNowIndicator()},t.prototype.updateSize=function(t,n,r){e.prototype.updateSize.call(this,t,n,r),this.timeGrid.updateSize(t),this.dayGrid&&this.dayGrid.updateSize(t)},t.prototype.updateBaseSize=function(e,t,n){var r,i,s,l=this;if(this.axisWidth=a.matchCellWidths(o.findElements(this.el,".fc-axis")),!this.timeGrid.colEls)return void(n||(i=this.computeScrollerHeight(t),this.scroller.setHeight(i)));var u=o.findElements(this.el,".fc-row").filter(function(e){return!l.scroller.el.contains(e)});this.timeGrid.bottomRuleEl.style.display="none",this.scroller.clear(),u.forEach(a.uncompensateScroll),this.dayGrid&&(this.dayGrid.removeSegPopover(),r=this.opt("eventLimit"),r&&"number"!=typeof r&&(r=5),r&&this.dayGrid.limitRows(r)),n||(i=this.computeScrollerHeight(t),this.scroller.setHeight(i),s=this.scroller.getScrollbarWidths(),(s.left||s.right)&&(u.forEach(function(e){a.compensateScroll(e,s)}),i=this.computeScrollerHeight(t),this.scroller.setHeight(i)),this.scroller.lockOverflow(s),this.timeGrid.getTotalSlatHeight()
',i.rootBgContainerEl=n.querySelector(".fc-bg"),i.slatContainerEl=n.querySelector(".fc-slats"),i.bottomRuleEl=n.querySelector(".fc-divider"),i.renderProps=r,i}return r.__extends(t,e),t.prototype.processOptions=function(){var e,t,n=this.opt("slotDuration"),r=this.opt("snapDuration");n=d.createDuration(n),r=r?d.createDuration(r):n,e=d.wholeDivideDurations(n,r),null===e&&(r=n,e=1),this.slotDuration=n,this.snapDuration=r,this.snapsPerSlot=e,t=this.opt("slotLabelFormat"),Array.isArray(t)&&(t=t[t.length-1]),this.labelFormat=p.createFormatter(t||{hour:"numeric",minute:"2-digit",omitZeroMinute:!0,meridiem:"short"}),t=this.opt("slotLabelInterval"),this.labelInterval=t?d.createDuration(t):this.computeLabelInterval(n)},t.prototype.computeLabelInterval=function(e){var t,n,r;for(t=v.length-1;t>=0;t--)if(n=d.createDuration(v[t]),null!==(r=d.wholeDivideDurations(n,e))&&r>1)return n;return e},t.prototype.render=function(e){var t=e.cells;this.colCnt=t.length,this.renderSlats(e.dateProfile),this.renderColumns(e.cells,e.dateProfile),this.renderBusinessHours(e.businessHourSegs),this.renderDateSelection(e.dateSelectionSegs),this.renderFgEvents(e.fgEventSegs),this.renderBgEvents(e.bgEventSegs),this.renderEventSelection(e.eventSelection),this.renderEventDrag(e.eventDrag),this.renderEventResize(e.eventResize)},t.prototype.destroy=function(){e.prototype.destroy.call(this),this.renderSlats.unrender(),this.renderColumns.unrender()},t.prototype.updateSize=function(e){var t=this,n=t.fillRenderer,r=t.eventRenderer,i=t.mirrorRenderer;(e||this.isSlatSizesDirty)&&(this.buildSlatPositions(),this.isSlatSizesDirty=!1),(e||this.isColSizesDirty)&&(this.buildColPositions(),this.isColSizesDirty=!1),n.computeSizes(e),r.computeSizes(e),i.computeSizes(e),n.assignSizes(e),r.assignSizes(e),i.assignSizes(e)},t.prototype._renderSlats=function(e){var t=this.theme;this.slatContainerEl.innerHTML=''+this.renderSlatRowHtml(e)+"
",this.slatEls=o.findElements(this.slatContainerEl,"tr"),this.slatPositions=new a.default(this.el,this.slatEls,!1,!0),this.isSlatSizesDirty=!0},t.prototype.renderSlatRowHtml=function(e){for(var t,n,r,o=this,a=o.dateEnv,s=o.theme,l=o.isRtl,u="",f=c.startOfDay(e.renderRange.start),h=e.minTime,g=d.createDuration(0);d.asRoughMs(h)'+(n?""+i.htmlEscape(a.format(t,this.labelFormat))+"":"")+"",u+='"+(l?"":r)+' | '+(l?r:"")+"
",h=d.addDurations(h,this.slotDuration),g=d.addDurations(g,this.slotDuration);return u},t.prototype._renderColumns=function(e,t){var n=this.theme,r=new h.default(this.context);this.rootBgContainerEl.innerHTML=''+r.renderHtml({cells:e,dateProfile:t,renderIntroHtml:this.renderProps.renderBgIntroHtml})+"
",this.colEls=o.findElements(this.el,".fc-day, .fc-disabled-day"),this.isRtl&&this.colEls.reverse(),this.colPositions=new a.default(this.el,this.colEls,!0,!1),this.renderContentSkeleton(),this.isColSizesDirty=!0},t.prototype._unrenderColumns=function(){this.unrenderContentSkeleton()},t.prototype.renderContentSkeleton=function(){var e,t=[];t.push(this.renderProps.renderIntroHtml());for(var n=0;n');this.isRtl&&t.reverse(),e=this.contentSkeletonEl=o.htmlToElement('"),this.colContainerEls=o.findElements(e,".fc-content-col"),this.mirrorContainerEls=o.findElements(e,".fc-mirror-container"),this.fgContainerEls=o.findElements(e,".fc-event-container:not(.fc-mirror-container)"),this.bgContainerEls=o.findElements(e,".fc-bgevent-container"),this.highlightContainerEls=o.findElements(e,".fc-highlight-container"),this.businessContainerEls=o.findElements(e,".fc-business-container"),this.isRtl&&(this.colContainerEls.reverse(),this.mirrorContainerEls.reverse(),this.fgContainerEls.reverse(),this.bgContainerEls.reverse(),this.highlightContainerEls.reverse(),this.businessContainerEls.reverse()),this.el.appendChild(e)},t.prototype.unrenderContentSkeleton=function(){o.removeElement(this.contentSkeletonEl)},t.prototype.groupSegsByCol=function(e){var t,n=[];for(t=0;t0){var s=o.createElement("div",{className:"fc-now-indicator fc-now-indicator-arrow"});s.style.top=r+"px",this.contentSkeletonEl.appendChild(s),i.push(s)}this.nowIndicatorEls=i}},t.prototype.unrenderNowIndicator=function(){this.nowIndicatorEls&&(this.nowIndicatorEls.forEach(o.removeElement),this.nowIndicatorEls=null)},t.prototype.getTotalSlatHeight=function(){return this.slatContainerEl.offsetHeight},t.prototype.computeDateTop=function(e,t){return t||(t=c.startOfDay(e)),this.computeTimeTop(e.valueOf()-t.valueOf())},t.prototype.computeTimeTop=function(e){var t,n,r=this.slatEls.length,i=this.props.dateProfile,o=(e-d.asRoughMs(i.minTime))/d.asRoughMs(this.slotDuration);return o=Math.max(0,o),o=Math.min(r,o),t=Math.floor(o),t=Math.min(t,r-1),n=o-t,this.slatPositions.tops[t]+this.slatPositions.getHeight(t)*n},t.prototype.computeSegVerticals=function(e){var t,n,r,i=this.opt("agendaEventMinHeight");for(t=0;tt.top&&e.top'+(n?'
'+d.htmlEscape(n)+"
":"")+(a.title?'
'+d.htmlEscape(a.title)+"
":"")+'
'+(p?'':"")+""},t.prototype.computeSegHorizontals=function(e){var t,n,a;if(e=this.sortEventSegs(e),t=r(e),i(t),n=t[0]){for(a=0;a"}Object.defineProperty(t,"__esModule",{value:!0});var i=n(21),o=n(8),a=function(){function e(e){this.context=e}return e.prototype.renderHtml=function(e){var t=[];e.renderIntroHtml&&t.push(e.renderIntroHtml());for(var n=0,i=e.cells;n'),"rtl"===this.context.options.dir&&t.reverse(),""+t.join("")+"
"},e}();t.default=a},function(e,t,n){function r(e,t){var n,r;for(n=0;n=e.firstCol)return!0;return!1}function i(e,t){return e.leftCol-t.leftCol}Object.defineProperty(t,"__esModule",{value:!0});var o=n(1),a=n(3),s=n(165),l=function(e){function t(t){var n=e.call(this,t.context)||this;return n.dayGrid=t,n}return o.__extends(t,e),t.prototype.attachSegs=function(e,t){var n=this.rowStructs=this.renderSegRows(e);this.dayGrid.rowEls.forEach(function(e,t){e.querySelector(".fc-content-skeleton > table").appendChild(n[t].tbodyEl)}),t||this.dayGrid.removeSegPopover()},t.prototype.detachSegs=function(){for(var e,t=this.rowStructs||[];e=t.pop();)a.removeElement(e.tbodyEl);this.rowStructs=null},t.prototype.renderSegRows=function(e){var t,n,r=[];for(t=this.groupSegRows(e),n=0;n'+i.htmlEscape(n)+""),r=''+(i.htmlEscape(s.title||"")||" ")+"",''+("rtl"===o.dir?r+" "+g:g+" "+r)+"
"+(c?'':"")+(p?'':"")+""},t.prototype.computeEventTimeFormat=function(){return{hour:"numeric",minute:"2-digit",omitZeroMinute:!0,meridiem:"narrow"}},t.prototype.computeDisplayEventEnd=function(){return!1},t}(o.default);t.default=a},function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0});var r=n(1),i=n(33),o=n(3),a=n(7),s=n(167),l=function(e){function t(t,n){var r=e.call(this,t)||this;return n.innerHTML="",n.appendChild(r.el=o.htmlToElement('')),r.thead=r.el.querySelector("thead"),r}return r.__extends(t,e),t.prototype.destroy=function(){o.removeElement(this.el)},t.prototype.render=function(e){var t=e.dates,n=e.datesRepDistinctDays,r=[];e.renderIntroHtml&&r.push(e.renderIntroHtml());for(var i=a.createFormatter(this.opt("columnHeaderFormat")||s.computeFallbackHeaderFormat(n,t.length)),o=0,l=t;o"+r.join("")+""},t}(i.default);t.default=l},function(e,t,n){function r(e,t){return!e||t>10?{weekday:"short"}:t>1?{weekday:"short",month:"numeric",day:"numeric",omitCommas:!0}:{weekday:"long"}}function i(e,t,n,r,i,u,d,c){var p,f=u.view,h=u.dateEnv,g=u.theme,v=u.options,m=o.rangeContainsMarker(t.activeRange,e),y=["fc-day-header",g.getClass("widgetHeader")];return p="function"==typeof v.columnHeaderHtml?v.columnHeaderHtml(e):"function"==typeof v.columnHeaderText?a.htmlEscape(v.columnHeaderText(e)):a.htmlEscape(h.format(e,i)),n?y=y.concat(s.getDayClasses(e,t,u,!0)):y.push("fc-"+l.DAY_IDS[e.getUTCDay()]),'1?' colspan="'+d+'"':"")+(c?" "+c:"")+">"+(m?s.buildGotoAnchorHtml(f,{date:e,forceOff:!n||1===r},p):p)+" | "}Object.defineProperty(t,"__esModule",{value:!0});var o=n(8),a=n(11),s=n(21),l=n(4);t.computeFallbackHeaderFormat=r,t.renderDateCell=i},function(e,t,n){function r(e,t,n){for(var r=[],i=0,o=e.headerDates;i"+i.htmlEscape(s.opt("weekLabel"))+"":""},s.renderDayGridNumberIntroHtml=function(e,t){var n=s.dateEnv,r=t.props.cells[e][0].date;return s.colWeekNumbersVisible?'"+c.buildGotoAnchorHtml(s,{date:r,type:"week",forceOff:1===t.colCnt},n.format(r,f))+" | ":""},s.renderDayGridBgIntroHtml=function(){var e=s.theme;return s.colWeekNumbersVisible?' | ":""},s.renderDayGridIntroHtml=function(){return s.colWeekNumbersVisible?' | ":""},s.el.classList.add("fc-basic-view"),s.el.innerHTML=s.renderSkeletonHtml(),s.scroller=new l.default("hidden","auto");var u=s.scroller.el;s.el.querySelector(".fc-body > tr > td").appendChild(u),u.classList.add("fc-day-grid-container");var d=o.createElement("div",{className:"fc-day-grid"});u.appendChild(d);var h;return s.opt("weekNumbers")?s.opt("weekNumbersWithinDays")?(h=!0,s.colWeekNumbersVisible=!1):(h=!1,s.colWeekNumbersVisible=!0):(s.colWeekNumbersVisible=!1,h=!1),s.dayGrid=new p.default(s.context,d,{renderNumberIntroHtml:s.renderDayGridNumberIntroHtml,renderBgIntroHtml:s.renderDayGridBgIntroHtml,renderIntroHtml:s.renderDayGridIntroHtml,colWeekNumbersVisible:s.colWeekNumbersVisible,cellWeekNumbersVisible:h}),s}return r.__extends(t,e),t.prototype.destroy=function(){e.prototype.destroy.call(this),this.dayGrid.destroy(),this.scroller.destroy()},t.prototype.renderSkeletonHtml=function(){var e=this.theme;return''+(this.opt("columnHeader")?'| |
':"")+' |
'},t.prototype.weekNumberStyleAttr=function(){return null!=this.weekNumberWidth?'style="width:'+this.weekNumberWidth+'px"':""},t.prototype.hasRigidRows=function(){var e=this.opt("eventLimit");return e&&"number"!=typeof e},t.prototype.updateSize=function(t,n,r){e.prototype.updateSize.call(this,t,n,r),this.dayGrid.updateSize(t)},t.prototype.updateBaseSize=function(e,t,n){var r,i,s=this.dayGrid,l=this.opt("eventLimit"),u=this.header?this.header.el:null;if(!s.rowEls)return void(n||(r=this.computeScrollerHeight(t),this.scroller.setHeight(r)));this.colWeekNumbersVisible&&(this.weekNumberWidth=a.matchCellWidths(o.findElements(this.el,".fc-week-number"))),this.scroller.clear(),u&&a.uncompensateScroll(u),s.removeSegPopover(),l&&"number"==typeof l&&s.limitRows(l),r=this.computeScrollerHeight(t),this.setGridHeight(r,n),l&&"number"!=typeof l&&s.limitRows(l),n||(this.scroller.setHeight(r),i=this.scroller.getScrollbarWidths(),(i.left||i.right)&&(u&&a.compensateScroll(u,i),r=this.computeScrollerHeight(t),this.scroller.setHeight(r)),this.scroller.lockOverflow(i))},t.prototype.computeScrollerHeight=function(e){return e-a.subtractInnerElHeight(this.el,this.scroller.el)},t.prototype.setGridHeight=function(e,t){this.opt("monthMode")?(t&&(e*=this.dayGrid.rowCnt/6),a.distributeHeight(this.dayGrid.rowEls,e,!t)):t?a.undistributeHeight(this.dayGrid.rowEls):a.distributeHeight(this.dayGrid.rowEls,e,!0)},t.prototype.computeInitialDateScroll=function(){return{top:0}},t.prototype.queryDateScroll=function(){return{top:this.scroller.getScrollTop()}},t.prototype.applyDateScroll=function(e){void 0!==e.top&&this.scroller.setScrollTop(e.top)},t}(u.default);t.default=h,h.prototype.dateProfileGeneratorClass=d.default},function(e,t,n){function r(e,t){var n=new u.default(e.renderRange,t);return new d.default(n,/year|month|week/.test(e.currentRangeUnit))}Object.defineProperty(t,"__esModule",{value:!0});var i=n(1),o=n(169),a=n(166),s=n(68),l=n(17),u=n(65),d=n(66),c=function(e){function t(t,n,i,o){var u=e.call(this,t,n,i,o)||this;return u.buildDayTable=l.memoize(r),u.opt("columnHeader")&&(u.header=new a.default(u.context,u.el.querySelector(".fc-head-container"))),u.simpleDayGrid=new s.default(u.context,u.dayGrid),u}return i.__extends(t,e),t.prototype.destroy=function(){e.prototype.destroy.call(this),this.header&&this.header.destroy(),this.simpleDayGrid.destroy()},t.prototype.render=function(t){e.prototype.render.call(this,t);var n=this.props.dateProfile,r=this.dayTable=this.buildDayTable(n,this.dateProfileGenerator);this.header&&this.header.receiveProps({dateProfile:n,dates:r.headerDates,datesRepDistinctDays:1===r.rowCnt,renderIntroHtml:this.renderHeadIntroHtml}),this.simpleDayGrid.receiveProps({dateProfile:n,dayTable:r,businessHours:t.businessHours,dateSelection:t.dateSelection,eventStore:t.eventStore,eventUiBases:t.eventUiBases,eventSelection:t.eventSelection,eventDrag:t.eventDrag,eventResize:t.eventResize,isRigid:this.hasRigidRows(),nextDayThreshold:this.nextDayThreshold})},t}(o.default);t.default=c,t.buildDayTable=r},function(e,t,n){function r(e){for(var t=c.startOfDay(e.renderRange.start),n=e.renderRange.end,r=[],i=[];t'+a.htmlEscape(this.opt("noEventsMessage"))+"
"},t.prototype.renderSegList=function(e){var t,n,r,i=this.groupSegsByDay(e),a=o.htmlToElement(''),s=a.querySelector("tbody");for(t=0;t'+(n?h.buildGotoAnchorHtml(this,e,{class:"fc-list-heading-main"},a.htmlEscape(t.format(e,n))):"")+(r?h.buildGotoAnchorHtml(this,e,{class:"fc-list-heading-alt"},a.htmlEscape(t.format(e,r))):"")+"")},t}(l.default);t.default=y,y.prototype.isInteractable=!0,y.prototype.fgSegSelector=".fc-list-item"},function(e,t,n){function r(e,t,n){for(var r=a.__assign({},t.leftoverProps),i=0,o=n.pluginSystem.hooks.externalDefTransforms;i");document.body.appendChild(e);var t=e.firstChild,n=t.getBoundingClientRect().left>e.getBoundingClientRect().left;return a.removeElement(e),n}function o(e){return e=Math.max(0,e),e=Math.round(e)}Object.defineProperty(t,"__esModule",{value:!0});var a=n(3),s=null;t.getIsRtlScrollbarOnLeft=r,t.sanitizeScrollbarWidth=o},function(e,t,n){function r(e,t,n){var r=Object.keys(e).length;return 1===r&&"short"===e.timeZoneName?function(e){return h.formatTimeZoneOffset(e.timeZoneOffset)}:0===r&&t.week?function(e){return l(n.computeWeekNumber(e.marker),n.weekLabel,n.locale,t.week)}:i(e,t,n)}function i(e,t,n){e=p.__assign({},e),t=p.__assign({},t),o(e,t),e.timeZone="UTC";var r,i=new Intl.DateTimeFormat(n.locale.codes,e);if(t.omitZeroMinute){var s=p.__assign({},e);delete s.minute,r=new Intl.DateTimeFormat(n.locale.codes,s)}return function(o){var s,l=o.marker;return s=r&&!l.getUTCMinutes()?r:i,a(s.format(l),o,e,t,n)}}function o(e,t){e.timeZoneName&&(e.hour||(e.hour="2-digit"),e.minute||(e.minute="2-digit")),"long"===e.timeZoneName&&(e.timeZoneName="short"),t.omitZeroMinute&&(e.second||e.millisecond)&&delete t.omitZeroMinute}function a(e,t,n,r,i){return e=e.replace(b,""),"short"===n.timeZoneName&&(e=s(e,"UTC"===i.timeZone||null==t.timeZoneOffset?"UTC":h.formatTimeZoneOffset(t.timeZoneOffset))),r.omitCommas&&(e=e.replace(E,"").trim()),r.omitZeroMinute&&(e=e.replace(":00","")),!1===r.meridiem?e=e.replace(y,"").trim():"narrow"===r.meridiem?e=e.replace(y,function(e,t){return t.toLocaleLowerCase()}):"short"===r.meridiem?e=e.replace(y,function(e,t){return t.toLocaleLowerCase()+"m"}):"lowercase"===r.meridiem&&(e=e.replace(y,function(e){return e.toLocaleLowerCase()})),e=e.replace(S," "),e=e.trim()}function s(e,t){var n=!1;return e=e.replace(D,function(){return n=!0,t}),n||(e+=" "+t),e}function l(e,t,n,r){var i=[];return"narrow"===r?i.push(t):"short"===r&&i.push(t," "),i.push(n.simpleNumberFormat.format(e)),n.options.isRtl&&i.reverse(),i.join("")}function u(e,t,n){return n.getMarkerYear(e)!==n.getMarkerYear(t)?5:n.getMarkerMonth(e)!==n.getMarkerMonth(t)?4:n.getMarkerDay(e)!==n.getMarkerDay(t)?2:f.timeAsMs(e)!==f.timeAsMs(t)?1:0}function d(e,t){var n={};for(var r in e)r in m&&!(m[r]<=t)||(n[r]=e[r]);return n}function c(e,t,n,r){for(var i=0;i1)||"numeric"!==o.year&&"2-digit"!==o.year||"numeric"!==o.month&&"2-digit"!==o.month||"numeric"!==o.day&&"2-digit"!==o.day||(l=1);var p=this.format(e,n),f=this.format(t,n);if(p===f)return p;var h=d(o,l),g=r(h,a,n),v=g(e),m=g(t),y=c(p,v,f,m),E=a.separator||"";return y?y.before+v+E+m+y.after:p+E+f},e.prototype.getLargestUnit=function(){switch(this.severity){case 7:case 6:case 5:return"year";case 4:return"month";case 3:return"week";default:return"day"}},e}();t.NativeFormatter=w},function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0});var r=n(7),i=function(){function e(e){this.func=e}return e.prototype.format=function(e,t){return this.func(r.createVerboseFormattingArg(e,null,t))},e.prototype.formatRange=function(e,t,n){return this.func(r.createVerboseFormattingArg(e,t,n))},e}();t.FuncFormatter=i},function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0});var r=n(34),i=n(27),o=function(){function e(e){var t=this;this.handlePointerDown=function(e){var n=t.dragging;n.setIgnoreMove(!t.component.isValidDateDownEl(n.pointer.downEl))},this.handleDragEnd=function(e){var n=t.component;if(!t.dragging.pointer.wasTouchScroll){var r=t.hitDragging,o=r.initialHit,a=r.finalHit;o&&a&&i.isHitsEqual(o,a)&&n.calendar.triggerDateClick(o.dateSpan,o.dayEl,n.view,e.origEvent)}},this.component=e,this.dragging=new r.default(e.el),this.dragging.autoScroller.isEnabled=!1;var n=this.hitDragging=new i.default(this.dragging,e);n.emitter.on("pointerdown",this.handlePointerDown),n.emitter.on("dragend",this.handleDragEnd)}return e.prototype.destroy=function(){this.dragging.destroy()},e}();t.default=o},function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0});var r=n(3),i=n(23),o=function(){function e(){this.isVisible=!1,this.sourceEl=null,this.mirrorEl=null,this.sourceElRect=null,this.parentNode=document.body,this.zIndex=9999,this.revertDuration=0}return e.prototype.start=function(e,t,n){this.sourceEl=e,this.sourceElRect=this.sourceEl.getBoundingClientRect(),this.origScreenX=t-window.pageXOffset,this.origScreenY=n-window.pageYOffset,this.deltaX=0,this.deltaY=0,this.updateElPosition()},e.prototype.handleMove=function(e,t){this.deltaX=e-window.pageXOffset-this.origScreenX,this.deltaY=t-window.pageYOffset-this.origScreenY,this.updateElPosition()},e.prototype.setIsVisible=function(e){e?this.isVisible||(this.mirrorEl&&(this.mirrorEl.style.display=""),this.isVisible=e,this.updateElPosition()):this.isVisible&&(this.mirrorEl&&(this.mirrorEl.style.display="none"),this.isVisible=e)},e.prototype.stop=function(e,t){var n=this,r=function(){n.cleanup(),t()};e&&this.mirrorEl&&this.isVisible&&this.revertDuration&&(this.deltaX||this.deltaY)?this.doRevertAnimation(r,this.revertDuration):setTimeout(r,0)},e.prototype.doRevertAnimation=function(e,t){var n=this.mirrorEl,o=this.sourceEl.getBoundingClientRect();n.style.transition="top "+t+"ms,left "+t+"ms",r.applyStyle(n,{left:o.left,top:o.top}),i.whenTransitionDone(n,function(){n.style.transition="",e()})},e.prototype.cleanup=function(){this.mirrorEl&&(r.removeElement(this.mirrorEl),this.mirrorEl=null),this.sourceEl=null},e.prototype.updateElPosition=function(){this.sourceEl&&this.isVisible&&r.applyStyle(this.getMirrorEl(),{left:this.sourceElRect.left+this.deltaX,top:this.sourceElRect.top+this.deltaY})},e.prototype.getMirrorEl=function(){var e=this.sourceElRect,t=this.mirrorEl;return t||(t=this.mirrorEl=this.sourceEl.cloneNode(!0),t.classList.add("fc-unselectable"),t.classList.add("fc-dragging"),r.applyStyle(t,{position:"fixed",zIndex:this.zIndex,visibility:"",boxSizing:"border-box",width:e.right-e.left,height:e.bottom-e.top,right:"auto",bottom:"auto",margin:0}),this.parentNode.appendChild(t)),t},e}();t.default=o},function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0});var r=n(150),i="function"==typeof performance?performance.now:Date.now,o=function(){function e(){var e=this;this.isEnabled=!0,this.scrollQuery=[window,".fc-scroller"],this.edgeThreshold=50,this.maxVelocity=300,this.pointerScreenX=null,this.pointerScreenY=null,this.isAnimating=!1,this.scrollCaches=null,this.everMovedUp=!1,this.everMovedDown=!1,this.everMovedLeft=!1,this.everMovedRight=!1,this.animate=function(){if(e.isAnimating){var t=e.computeBestEdge(e.pointerScreenX+window.pageXOffset,e.pointerScreenY+window.pageYOffset);if(t){var n=i();e.handleSide(t,(n-e.msSinceRequest)/1e3),e.requestAnimation(n)}else e.isAnimating=!1}}}return e.prototype.start=function(e,t){this.isEnabled&&(this.scrollCaches=this.buildCaches(),this.pointerScreenX=null,this.pointerScreenY=null,this.everMovedUp=!1,this.everMovedDown=!1,this.everMovedLeft=!1,this.everMovedRight=!1,this.handleMove(e,t))},e.prototype.handleMove=function(e,t){if(this.isEnabled){var n=e-window.pageXOffset,r=t-window.pageYOffset,o=null===this.pointerScreenY?0:r-this.pointerScreenY,a=null===this.pointerScreenX?0:n-this.pointerScreenX;o<0?this.everMovedUp=!0:o>0&&(this.everMovedDown=!0),a<0?this.everMovedLeft=!0:o>0&&(this.everMovedRight=!0),this.pointerScreenX=n,this.pointerScreenY=r,this.isAnimating||(this.isAnimating=!0,this.requestAnimation(i()))}},e.prototype.stop=function(){if(this.isEnabled){this.isAnimating=!1;for(var e=0,t=this.scrollCaches;e=0&&u>=0&&d>=0&&c>=0&&(d<=n&&this.everMovedUp&&a.canScrollUp()&&(!r||r.distance>d)&&(r={scrollCache:a,name:"top",distance:d}),c<=n&&this.everMovedDown&&a.canScrollDown()&&(!r||r.distance>c)&&(r={scrollCache:a,name:"bottom",distance:c}),l<=n&&this.everMovedLeft&&a.canScrollLeft()&&(!r||r.distance>l)&&(r={scrollCache:a,name:"left",distance:l}),u<=n&&this.everMovedRight&&a.canScrollRight()&&(!r||r.distance>u)&&(r={scrollCache:a,name:"right",distance:u}))}return r},e.prototype.buildCaches=function(){return this.queryScrollEls().map(function(e){return e===window?new r.WindowScrollGeomCache(!1):new r.ElementScrollGeomCache(e,!1)})},e.prototype.queryScrollEls=function(){for(var e=[],t=0,n=this.scrollQuery;tr.start)return d.endDelta=l,d;return null}Object.defineProperty(t,"__esModule",{value:!0});var i=n(27),o=n(43),a=n(3),s=n(34),l=n(13),u=n(2),d=n(16),c=n(10),p=n(9),f=n(1),h=function(){function e(e){var t=this;this.draggingSeg=null,this.eventRange=null,this.relevantEvents=null,this.validMutation=null,this.mutatedRelevantEvents=null,this.handlePointerDown=function(e){var n=t.querySeg(e),r=t.eventRange=n.eventRange;t.dragging.minDistance=5,t.dragging.setIgnoreMove(!t.component.isValidSegDownEl(e.origEvent.target)||e.isTouch&&t.component.props.eventSelection!==r.instance.instanceId)},this.handleDragStart=function(e){var n=t.component.calendar,r=t.eventRange;t.relevantEvents=l.getRelevantEvents(n.state.eventStore,t.eventRange.instance.instanceId),t.draggingSeg=t.querySeg(e),n.unselect(),n.publiclyTrigger("eventResizeStart",[{el:t.draggingSeg.el,event:new d.default(n,r.def,r.instance),jsEvent:e.origEvent,view:t.component.view}])},this.handleHitUpdate=function(e,n,a){var s=t.component.calendar,d=t.relevantEvents,c=t.hitDragging.initialHit,p=t.eventRange.instance,f=null,h=null,g=!1,v={affectedEvents:d,mutatedEvents:l.createEmptyEventStore(),isEvent:!0,origSeg:t.draggingSeg};e&&(f=r(c,e,a.subjectEl.classList.contains("fc-start-resizer"),p.range,s.pluginSystem.hooks.eventResizeJoinTransforms)),f&&(h=o.applyMutationToEventStore(d,s.eventUiBases,f,s),v.mutatedEvents=h,t.component.isInteractionValid(v)||(g=!0,f=null,h=null,v.mutatedEvents=null)),h?s.dispatch({type:"SET_EVENT_RESIZE",state:v}):s.dispatch({type:"UNSET_EVENT_RESIZE"}),g?u.disableCursor():u.enableCursor(),n||(f&&i.isHitsEqual(c,e)&&(f=null),t.validMutation=f,t.mutatedRelevantEvents=h)},this.handleDragEnd=function(e){var n=t.component.calendar,r=t.component.view,i=t.eventRange.def,o=t.eventRange.instance,a=new d.default(n,i,o),s=t.relevantEvents,l=t.mutatedRelevantEvents;n.publiclyTrigger("eventResizeStop",[{el:t.draggingSeg.el,event:a,jsEvent:e.origEvent,view:r}]),t.validMutation?(n.dispatch({type:"MERGE_EVENTS",eventStore:l}),n.publiclyTrigger("eventResize",[{el:t.draggingSeg.el,startDelta:t.validMutation.startDelta||p.createDuration(0),endDelta:t.validMutation.endDelta||p.createDuration(0),prevEvent:a,event:new d.default(n,l.defs[i.defId],o?l.instances[o.instanceId]:null),revert:function(){n.dispatch({type:"MERGE_EVENTS",eventStore:s})},jsEvent:e.origEvent,view:r}])):n.publiclyTrigger("_noEventResize"),t.draggingSeg=null,t.relevantEvents=null,t.validMutation=null},this.component=e;var n=this.dragging=new s.default(e.el);n.pointer.selector=".fc-resizer",n.touchScrollAllowed=!1,n.autoScroller.isEnabled=e.opt("dragScroll");var a=this.hitDragging=new i.default(this.dragging,e);a.emitter.on("pointerdown",this.handlePointerDown),a.emitter.on("dragstart",this.handleDragStart),a.emitter.on("hitupdate",this.handleHitUpdate),a.emitter.on("dragend",this.handleDragEnd)}return e.prototype.destroy=function(){this.dragging.destroy()},e.prototype.querySeg=function(e){return c.getElSeg(a.elementClosest(e.subjectEl,this.component.fgSegSelector))},e}();t.default=h},function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0});var r=n(1),i=n(2),o=n(35),a=n(47),s=function(){function e(e){this.overrides=r.__assign({},e),this.dynamicOverrides={},this.compute()}return e.prototype.add=function(e,t){this.dynamicOverrides[e]=t,this.compute()},e.prototype.compute=function(){var e,t,n,r;e=i.firstDefined(this.dynamicOverrides.locale,this.overrides.locale,o.globalDefaults.locale),t=a.getLocale(e).options,n=i.firstDefined(this.dynamicOverrides.dir,this.overrides.dir,t.dir),r="rtl"===n?o.rtlDefaults:{},this.dirDefaults=r,this.localeDefaults=t,this.computed=o.mergeOptions([o.globalDefaults,r,t,this.overrides,this.dynamicOverrides])},e}();t.default=s},function(e,t,n){function r(e,t){a[e]=t}function i(e){return new a[e]}Object.defineProperty(t,"__esModule",{value:!0});var o=n(4),a={};t.registerCalendarSystem=r,t.createCalendarSystem=i,r("gregory",function(){function e(){}return e.prototype.getMarkerYear=function(e){return e.getUTCFullYear()},e.prototype.getMarkerMonth=function(e){return e.getUTCMonth()},e.prototype.getMarkerDay=function(e){return e.getUTCDate()},e.prototype.arrayToMarker=function(e){return o.arrayToUtcDate(e)},e.prototype.markerToArray=function(e){return o.dateToUtcArray(e)},e}())},function(e,t,n){function r(e,t,n){for(var r=i(e.viewType,t),d=o(e.dateProfile,t,r,n),g=f.default(e.eventSources,t,d,n),v=p.__assign({},e,{viewType:r,dateProfile:d,eventSources:g,eventStore:h.default(e.eventStore,t,g,d,n),dateSelection:a(e.dateSelection,t,n),eventSelection:s(e.eventSelection,t),eventDrag:l(e.eventDrag,t,g,n),eventResize:u(e.eventResize,t,g,n),eventSourceLoadingLevel:c(g),loadingLevel:c(g)}),m=0,y=n.pluginSystem.hooks.reducers;me.fetchRange.end:!e.latestFetchId}function l(e,t,n,r){var i={};for(var o in e){var a=e[o];t[o]?i[o]=u(a,n,r):i[o]=a}return i}function u(e,t,n){var r=f.getEventSourceDef(e.sourceDefId),i=String(v++);return r.fetch({eventSource:e,calendar:n,range:t},function(r){var o,a,s=r.rawEvents,l=n.opt("eventSourceSuccess");e.success&&(a=e.success(s,r.response)),l&&(o=l(s,r.response)),s=a||o||s,n.dispatch({type:"RECEIVE_EVENTS",sourceId:e.sourceId,fetchId:i,fetchRange:t,rawEvents:s})},function(r){var o=n.opt("eventSourceFailure");g.warn(r.message,r),e.failure&&e.failure(r),o&&o(r),n.dispatch({type:"RECEIVE_EVENT_ERROR",sourceId:e.sourceId,fetchId:i,fetchRange:t,error:r})}),p.__assign({},e,{isFetching:!0,latestFetchId:i})}function d(e,t,n,r){var i,o=e[t];return o&&n===o.latestFetchId?p.__assign({},e,(i={},i[t]=p.__assign({},o,{isFetching:!1,fetchRange:r}),i)):e}function c(e){return h.filterHash(e,function(e){return f.doesSourceNeedRange(e)})}Object.defineProperty(t,"__esModule",{value:!0});var p=n(1),f=n(28),h=n(12),g=n(2);t.default=r;var v=0},function(e,t,n){function r(e){return a.mapHash(e,i)}function i(e){"function"==typeof e&&(e={class:e});var t={},n=o.refineProps(e,s,{},t);return{superType:n.type,class:n.class,options:t}}Object.defineProperty(t,"__esModule",{value:!0});var o=n(2),a=n(12);t.parseViewConfigs=r;var s={type:String,class:null}},function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0});var r=n(1),i=n(11),o=n(3),a=n(33),s=n(20),l=function(e){function t(t,n){var r=e.call(this,t)||this;return r._renderLayout=s.memoizeRendering(r.renderLayout,r.unrenderLayout),r._updateTitle=s.memoizeRendering(r.updateTitle,null,[r._renderLayout]),r._updateActiveButton=s.memoizeRendering(r.updateActiveButton,null,[r._renderLayout]),r._updateToday=s.memoizeRendering(r.updateToday,null,[r._renderLayout]),r._updatePrev=s.memoizeRendering(r.updatePrev,null,[r._renderLayout]),r._updateNext=s.memoizeRendering(r.updateNext,null,[r._renderLayout]),r.el=o.createElement("div",{className:"fc-toolbar "+n}),r}return r.__extends(t,e),t.prototype.destroy=function(){e.prototype.destroy.call(this),this._renderLayout.unrender(),o.removeElement(this.el)},t.prototype.render=function(e){this._renderLayout(e.layout),this._updateTitle(e.title),this._updateActiveButton(e.activeButton),this._updateToday(e.isTodayEnabled),this._updatePrev(e.isPrevEnabled),this._updateNext(e.isNextEnabled)},t.prototype.renderLayout=function(e){var t=this.el;this.viewsWithButtons=[],o.appendToElement(t,this.renderSection("left",e.left)),o.appendToElement(t,this.renderSection("right",e.right)),o.appendToElement(t,this.renderSection("center",e.center)),o.appendToElement(t,'')},t.prototype.unrenderLayout=function(){this.el.innerHTML=""},t.prototype.renderSection=function(e,t){var n=this,r=this,a=r.theme,s=r.calendar,l=s.optionsManager,u=s.viewSpecs,d=o.createElement("div",{className:"fc-"+e}),c=l.computed.customButtons||{},p=l.overrides.buttonText||{},f=l.computed.buttonText||{};return t&&t.split(" ").forEach(function(e,t){var r,l=[],h=!0;if(e.split(",").forEach(function(e,t){var r,d,g,v,m,y,E,S,b;if("title"===e)l.push(o.htmlToElement("
")),h=!1;else if((r=c[e])?(g=function(e){r.click&&r.click.call(S,e)},(v=a.getCustomButtonIconClass(r))||(v=a.getIconClass(e))||(m=r.text)):(d=u[e])?(n.viewsWithButtons.push(e),g=function(){s.changeView(e)},(m=d.buttonTextOverride)||(v=a.getIconClass(e))||(m=d.buttonTextDefault)):s[e]&&(g=function(){s[e]()},(m=p[e])||(v=a.getIconClass(e))||(m=f[e])),g){E=["fc-"+e+"-button",a.getClass("button"),a.getClass("stateDefault")],m?(y=i.htmlEscape(m),b=""):v&&(y="",b=' aria-label="'+e+'"'),S=o.htmlToElement('");var D=function(){var e=a.getClass("stateActive"),t=a.getClass("stateDisabled");return!(e&&S.classList.contains(e)||t&&S.classList.contains(t))};S.addEventListener("click",function(e){var t=a.getClass("stateDisabled"),n=a.getClass("stateHover");t&&S.classList.contains(t)||(g(e),!D()&&n&&S.classList.remove(n))}),S.addEventListener("mousedown",function(e){var t=a.getClass("stateDown");D()&&t&&S.classList.add(t)}),S.addEventListener("mouseup",function(e){var t=a.getClass("stateDown");t&&S.classList.remove(t)}),S.addEventListener("mouseenter",function(e){var t=a.getClass("stateHover");D()&&t&&S.classList.add(t)}),S.addEventListener("mouseleave",function(e){var t=a.getClass("stateHover"),n=a.getClass("stateDown");t&&S.classList.remove(t),n&&S.classList.remove(n)}),l.push(S)}}),h&&l.length>0){var g=a.getClass("cornerLeft"),v=a.getClass("cornerRight");g&&l[0].classList.add(g),v&&l[l.length-1].classList.add(v)}if(l.length>1){r=document.createElement("div");var m=a.getClass("buttonGroup");h&&m&&r.classList.add(m),o.appendToElement(r,l),d.appendChild(r)}else o.appendToElement(d,l)}),d},t.prototype.updateToday=function(e){this.toggleButtonEnabled("today",e)},t.prototype.updatePrev=function(e){this.toggleButtonEnabled("prev",e)},t.prototype.updateNext=function(e){this.toggleButtonEnabled("next",e)},t.prototype.updateTitle=function(e){o.findElements(this.el,"h2").forEach(function(t){t.innerText=e})},t.prototype.updateActiveButton=function(e){var t=this.theme.getClass("stateActive");o.findElements(this.el,"button").forEach(function(n){e&&n.classList.contains("fc-"+e+"-button")?n.classList.add(t):n.classList.remove(t)})},t.prototype.toggleButtonEnabled=function(e,t){var n=this.theme.getClass("stateDisabled");o.findElements(this.el,".fc-"+e+"-button").forEach(function(e){e.disabled=!t,t?e.classList.remove(n):e.classList.add(n)})},t}(a.default);t.default=l},function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0});var r=n(1),i=n(162),o=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return r.__extends(t,e),t.prototype.attachSegs=function(e,t){this.segsByCol=this.timeGrid.groupSegsByCol(e),this.timeGrid.attachSegsByCol(this.segsByCol,this.timeGrid.mirrorContainerEls),this.sourceSeg=t.sourceSeg},t.prototype.generateSegCss=function(t){var n=e.prototype.generateSegCss.call(this,t),r=this.sourceSeg;if(r&&r.col===t.col){var i=e.prototype.generateSegCss.call(this,r);n.left=i.left,n.right=i.right,n.marginLeft=i.marginLeft,n.marginRight=i.marginRight}return n},t}(i.default);t.default=o},function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0});var r=n(1),i=n(63),o=function(e){function t(t){var n=e.call(this,t.context)||this;return n.timeGrid=t,n}return r.__extends(t,e),t.prototype.attachSegs=function(e,t){var n,r=this.timeGrid;return"bgEvent"===e?n=r.bgContainerEls:"businessHours"===e?n=r.businessContainerEls:"highlight"===e&&(n=r.highlightContainerEls),r.attachSegsByCol(r.groupSegsByCol(t),n),t.map(function(e){return e.el})},t.prototype.computeSegSizes=function(e){this.timeGrid.computeSegVerticals(e)},t.prototype.assignSegSizes=function(e){this.timeGrid.assignSegVerticals(e)},t}(i.default)
+;t.default=o},function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0});var r=n(3),i=n(23),o=n(15),a=function(){function e(e){var t=this;this.isHidden=!0,this.margin=10,this.documentMousedown=function(e){t.el&&!t.el.contains(e.target)&&t.hide()},this.options=e}return e.prototype.show=function(){this.isHidden&&(this.el||this.render(),this.el.style.display="",this.position(),this.isHidden=!1,this.trigger("show"))},e.prototype.hide=function(){this.isHidden||(this.el.style.display="none",this.isHidden=!0,this.trigger("hide"))},e.prototype.render=function(){var e=this,t=this.options,n=this.el=r.createElement("div",{className:"fc-popover "+(t.className||""),style:{top:"0",left:"0"}});"function"==typeof t.content&&t.content(n),t.parentEl.appendChild(n),i.listenBySelector(n,"click",".fc-close",function(t){e.hide()}),t.autoHide&&document.addEventListener("mousedown",this.documentMousedown)},e.prototype.destroy=function(){this.hide(),this.el&&(r.removeElement(this.el),this.el=null),document.removeEventListener("mousedown",this.documentMousedown)},e.prototype.position=function(){var e,t,n=this.options,i=this.el,a=i.getBoundingClientRect(),s=o.computeRect(i.offsetParent),l=o.computeClippingRect(n.parentEl);e=n.top||0,t=void 0!==n.left?n.left:void 0!==n.right?n.right-a.width:0,e=Math.min(e,l.bottom-a.height-this.margin),e=Math.max(e,l.top+this.margin),t=Math.min(t,l.right-a.width-this.margin),t=Math.max(t,l.left+this.margin),r.applyStyle(i,{top:e-s.top,left:t-s.left})},e.prototype.trigger=function(e){this.options[e]&&this.options[e].apply(this,Array.prototype.slice.call(arguments,1))},e}();t.default=a},function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0});var r=n(1),i=n(3),o=n(164),a=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return r.__extends(t,e),t.prototype.attachSegs=function(e,t){var n=t.sourceSeg,r=this.rowStructs=this.renderSegRows(e);this.dayGrid.rowEls.forEach(function(e,t){var o,a,s=i.htmlToElement('');n&&n.row===t?o=n.el:(o=e.querySelector(".fc-content-skeleton tbody"))||(o=e.querySelector(".fc-content-skeleton table")),a=o.getBoundingClientRect().top-e.getBoundingClientRect().top,s.style.top=a+"px",s.querySelector("table").appendChild(r[t].tbodyEl),e.appendChild(s)})},t}(o.default);t.default=a},function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0});var r=n(1),i=n(3),o=n(63),a=function(e){function t(t){var n=e.call(this,t.context)||this;return n.fillSegTag="td",n.dayGrid=t,n}return r.__extends(t,e),t.prototype.renderSegs=function(t,n){"bgEvent"===t&&(n=n.filter(function(e){return e.eventRange.def.allDay})),e.prototype.renderSegs.call(this,t,n)},t.prototype.attachSegs=function(e,t){var n,r,i,o=[];for(n=0;n'),o=r.getElementsByTagName("tr")[0],c>0&&o.appendChild(i.createElement("td",{colSpan:c})),t.el.colSpan=p-c,o.appendChild(t.el),p