DEV: Move discourse-gamification to core (#33570)

https://meta.discourse.org/t/373574

Internal `/t/-/156778`
This commit is contained in:
Jarek Radosz
2025-07-15 16:38:05 +02:00
parent b3c1f4e0af
commit 48a1f9cd2f
226 changed files with 9365 additions and 0 deletions
+4
View File
@@ -102,6 +102,10 @@ discourse-hcaptcha:
- changed-files:
- any-glob-to-any-file: plugins/discourse-hcaptcha/**/*
discourse-gamification:
- changed-files:
- any-glob-to-any-file: plugins/discourse-gamification/**/*
footnote:
- changed-files:
- any-glob-to-any-file: plugins/footnote/**/*
+1
View File
@@ -68,6 +68,7 @@
!/plugins/discourse-assign
!/plugins/discourse-subscriptions
!/plugins/discourse-hcaptcha
!/plugins/discourse-gamification
/plugins/*/auto_generated
/spec/fixtures/plugins/my_plugin/auto_generated
+10
View File
@@ -0,0 +1,10 @@
# **Discourse Gamification** Plugin
# User Card
<img width="585" alt="Screen Shot 2022-03-18 at 9 33 24 AM" src="https://user-images.githubusercontent.com/50783505/159036553-b162f5b7-5acd-4134-ba5f-fa8dbb7f8f8f.png">
# User Metadata
<img width="1129" alt="Screen Shot 2022-03-18 at 10 48 25 AM" src="https://user-images.githubusercontent.com/50783505/159036559-15eff295-47f6-4294-bc4d-8ea4452b6d60.png">
# Directory
<img width="1106" alt="Screen Shot 2022-03-18 at 10 48 54 AM" src="https://user-images.githubusercontent.com/50783505/159036563-d866a819-e9c4-4c92-a415-1d51f5fe6d1a.png">
@@ -0,0 +1,95 @@
import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import { action } from "@ember/object";
import { readOnly } from "@ember/object/computed";
import { service } from "@ember/service";
import Form from "discourse/components/form";
import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { i18n } from "discourse-i18n";
export default class AdminCreateLeaderboard extends Component {
@service currentUser;
@service router;
@service toasts;
@tracked newLeaderboardName = "";
@tracked loading = false;
@readOnly("newLeaderboardName") nameValid;
get formData() {
return { name: "", created_by_id: this.currentUser.id };
}
@action
async createNewLeaderboard(data) {
if (this.loading) {
return;
}
this.loading = true;
try {
const leaderboard = await ajax(
"/admin/plugins/gamification/leaderboard",
{
data,
type: "POST",
}
);
this.toasts.success({
duration: 3000,
data: {
message: i18n("gamification.leaderboard.create_success"),
},
});
this.args.onCancel();
this.router.transitionTo(
"adminPlugins.show.discourse-gamification-leaderboards.show",
leaderboard.id
);
} catch (err) {
popupAjaxError(err);
} finally {
this.loading = false;
}
}
<template>
<div class="new-leaderboard-container">
<Form
@data={{this.formData}}
@onSubmit={{this.createNewLeaderboard}}
class="new-leaderboard-form"
as |form|
>
<form.Row>
<form.Field
@name="name"
@title={{i18n "gamification.leaderboard.name"}}
@showTitle={{false}}
class="new-leaderboard__name"
@validation="required"
as |field|
>
<field.Input
placeholder={{i18n "gamification.leaderboard.name_placeholder"}}
/>
</form.Field>
</form.Row>
<form.Actions>
<form.Submit />
<form.Button
@action={{@onCancel}}
class="new-leaderboard__cancel form-kit__button"
@label="gamification.cancel"
@title="gamification.cancel"
/>
</form.Actions>
</Form>
</div>
</template>
}
@@ -0,0 +1,174 @@
import Component from "@glimmer/component";
import { action } from "@ember/object";
import { service } from "@ember/service";
import BackButton from "discourse/components/back-button";
import Form from "discourse/components/form";
import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { AUTO_GROUPS } from "discourse/lib/constants";
import { i18n } from "discourse-i18n";
import GroupChooser from "select-kit/components/group-chooser";
import PeriodInput from "discourse/plugins/discourse-gamification/discourse/components/period-input";
export default class AdminEditLeaderboard extends Component {
@service currentUser;
@service site;
@service toasts;
@service router;
get siteGroups() {
return this.site.groups.rejectBy("id", AUTO_GROUPS.everyone.id);
}
get formData() {
return {
name: this.args.leaderboard.name,
from_date: this.args.leaderboard.fromDate,
to_date: this.args.leaderboard.toDate,
included_groups_ids: this.args.leaderboard.includedGroupsIds,
excluded_groups_ids: this.args.leaderboard.excludedGroupsIds,
visible_to_groups_ids: this.args.leaderboard.visibleToGroupsIds,
default_period: this.args.leaderboard.defaultPeriod,
period_filter_disabled: this.args.leaderboard.periodFilterDisabled,
};
}
@action
async save(data) {
try {
await ajax(
`/admin/plugins/gamification/leaderboard/${this.args.leaderboard.id}`,
{
data,
type: "PUT",
}
);
this.toasts.success({
duration: 3000,
data: {
message: i18n("gamification.leaderboard.save_success"),
},
});
await this.router.transitionTo(
"adminPlugins.show.discourse-gamification-leaderboards.index"
);
// To refresh the list of leaderboards in the index.
this.router.refresh();
} catch (err) {
popupAjaxError(err);
}
}
<template>
<BackButton
@route="adminPlugins.show.discourse-gamification-leaderboards"
@label="gamification.back"
/>
<Form
@data={{this.formData}}
@onSubmit={{this.save}}
class="edit-create-leaderboard-form"
as |form|
>
<form.Field
@name="name"
@title={{i18n "gamification.leaderboard.name"}}
@validation="required"
as |field|
>
<field.Input />
</form.Field>
<form.Row as |row|>
<row.Col @size={{6}}>
<form.Field
@name="from_date"
@title={{i18n "gamification.leaderboard.date.from"}}
as |field|
>
<field.Input @type="date" />
</form.Field>
</row.Col>
<row.Col @size={{6}}>
<form.Field
@name="to_date"
@title={{i18n "gamification.leaderboard.date.to"}}
as |field|
>
<field.Input @type="date" />
</form.Field>
</row.Col>
</form.Row>
<form.Field
@name="included_groups_ids"
@title={{i18n "gamification.leaderboard.included_groups"}}
as |field|
>
<field.Custom>
<GroupChooser
@id="leaderboard-edit__included-groups"
@content={{this.siteGroups}}
@value={{field.value}}
@labelProperty="name"
@onChange={{field.set}}
/>
</field.Custom>
</form.Field>
<form.Field
@name="excluded_groups_ids"
@title={{i18n "gamification.leaderboard.excluded_groups"}}
as |field|
>
<field.Custom>
<GroupChooser
@id="leaderboard-edit__excluded-groups"
@content={{this.siteGroups}}
@value={{field.value}}
@labelProperty="name"
@onChange={{field.set}}
/>
</field.Custom>
</form.Field>
<form.Field
@name="visible_to_groups_ids"
@title={{i18n "gamification.leaderboard.visible_to_groups"}}
as |field|
>
<field.Custom>
<GroupChooser
@id="leaderboard-edit__visible-groups"
@content={{this.siteGroups}}
@value={{field.value}}
@labelProperty="name"
@onChange={{field.set}}
/>
</field.Custom>
</form.Field>
<form.Field
@name="default_period"
@title={{i18n "gamification.leaderboard.default_period"}}
as |field|
>
<field.Custom>
<PeriodInput @value={{field.value}} @onChange={{field.set}} />
</field.Custom>
</form.Field>
<form.Field
@name="period_filter_disabled"
@title={{i18n "gamification.leaderboard.period_filter_disabled"}}
@showTitle={{false}}
as |field|
>
<field.Checkbox @value={{field.value}} />
</form.Field>
<form.Submit />
</Form>
</template>
}
@@ -0,0 +1,65 @@
import Controller from "@ember/controller";
import { action } from "@ember/object";
import { service } from "@ember/service";
import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from "discourse/lib/ajax-error";
import discourseComputed from "discourse/lib/decorators";
import { i18n } from "discourse-i18n";
import RecalculateScoresForm from "discourse/plugins/discourse-gamification/discourse/components/modal/recalculate-scores-form";
export default class AdminPluginsShowDiscourseGamificationLeaderboardsIndexController extends Controller {
@service modal;
@service dialog;
@service toasts;
creatingNew = false;
@discourseComputed("model.leaderboards.@each.updatedAt")
sortedLeaderboards(leaderboards) {
return leaderboards?.sortBy("updatedAt").reverse() || [];
}
@action
resetNewLeaderboard() {
this.set("creatingNew", false);
}
@action
destroyLeaderboard(leaderboard) {
this.dialog.deleteConfirm({
message: i18n("gamification.leaderboard.confirm_destroy"),
didConfirm: () => {
return ajax(
`/admin/plugins/gamification/leaderboard/${leaderboard.id}`,
{
type: "DELETE",
}
)
.then(() => {
this.toasts.success({
duration: 3000,
data: {
message: i18n("gamification.leaderboard.delete_success"),
},
});
this.model.leaderboards.removeObject(leaderboard);
})
.catch(popupAjaxError);
},
});
}
@action
recalculateScores() {
this.modal.show(RecalculateScoresForm, {
model: this.model,
});
}
parseDate(date) {
if (date) {
// using the format YYYY-MM-DD returns the previous day for some timezones
return date.replace(/-/g, "/");
}
}
}
@@ -0,0 +1,24 @@
import { service } from "@ember/service";
import { ajax } from "discourse/lib/ajax";
import DiscourseRoute from "discourse/routes/discourse";
import GamificationLeaderboard from "discourse/plugins/discourse-gamification/discourse/models/gamification-leaderboard";
export default class DiscourseGamificationLeaderboardShow extends DiscourseRoute {
@service adminPluginNavManager;
model(params) {
const leaderboardsData = this.modelFor(
"adminPlugins.show.discourse-gamification-leaderboards"
);
const id = parseInt(params.id, 10);
const leaderboard = leaderboardsData.leaderboards.findBy("id", id);
if (leaderboard) {
return leaderboard;
}
return ajax(
`/admin/plugins/discourse-gamification/leaderboards/${id}`
).then((response) => GamificationLeaderboard.create(response.leaderboard));
}
}
@@ -0,0 +1,24 @@
import EmberObject from "@ember/object";
import { service } from "@ember/service";
import DiscourseRoute from "discourse/routes/discourse";
import GamificationLeaderboard from "discourse/plugins/discourse-gamification/discourse/models/gamification-leaderboard";
export default class DiscourseGamificationLeaderboards extends DiscourseRoute {
@service adminPluginNavManager;
model() {
if (!this.currentUser?.admin) {
return { model: null };
}
const gamificationPlugin = this.adminPluginNavManager.currentPlugin;
return EmberObject.create({
leaderboards: gamificationPlugin.extras.gamification_leaderboards.map(
(leaderboard) => GamificationLeaderboard.create(leaderboard)
),
groups: gamificationPlugin.extras.gamification_groups,
recalculate_scores_remaining:
gamificationPlugin.extras.gamification_recalculate_scores_remaining,
});
}
}
@@ -0,0 +1,112 @@
import { concat, fn } from "@ember/helper";
import { LinkTo } from "@ember/routing";
import RouteTemplate from "ember-route-template";
import DBreadcrumbsItem from "discourse/components/d-breadcrumbs-item";
import DButton from "discourse/components/d-button";
import DPageSubheader from "discourse/components/d-page-subheader";
import formatDate from "discourse/helpers/format-date";
import { i18n } from "discourse-i18n";
import AdminCreateLeaderboard from "discourse/plugins/discourse-gamification/admin/components/admin-create-leaderboard";
export default RouteTemplate(
<template>
<DBreadcrumbsItem
@path="/admin/plugins/{{@controller.adminPluginNavManager.currentPlugin.name}}/leaderboards"
@label={{i18n "gamification.leaderboard.title"}}
/>
<div class="discourse-gamification__leaderboards admin-detail">
<DPageSubheader @titleLabel={{i18n "gamification.leaderboard.title"}}>
<:actions as |actions|>
{{#if @controller.model.leaderboards}}
<actions.Primary
@label="gamification.leaderboard.new"
@title="gamification.leaderboard.new"
class="leaderboard-admin__btn-new"
@action={{fn (mut @controller.creatingNew) true}}
/>
<actions.Default
@label="gamification.recalculate"
@title="gamification.recalculate"
class="leaderboard-admin__btn-recalculate"
@action={{@controller.recalculateScores}}
/>
{{/if}}
</:actions>
</DPageSubheader>
{{#if @controller.creatingNew}}
<AdminCreateLeaderboard @onCancel={{@controller.resetNewLeaderboard}} />
{{/if}}
<div class="leaderboards">
{{#if @controller.model.leaderboards}}
<table>
<thead>
<th>{{i18n "gamification.admin.name"}}</th>
<th>{{i18n "gamification.admin.period"}}</th>
<th></th>
</thead>
<tbody>
{{#each @controller.sortedLeaderboards as |leaderboard|}}
<tr id={{concat "leaderboard-admin__row-" leaderboard.id}}>
<td>
<LinkTo
@route="gamificationLeaderboard.byName"
@model={{leaderboard.id}}
>
{{leaderboard.name}}
</LinkTo>
</td>
<td>
{{#if leaderboard.fromDate}}
{{formatDate
(@controller.parseDate leaderboard.fromDate)
}}
-
{{formatDate (@controller.parseDate leaderboard.toDate)}}
{{else}}
{{leaderboard.defaultPeriod}}
{{/if}}
</td>
<td style="width: 120px">
<div class="leaderboard-admin__listitem-action">
<LinkTo
@route="adminPlugins.show.discourse-gamification-leaderboards.show"
@model={{leaderboard}}
class="btn leaderboard-admin__edit btn-text btn-small"
>{{i18n "gamification.edit"}} </LinkTo>
<DButton
class="btn-small leaderboard-admin__delete btn-danger"
@icon="trash-can"
@title="gamification.delete"
@action={{fn
@controller.destroyLeaderboard
leaderboard
}}
/>
</div>
</td>
</tr>
{{/each}}
</tbody>
</table>
{{else}}
{{#unless @controller.creatingNew}}
<div class="admin-plugin-config-area__empty-list">
{{i18n "gamification.leaderboard.none"}}
<DButton
@label="gamification.leaderboard.cta"
class="btn-default btn-small leaderboard-admin__cta-new"
@action={{fn (mut @controller.creatingNew) true}}
/>
</div>
{{/unless}}
{{/if}}
</div>
</div>
</template>
);
@@ -0,0 +1,10 @@
import RouteTemplate from "ember-route-template";
import AdminEditLeaderboard from "discourse/plugins/discourse-gamification/admin/components/admin-edit-leaderboard";
export default RouteTemplate(
<template>
<div class="admin-detail">
<AdminEditLeaderboard @leaderboard={{@model}} />
</div>
</template>
);
@@ -0,0 +1,93 @@
# frozen_string_literal: true
class DiscourseGamification::AdminGamificationLeaderboardController < Admin::AdminController
requires_plugin DiscourseGamification::PLUGIN_NAME
def index
render_serialized(
{ leaderboards: DiscourseGamification::GamificationLeaderboard.all },
AdminGamificationIndexSerializer,
root: false,
)
end
def show
render json:
LeaderboardSerializer.new(
DiscourseGamification::GamificationLeaderboard.find(params[:id]),
)
end
def create
params.require(%i[name created_by_id])
leaderboard =
DiscourseGamification::GamificationLeaderboard.new(
name: params[:name],
created_by_id: params[:created_by_id],
)
if leaderboard.save
Jobs.enqueue(Jobs::GenerateLeaderboardPositions, leaderboard_id: leaderboard.id)
render_serialized(leaderboard, LeaderboardSerializer, root: false)
else
render_json_error(leaderboard)
end
end
def update
params.require(%i[id name])
leaderboard = DiscourseGamification::GamificationLeaderboard.find(params[:id])
raise Discourse::NotFound unless leaderboard
leaderboard.update(
name: params[:name],
to_date: params[:to_date],
from_date: params[:from_date],
included_groups_ids: params[:included_groups_ids] || [],
excluded_groups_ids: params[:excluded_groups_ids] || [],
visible_to_groups_ids: params[:visible_to_groups_ids] || [],
default_period: params[:default_period],
period_filter_disabled: params[:period_filter_disabled] || false,
)
if leaderboard.save
# TODO(selase): Only refresh on specific attribute changes
Jobs.enqueue(Jobs::RefreshLeaderboardPositions, leaderboard_id: leaderboard.id)
render json: success_json
else
render_json_error(leaderboard)
end
end
def destroy
params.require(:id)
leaderboard = DiscourseGamification::GamificationLeaderboard.find(params[:id])
if leaderboard && leaderboard.destroy
Jobs.enqueue(Jobs::DeleteLeaderboardPositions, leaderboard_id: leaderboard.id)
end
render json: success_json
end
def recalculate_scores
DiscourseGamification::RecalculateScoresRateLimiter.perform!
since =
begin
Date.parse(params[:from_date]).midnight
rescue StandardError
raise Discourse::InvalidParameters.new(:from_date)
end
raise Discourse::InvalidParameters.new(:from_date) if since > Time.now
Jobs.enqueue(Jobs::RecalculateScores, since: since, user_id: current_user.id)
render json: success_json
end
end
@@ -0,0 +1,53 @@
# frozen_string_literal: true
class DiscourseGamification::AdminGamificationScoreEventController < Admin::AdminController
requires_plugin DiscourseGamification::PLUGIN_NAME
def show
params.permit(%i[id user_id date])
events = DiscourseGamification::GamificationScoreEvent.limit(100)
events = events.where(id: params[:id]) if params[:id]
events = events.where(user_id: params[:user_id]) if params[:user_id]
events = events.where(date: params[:date]) if params[:date]
raise Discourse::NotFound unless events
render_serialized({ events: events }, AdminGamificationScoreEventIndexSerializer, root: false)
end
def create
params.require(%i[user_id date points])
params.permit(:description)
event =
DiscourseGamification::GamificationScoreEvent.new(
user_id: params[:user_id],
date: params[:date],
points: params[:points],
description: params[:description],
)
if event.save
render_serialized(event, AdminGamificationScoreEventSerializer, root: false)
else
render_json_error(event)
end
end
def update
params.require(%i[id points])
params.permit(:description)
event = DiscourseGamification::GamificationScoreEvent.find(params[:id])
raise Discourse::NotFound unless event
event.update(points: params[:points], description: params[:description] || event.description)
if event.save
render json: success_json
else
render_json_error(event)
end
end
end
@@ -0,0 +1,40 @@
# frozen_string_literal: true
module ::DiscourseGamification
class GamificationLeaderboardController < ::ApplicationController
requires_plugin PLUGIN_NAME
def respond
discourse_expires_in 1.minute
default_leaderboard_id = GamificationLeaderboard.first.id
params[:id] ||= default_leaderboard_id
leaderboard = GamificationLeaderboard.find(params[:id])
period_param = params[:period] == "all" ? "all_time" : params[:period]
raise Discourse::NotFound unless @guardian.can_see_leaderboard?(leaderboard)
render_serialized(
{
leaderboard: leaderboard,
page: params[:page].to_i,
for_user_id: current_user&.id,
period: leaderboard.resolve_period(period_param),
user_limit: params[:user_limit]&.to_i,
},
LeaderboardViewSerializer,
root: false,
)
rescue LeaderboardCachedView::NotReadyError => e
Jobs.enqueue(Jobs::GenerateLeaderboardPositions, leaderboard_id: leaderboard.id)
render json:
LeaderboardSerializer
.new(leaderboard)
.as_json
.merge({ users: [], reason: e.message }),
status: 202
end
end
end
@@ -0,0 +1,11 @@
# frozen_string_literal: true
module ::DiscourseGamification
class DeletedGamificationLeaderboard
attr_reader :id
def initialize(id)
@id = id
end
end
end
@@ -0,0 +1,65 @@
# frozen_string_literal: true
module ::DiscourseGamification
class GamificationLeaderboard < ::ActiveRecord::Base
PAGE_SIZE = 100
self.table_name = "gamification_leaderboards"
validates :name, exclusion: { in: %w[new], message: "%{value} is reserved." }
attribute :period, :integer
enum :period, { all_time: 0, yearly: 1, quarterly: 2, monthly: 3, weekly: 4, daily: 5 }
def resolve_period(given_period)
return given_period if self.class.periods.key?(given_period)
self.class.periods.key(default_period) || "all_time"
end
def self.find_position_by(leaderboard_id:, for_user_id:, period: nil)
self.scores_for(leaderboard_id, for_user_id: for_user_id, period: period).first
end
def self.scores_for(leaderboard_id, page: 0, for_user_id: false, period: nil, user_limit: nil)
offset = PAGE_SIZE * page
limit = user_limit || PAGE_SIZE
period = period || "all_time"
leaderboard = self.find(leaderboard_id)
return [] unless leaderboard
LeaderboardCachedView.new(leaderboard).scores(
page: page,
for_user_id: for_user_id,
period: period,
limit: limit,
offset: offset,
)
end
end
end
# == Schema Information
#
# Table name: gamification_leaderboards
#
# id :bigint not null, primary key
# name :string not null
# from_date :date
# to_date :date
# for_category_id :integer
# created_by_id :integer not null
# created_at :datetime not null
# updated_at :datetime not null
# visible_to_groups_ids :integer default([]), not null, is an Array
# included_groups_ids :integer default([]), not null, is an Array
# excluded_groups_ids :integer default([]), not null, is an Array
# default_period :integer default(0)
# period_filter_disabled :boolean default(FALSE), not null
#
# Indexes
#
# index_gamification_leaderboards_on_name (name) UNIQUE
#
@@ -0,0 +1,76 @@
# frozen_string_literal: true
module ::DiscourseGamification
class GamificationScore < ::ActiveRecord::Base
self.table_name = "gamification_scores"
belongs_to :user
def self.enabled_scorables
Scorable.subclasses.filter { _1.enabled? }
end
def self.scorables_queries
enabled_scorables.map { "( #{_1.query} )" }.join(" UNION ALL ")
end
def self.calculate_scores(since_date: Date.today, only_subclass: nil)
queries = only_subclass&.query || scorables_queries
DB.exec(<<~SQL, since: since_date)
DELETE FROM gamification_scores
WHERE date >= :since;
INSERT INTO gamification_scores (user_id, date, score)
SELECT user_id, date, SUM(points) AS score
FROM (
#{queries}
UNION ALL
SELECT user_id, date, SUM(points) AS points
FROM gamification_score_events
WHERE date >= :since
GROUP BY 1, 2
) AS source
WHERE user_id IS NOT NULL
GROUP BY 1, 2
ON CONFLICT (user_id, date) DO UPDATE
SET score = EXCLUDED.score;
SQL
end
def self.merge_scores(source_user, target_user)
DB.exec(<<~SQL, source_id: source_user.id, target_id: target_user.id)
WITH new_scores AS (
SELECT :target_id AS user_id, date, SUM(score) AS score
FROM gamification_scores
WHERE user_id IN (:source_id, :target_id)
GROUP BY 1, 2
) INSERT INTO gamification_scores (user_id, date, score)
SELECT user_id, date, score AS score
FROM new_scores
ON CONFLICT (user_id, date) DO UPDATE
SET score = EXCLUDED.score;
SQL
DB.exec(<<~SQL, source_id: source_user.id)
DELETE FROM gamification_scores
WHERE user_id = :source_id;
SQL
end
end
end
# == Schema Information
#
# Table name: gamification_scores
#
# id :bigint not null, primary key
# user_id :integer not null
# date :date not null
# score :integer not null
#
# Indexes
#
# index_gamification_scores_on_date (date)
# index_gamification_scores_on_user_id_and_date (user_id,date) UNIQUE
#
@@ -0,0 +1,27 @@
# frozen_string_literal: true
module ::DiscourseGamification
class GamificationScoreEvent < ::ActiveRecord::Base
self.table_name = "gamification_score_events"
belongs_to :user
end
end
# == Schema Information
#
# Table name: gamification_score_events
#
# id :bigint not null, primary key
# user_id :integer not null
# date :date not null
# points :integer not null
# description :text
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_gamification_score_events_on_date (date)
# index_gamification_score_events_on_user_id_and_date (user_id,date)
#
@@ -0,0 +1,19 @@
# frozen_string_literal: true
class AdminGamificationIndexSerializer < ApplicationSerializer
attribute :gamification_recalculate_scores_remaining
has_many :gamification_leaderboards, serializer: LeaderboardSerializer, embed: :objects
has_many :gamification_groups, serializer: BasicGroupSerializer, embed: :object
def gamification_leaderboards
object[:leaderboards]
end
def gamification_groups
Group.all
end
def gamification_recalculate_scores_remaining
DiscourseGamification::RecalculateScoresRateLimiter.remaining
end
end
@@ -0,0 +1,9 @@
# frozen_string_literal: true
class AdminGamificationScoreEventIndexSerializer < ApplicationSerializer
has_many :events, serializer: AdminGamificationScoreEventSerializer, embed: :objects
def events
object[:events]
end
end
@@ -0,0 +1,5 @@
# frozen_string_literal: true
class AdminGamificationScoreEventSerializer < ApplicationSerializer
attributes :id, :user_id, :date, :points, :description, :created_at, :updated_at
end
@@ -0,0 +1,15 @@
# frozen_string_literal: true
class LeaderboardSerializer < ApplicationSerializer
attributes :id,
:name,
:created_by_id,
:from_date,
:to_date,
:visible_to_groups_ids,
:included_groups_ids,
:excluded_groups_ids,
:default_period,
:updated_at,
:period_filter_disabled
end
@@ -0,0 +1,34 @@
# frozen_string_literal: true
class LeaderboardViewSerializer < ApplicationSerializer
attributes :personal
has_one :leaderboard, serializer: LeaderboardSerializer, embed: :objects
has_many :users, serializer: UserScoreSerializer, embed: :objects
def leaderboard
object[:leaderboard]
end
def users
DiscourseGamification::GamificationLeaderboard.scores_for(
object[:leaderboard].id,
page: object[:page],
period: object[:period],
user_limit: object[:user_limit],
)
end
def personal
return {} if object[:for_user_id].blank?
user_score =
DiscourseGamification::GamificationLeaderboard.scores_for(
object[:leaderboard].id,
for_user_id: object[:for_user_id],
period: object[:period],
).take
{ user: UserScoreSerializer.new(user_score, root: false), position: user_score.try(:position) }
end
end
@@ -0,0 +1,5 @@
# frozen_string_literal: true
class UserScoreSerializer < BasicUserSerializer
attributes :total_score, :position
end
@@ -0,0 +1,15 @@
export default {
resource: "admin.adminPlugins.show",
path: "/plugins",
map() {
this.route(
"discourse-gamification-leaderboards",
{ path: "leaderboards" },
function () {
this.route("show", { path: "/:id" });
}
);
},
};
@@ -0,0 +1,41 @@
import Component from "@ember/component";
import { tagName } from "@ember-decorators/component";
import { or } from "truth-helpers";
import avatar from "discourse/helpers/avatar";
import number from "discourse/helpers/number";
import fullnumber from "../helpers/fullnumber";
@tagName("")
export default class GamificationLeaderboardRow extends Component {
rank = null;
<template>
<div
class="user {{if this.rank.currentUser 'user-highlight'}}"
id="leaderboard-user-{{this.rank.id}}"
>
<div class="user__rank">{{this.rank.position}}</div>
<div
class="user__avatar clickable"
role="button"
data-user-card={{this.rank.username}}
>
{{avatar this.rank imageSize="large"}}
<span class="user__name">
{{#if this.siteSettings.prioritize_username_in_ux}}
{{this.rank.username}}
{{else}}
{{or this.rank.name this.rank.username}}
{{/if}}
</span>
</div>
<div class="user__score">
{{#if this.site.mobileView}}
{{number this.rank.total_score}}
{{else}}
{{fullnumber this.rank.total_score}}
{{/if}}
</div>
</div>
</template>
}
@@ -0,0 +1,249 @@
import Component from "@ember/component";
import { hash } from "@ember/helper";
import { action } from "@ember/object";
import { service } from "@ember/service";
import { tagName } from "@ember-decorators/component";
import { or } from "truth-helpers";
import ConditionalLoadingSpinner from "discourse/components/conditional-loading-spinner";
import DButton from "discourse/components/d-button";
import LoadMore from "discourse/components/load-more";
import avatar from "discourse/helpers/avatar";
import icon from "discourse/helpers/d-icon";
import number from "discourse/helpers/number";
import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from "discourse/lib/ajax-error";
import discourseComputed from "discourse/lib/decorators";
import { i18n } from "discourse-i18n";
import PeriodChooser from "select-kit/components/period-chooser";
import fullnumber from "../helpers/fullnumber";
import GamificationLeaderboardRow from "./gamification-leaderboard-row";
import LeaderboardInfo from "./modal/leaderboard-info";
export const LEADERBOARD_PERIODS = [
"all_time",
"yearly",
"quarterly",
"monthly",
"weekly",
"daily",
];
function periodString(periodValue) {
switch (periodValue) {
case 0:
return "all";
case 1:
return "yearly";
case 2:
return "quarterly";
case 3:
return "monthly";
case 4:
return "weekly";
case 5:
return "daily";
default:
return "all";
}
}
@tagName("")
export default class GamificationLeaderboard extends Component {
@service router;
@service modal;
eyelineSelector = ".user";
page = 1;
loading = false;
canLoadMore = true;
period = "all";
init() {
super.init(...arguments);
const default_leaderboard_period = periodString(
this.model.leaderboard.default_period
);
this.set("period", default_leaderboard_period);
}
@discourseComputed("model.reason")
isNotReady(reason) {
return reason !== undefined;
}
@discourseComputed("model.users")
currentUserRanking() {
const user = this.model.personal;
return user || null;
}
@discourseComputed("model.users")
winners(users) {
return users.slice(0, 3);
}
@discourseComputed("model.users.[]")
ranking(users) {
users.forEach((user) => {
if (user.id === this.currentUser?.id) {
user.isCurrentUser = "true";
}
});
return users.slice(3);
}
@action
showLeaderboardInfo() {
this.modal.show(LeaderboardInfo);
}
@action
loadMore() {
if (this.loading || !this.canLoadMore) {
return;
}
this.set("loading", true);
return ajax(
`/leaderboard/${this.model.leaderboard.id}?page=${this.page}&period=${this.period}`
)
.then((result) => {
if (result.users.length === 0) {
this.set("canLoadMore", false);
}
this.set("page", (this.page += 1));
this.set("model.users", this.model.users.concat(result.users));
})
.finally(() => this.set("loading", false))
.catch(popupAjaxError);
}
@action
changePeriod(period) {
this.set("period", period);
return ajax(
`/leaderboard/${this.model.leaderboard.id}?period=${this.period}`
)
.then((result) => {
if (result.users.length === 0) {
this.set("canLoadMore", false);
this.set("model.reason", result.reason);
}
this.set("page", 1);
this.set("model.users", result.users);
this.set("model.personal", result.personal);
})
.finally(() => this.set("loading", false))
.catch(popupAjaxError);
}
@action
refresh() {
this.router.refresh();
}
<template>
<div class="leaderboard">
<div class="page__header">
<h1 class="page__title">{{this.model.leaderboard.name}}</h1>
<DButton
@action={{this.showLeaderboardInfo}}
class="-ghost"
@icon="circle-info"
@label={{unless this.site.mobileView "gamification.leaderboard.info"}}
/>
</div>
<div class="leaderboard__controls">
<PeriodChooser
@period={{this.period}}
@action={{this.changePeriod}}
@fullDay={{false}}
@options={{hash
disabled=this.model.leaderboard.period_filter_disabled
}}
class="leaderboard__period-chooser"
/>
{{#if this.currentUser.staff}}
<a href="/admin/plugins/gamification" class="leaderboard__settings">
{{icon "gear"}}
{{unless
this.site.mobileView
(i18n "gamification.leaderboard.link_to_settings")
}}
</a>
{{/if}}
</div>
{{#if this.isNotReady}}
<div class="leaderboard__not-ready">
<p>{{this.model.reason}}</p>
<DButton
@icon="arrows-rotate"
@label="gamification.leaderboard.refresh"
@action={{this.refresh}}
class="btn-primary refresh"
/>
</div>
{{else}}
<div class="podium__wrapper">
<div class="podium">
{{#each this.winners as |winner|}}
<div class="winner -position{{winner.position}}">
<div class="winner__crown">{{icon "crown"}}</div>
<div
class="winner__avatar clickable"
role="button"
data-user-card={{winner.username}}
>
{{avatar winner imageSize="huge"}}
<div class="winner__rank">
<span>{{winner.position}}</span>
</div>
</div>
<div class="winner__name">
{{#if this.siteSettings.prioritize_username_in_ux}}
{{winner.username}}
{{else}}
{{or winner.name winner.username}}
{{/if}}
</div>
<div class="winner__score">{{fullnumber
winner.total_score
}}</div>
</div>
{{/each}}
</div>
</div>
<div class="ranking">
<div class="ranking-col-names">
<span>{{i18n "gamification.leaderboard.rank"}}</span>
<span>{{icon "award"}}{{i18n "gamification.score"}}</span>
</div>
<div class="ranking-col-names__sticky-border"></div>
{{#if this.currentUserRanking.user}}
<div class="user -self">
<div class="user__rank">{{this.currentUserRanking.position}}</div>
<div class="user__name">{{i18n "gamification.you"}}</div>
<div class="user__score">
{{#if this.site.mobileView}}
{{number this.currentUserRanking.user.total_score}}
{{else}}
{{fullnumber this.currentUserRanking.user.total_score}}
{{/if}}
</div>
</div>
{{/if}}
<LoadMore @action={{this.loadMore}}>
{{#each this.ranking as |rank index|}}
<GamificationLeaderboardRow @rank={{rank}} @index={{index}} />
{{/each}}
</LoadMore>
<ConditionalLoadingSpinner @condition={{this.loading}} />
</div>
{{/if}}
</div>
</template>
}
@@ -0,0 +1,22 @@
import Component from "@ember/component";
import { LinkTo } from "@ember/routing";
import { classNames, tagName } from "@ember-decorators/component";
import fullnumber from "../helpers/fullnumber";
@tagName("span")
@classNames("gamification-score")
export default class GamificationScore extends Component {
<template>
{{#if this.site.default_gamification_leaderboard_id}}
<LinkTo
@route="gamificationLeaderboard.byName"
@model={{this.site.default_gamification_leaderboard_id}}
class="gamification-score__link"
>
{{fullnumber this.model.gamification_score}}
</LinkTo>
{{else}}
{{fullnumber this.model.gamification_score}}
{{/if}}
</template>
}
@@ -0,0 +1,51 @@
import Component from "@glimmer/component";
import { service } from "@ember/service";
import { or } from "truth-helpers";
import avatar from "discourse/helpers/avatar";
import concatClass from "discourse/helpers/concat-class";
import icon from "discourse/helpers/d-icon";
import number from "discourse/helpers/number";
import { i18n } from "discourse-i18n";
import sum from "../helpers/sum";
export default class MinimalGamificationLeaderboardRow extends Component {
@service siteSettings;
<template>
<div
id="leaderboard-user-{{@rank.id}}"
class={{concatClass "user" (if @rank.isCurrentUser "user-highlight")}}
>
<div class={{concatClass "user__rank" (if @rank.topRanked "-winner")}}>
{{#if @rank.topRanked}}
{{icon "crown"}}
{{else}}
{{sum @index 1}}
{{/if}}
</div>
<div
role="button"
data-user-card={{@rank.username}}
class="user__avatar clickable"
>
{{avatar @rank imageSize="small"}}
{{#if @rank.isCurrentUser}}
<span class="user__name">{{i18n "gamification.you"}}</span>
{{else}}
<span class="user__name">
{{#if this.siteSettings.prioritize_username_in_ux}}
{{@rank.username}}
{{else}}
{{or @rank.name @rank.username}}
{{/if}}
</span>
{{/if}}
</div>
<div class="user__score">
{{number @rank.total_score}}
</div>
</div>
</template>
}
@@ -0,0 +1,83 @@
import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import { LinkTo } from "@ember/routing";
import { service } from "@ember/service";
import icon from "discourse/helpers/d-icon";
import number from "discourse/helpers/number";
import { ajax } from "discourse/lib/ajax";
import { i18n } from "discourse-i18n";
import fullnumber from "../helpers/fullnumber";
import MinimalGamificationLeaderboardRow from "./minimal-gamification-leaderboard-row";
export default class extends Component {
@service site;
@tracked model;
constructor() {
super(...arguments);
// id is used by discourse-right-sidebar-blocks theme component
const endpoint = this.args.id
? `/leaderboard/${this.args.id}`
: "/leaderboard";
ajax(endpoint, { data: { user_limit: this.args.count || 10 } }).then(
(model) => {
for (const user of model.users) {
if (user.id === model.personal?.user?.id) {
user.isCurrentUser = "true";
}
}
if (model.users[0]) {
model.users[0].topRanked = true;
}
this.model = model;
}
);
}
get notTop10() {
return this.model?.personal?.position > 10;
}
<template>
<div class="leaderboard -minimal">
<div class="page__header">
<LinkTo
@route="gamificationLeaderboard.byName"
@model={{this.model.leaderboard.id}}
>
<h3 class="page__title">{{this.model.leaderboard.name}}</h3>
</LinkTo>
</div>
<div class="ranking-col-names">
<span>{{i18n "gamification.leaderboard.rank"}}</span>
<span>{{icon "award"}}{{i18n "gamification.score"}}</span>
</div>
<div class="ranking-col-names__sticky-border"></div>
{{#if this.notTopTen}}
<div class="user -self">
<div class="user__rank">{{this.model.personal.position}}</div>
<div class="user__name">{{i18n "gamification.you"}}</div>
<div class="user__score">
{{#if this.site.mobileView}}
{{number this.model.personal.user.total_score}}
{{else}}
{{fullnumber this.model.personal.user.total_score}}
{{/if}}
</div>
</div>
{{/if}}
{{#each this.model.users as |rank index|}}
<MinimalGamificationLeaderboardRow @rank={{rank}} @index={{index}} />
{{/each}}
</div>
</template>
}
@@ -0,0 +1,19 @@
import { htmlSafe } from "@ember/template";
import DModal from "discourse/components/d-modal";
import icon from "discourse/helpers/d-icon";
import { i18n } from "discourse-i18n";
const LeaderboardInfo = <template>
<DModal
@title={{i18n "gamification.leaderboard.modal.title"}}
@closeModal={{@closeModal}}
class="leaderboard-info-modal"
>
<:body>
{{icon "award"}}
{{htmlSafe (i18n "gamification.leaderboard.modal.text")}}
</:body>
</DModal>
</template>;
export default LeaderboardInfo;
@@ -0,0 +1,207 @@
import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import { action } from "@ember/object";
import { service } from "@ember/service";
import { eq } from "truth-helpers";
import DButton from "discourse/components/d-button";
import DModal from "discourse/components/d-modal";
import DatePickerPast from "discourse/components/date-picker-past";
import icon from "discourse/helpers/d-icon";
import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { bind } from "discourse/lib/decorators";
import { i18n } from "discourse-i18n";
import ComboBox from "select-kit/components/combo-box";
export default class RecalculateScoresForm extends Component {
@service messageBus;
@tracked updateRangeValue = 0;
@tracked recalculateFromDate = "";
@tracked haveAvailability = this.args.model.recalculate_scores_remaining > 0;
@tracked remaining = this.args.model.recalculate_scores_remaining;
@tracked status = "initial";
updateRange = [
{
name: i18n("gamification.update_range.last_10_days"),
value: 0,
calculation: { count: 10, type: "days" },
},
{
name: i18n("gamification.update_range.last_30_days"),
value: 1,
calculation: { count: 30, type: "days" },
},
{
name: i18n("gamification.update_range.last_90_days"),
value: 2,
calculation: { count: 90, type: "days" },
},
{
name: i18n("gamification.update_range.last_year"),
value: 3,
calculation: { count: 1, type: "year" },
},
{ name: i18n("gamification.update_range.all_time"), value: 4 },
{ name: i18n("gamification.update_range.custom_date_range"), value: 5 },
];
constructor() {
super(...arguments);
this.messageBus.subscribe("/recalculate_scores", this.onMessage);
}
willDestroy() {
super.willDestroy(...arguments);
this.messageBus.unsubscribe("/recalculate_scores", this.onMessage);
}
@bind
onMessage(message) {
if (message.success) {
this.status = "complete";
this.args.model.recalculate_scores_remaining = message.remaining;
this.remaining = message.remaining;
}
}
get remainingText() {
return i18n("gamification.daily_update_scores_availability", {
count: this.remaining,
});
}
get applyDisabled() {
if (!this.haveAvailability || this.status !== "initial") {
return true;
} else if (
this.updateRangeValue === 5 &&
this.recalculateFromDate <= moment().locale("en").utc().endOf("day")
) {
return true;
} else {
return false;
}
}
get dateRange() {
if (this.updateRangeValue === 4) {
return;
}
let today = moment().locale("en").utc().endOf("day");
let pastDate = this.dateRangeToDate(this.updateRangeValue);
return `${pastDate} - ${today.format(
i18n("dates.long_with_year_no_time")
)}`;
}
@bind
dateRangeToDate(updateRangeValue) {
if (updateRangeValue === 4) {
return "2014-8-26";
}
if (updateRangeValue === 5) {
return this.recalculateFromDate;
}
let today = moment().locale("en").utc().endOf("day");
let updateRange = this.updateRange.find((obj) => {
return obj.value === updateRangeValue;
});
let pastDate = today
.clone()
.subtract(updateRange.calculation.count, updateRange.calculation.type);
return pastDate.format(i18n("dates.long_with_year_no_time"));
}
@action
apply() {
this.status = "loading";
const data = {
from_date: this.dateRangeToDate(this.updateRangeValue),
};
return ajax(`/admin/plugins/gamification/recalculate-scores.json`, {
data,
type: "PUT",
}).catch(popupAjaxError);
}
<template>
<DModal
class="recalculate-scores-form-modal"
@title={{i18n "gamification.recalculate"}}
>
<:body>
{{#if (eq this.status "loading")}}
<div class="recalculate-modal__status">
<em>{{i18n "gamification.recalculating"}}</em>
</div>
{{else if (eq this.status "complete")}}
<div class="recalculate-modal__status is-success">
{{icon "check"}}
{{i18n "gamification.completed"}}
</div>
{{else}}
<form class="form-horizontal">
<div class="input-group">
<label>{{i18n "gamification.update_scores_help"}}</label>
<ComboBox
@id="update-range"
@valueProperty="value"
@content={{this.updateRange}}
@value={{this.updateRangeValue}}
@onChange={{action (mut this.updateRangeValue)}}
/>
{{#if (eq this.updateRangeValue 5)}}
<div class="input-group -custom-range">
<label>{{i18n "gamification.custom_range_from"}}</label>
<DatePickerPast
@id="custom-from-date"
@placeholder="yyyy-mm-dd"
@value={{this.recalculateFromDate}}
@onSelect={{action (mut this.recalculateFromDate)}}
class="date-input"
/>
</div>
{{else}}
<div class="recalculate-modal__date-range">
{{this.dateRange}}
</div>
{{/if}}
</div>
</form>
{{/if}}
</:body>
<:footer>
<DButton
@action={{this.apply}}
@label="gamification.apply"
@ariaLabel="gamification.apply"
@disabled={{this.applyDisabled}}
id="apply-section"
class="btn-primary"
/>
<DButton
@action={{@closeModal}}
@label={{if
(eq this.status "complete")
"gamification.close"
"gamification.cancel"
}}
@ariaLabel="gamification.cancel"
id="cancel-section"
class="btn-secondary"
/>
<div class="recalculate-modal__footer-text">{{this.remainingText}}</div>
</:footer>
</DModal>
</template>
}
@@ -0,0 +1,31 @@
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 { LEADERBOARD_PERIODS } from "discourse/plugins/discourse-gamification/discourse/components/gamification-leaderboard";
@selectKitOptions({
filterable: true,
allowAny: false,
})
@pluginApiIdentifiers("period-input")
@classNames("period-input", "period-input")
export default class PeriodInput extends ComboBoxComponent {
@computed
get content() {
let periods = [];
periods = periods.concat(
LEADERBOARD_PERIODS.map((period, index) => ({
name: i18n(`gamification.leaderboard.period.${period}`),
id: index,
}))
);
return periods;
}
}
@@ -0,0 +1,15 @@
import Component from "@ember/component";
import { classNames, tagName } from "@ember-decorators/component";
import { i18n } from "discourse-i18n";
import GamificationScore from "../../components/gamification-score";
@tagName("div")
@classNames("user-card-metadata-outlet", "gamification-score")
export default class GamificationScoreConnector extends Component {
<template>
{{#if this.user.gamification_score}}
<span class="desc">{{i18n "gamification.score"}} </span>
<span><GamificationScore @model={{this.user}} /></span>
{{/if}}
</template>
}
@@ -0,0 +1,17 @@
import { i18n } from "discourse-i18n";
import GamificationScore from "../../components/gamification-score";
const GamificationScoreConnector = <template>
{{#if @model.gamification_score}}
<div>
<dt>
{{i18n "gamification.score"}}
</dt>
<dd>
<GamificationScore @model={{@model}} />
</dd>
</div>
{{/if}}
</template>;
export default GamificationScoreConnector;
@@ -0,0 +1,5 @@
export default function () {
this.route("gamificationLeaderboard", { path: "/leaderboard" }, function () {
this.route("byName", { path: "/:leaderboardId" });
});
}
@@ -0,0 +1,8 @@
import Helper from "@ember/component/helper";
import I18n from "discourse-i18n";
export function fullnumber(number) {
return I18n.toNumber(number, { precision: 0 });
}
export default Helper.helper(fullnumber);
@@ -0,0 +1,7 @@
import Helper from "@ember/component/helper";
export function sum(params) {
return params[0] + params[1];
}
export default Helper.helper(sum);
@@ -0,0 +1,47 @@
import { tracked } from "@glimmer/tracking";
import { i18n } from "discourse-i18n";
import { LEADERBOARD_PERIODS } from "discourse/plugins/discourse-gamification/discourse/components/gamification-leaderboard";
export default class GamificationLeaderboard {
static create(args = {}) {
return new GamificationLeaderboard(args);
}
@tracked id;
@tracked createdAt;
@tracked updatedAt;
@tracked createdById;
@tracked excludedGroupsIds;
@tracked includedGroupsIds;
@tracked visibleToGroupsIds;
@tracked forCategoryId;
@tracked fromDate;
@tracked toDate;
@tracked name;
@tracked period;
@tracked periodFilterDisabled;
constructor(args = {}) {
this.id = args.id;
this.createdAt = args.created_at;
this.updatedAt = args.updated_at;
this.createdById = args.created_by_id;
this.excludedGroupsIds = args.excluded_groups_ids;
this.includedGroupsIds = args.included_groups_ids;
this.visibleToGroupsIds = args.visible_to_groups_ids;
this.forCategoryId = args.for_category_id;
this.fromDate = args.from_date;
this.toDate = args.to_date;
this.name = args.name;
this.period = args.period;
this.periodFilterDisabled = args.period_filter_disabled;
if (Number.isInteger(args.default_period)) {
this.defaultPeriod = i18n(
`gamification.leaderboard.period.${
LEADERBOARD_PERIODS[args.default_period]
}`
);
}
}
}
@@ -0,0 +1,15 @@
import { service } from "@ember/service";
import { ajax } from "discourse/lib/ajax";
import DiscourseRoute from "discourse/routes/discourse";
export default class GamificationLeaderboardByName extends DiscourseRoute {
@service router;
model(params) {
return ajax(`/leaderboard/${params.leaderboardId}`)
.then((response) => {
return response;
})
.catch(() => this.router.replaceWith("/404"));
}
}
@@ -0,0 +1,15 @@
import { service } from "@ember/service";
import { ajax } from "discourse/lib/ajax";
import DiscourseRoute from "discourse/routes/discourse";
export default class GamificationLeaderboardIndex extends DiscourseRoute {
@service router;
model() {
return ajax(`/leaderboard`)
.then((response) => {
return response;
})
.catch(() => this.router.replaceWith("/404"));
}
}
@@ -0,0 +1,6 @@
import RouteTemplate from "ember-route-template";
import GamificationLeaderboard from "../components/gamification-leaderboard";
export default RouteTemplate(
<template><GamificationLeaderboard @model={{@controller.model}} /></template>
);
@@ -0,0 +1,6 @@
import RouteTemplate from "ember-route-template";
import GamificationLeaderboard from "../components/gamification-leaderboard";
export default RouteTemplate(
<template><GamificationLeaderboard @model={{@controller.model}} /></template>
);
@@ -0,0 +1,21 @@
import { withPluginApi } from "discourse/lib/plugin-api";
export default {
name: "discourse-gamification-admin-plugin-configuration-nav",
initialize(container) {
const currentUser = container.lookup("service:current-user");
if (!currentUser || !currentUser.admin) {
return;
}
withPluginApi("1.1.0", (api) => {
api.addAdminPluginConfigurationNav("discourse-gamification", [
{
label: "gamification.leaderboard.title",
route: "adminPlugins.show.discourse-gamification-leaderboards",
},
]);
});
},
};
@@ -0,0 +1,5 @@
h3 {
a.gamification-score__link {
color: var(--tertiary);
}
}
@@ -0,0 +1,44 @@
.leaderboard-admin {
&__title {
display: inline-block;
}
&__cta-new {
display: flex;
margin-top: 1rem;
}
&__btn-recalculate {
float: right;
margin-right: 1rem;
}
&__btn-new {
float: right;
}
&__btn-back {
margin-bottom: 1rem;
padding-left: 0;
}
&__listitem-action {
text-align: right;
display: flex;
flex-direction: row;
gap: 0.5em;
justify-content: flex-end;
}
}
.leaderboard-edit {
&__cancel {
margin-left: 1rem;
}
}
.new-leaderboard-container {
.form-kit__row {
padding-top: 0;
}
}
@@ -0,0 +1,12 @@
.leaderboard-info-modal {
.d-modal__body {
display: flex;
align-items: center;
justify-content: center;
}
.d-icon-award {
margin-right: 2rem;
font-size: 4rem;
}
}
@@ -0,0 +1,59 @@
.leaderboard.-minimal {
.page {
&__header {
display: block;
margin: 0;
border-bottom: 0;
}
}
.ranking-col-names {
position: relative;
padding: 0.5em;
top: unset;
}
.ranking-col-names__sticky-border {
display: none;
}
.user {
padding: 0.75em 0.5em;
margin-bottom: 0;
background-color: transparent;
border-bottom: 1px solid var(--primary-low);
border-radius: 0;
&.-self {
display: none;
}
&__rank {
font-size: var(--font-up-3);
&.-winner {
color: $gold;
}
}
&__name {
font-size: var(--font-0);
vertical-align: middle;
}
&__avatar {
img {
margin-right: 0.25em;
}
}
&__score {
font-size: var(--font-up-1);
}
&-highlight {
background-color: var(--tertiary-50);
color: var(--primary);
}
}
}
@@ -0,0 +1,314 @@
.leaderboard {
$gold: #ffd82a;
$silver: #c4c4c4;
$bronze: #cd7f32;
$success: #4bb543;
.page {
&__header {
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid var(--header_primary);
margin-top: 2rem;
padding-bottom: 0.5rem;
svg {
vertical-align: middle;
}
}
&__title {
margin: 0;
}
}
&__controls {
display: flex;
justify-content: space-between;
align-items: center;
}
&__period-chooser {
margin: 1.5rem 0 0.75rem;
.selected-name {
margin: 0;
}
}
.-ghost,
.-ghost .d-icon {
padding: 0;
color: var(--tertiary);
border: 0;
background-color: transparent;
box-shadow: none;
&:hover {
color: var(--tertiary);
background-color: transparent;
.d-icon {
color: var(--tertiary);
background-color: transparent;
}
}
}
.podium {
display: flex;
justify-content: center;
align-items: center;
padding-top: 2rem;
padding-bottom: 5rem;
&__wrapper {
background: rgb(var(--tertiary-rgb), 0.1);
border-bottom-left-radius: 20px;
border-bottom-right-radius: 20px;
}
}
.winner {
overflow: hidden; // best worst solution in case the username is very long, better to cut it than have it wrap mid-name or overflow
&__crown {
display: none;
margin-bottom: 1rem;
text-align: center;
.d-icon {
color: $gold;
font-size: 40px;
}
}
&__avatar {
position: relative;
margin-bottom: 25px;
img {
width: 100%;
height: auto;
border-radius: 100%;
border-width: 5px;
border-style: solid;
box-sizing: border-box;
}
}
&__rank {
display: flex;
align-items: center;
justify-content: center;
position: absolute;
bottom: 0;
right: 50%;
transform: translate(50%, 50%);
width: 48px;
height: 48px;
border-radius: 100%;
font-size: 28px;
color: #222;
font-weight: bold;
}
&__name {
text-align: center;
}
&__score {
font-size: var(--font-up-5);
font-weight: bold;
text-align: center;
}
&.-position1 {
position: relative;
z-index: 1;
order: 2;
.winner__crown {
display: block;
}
.winner__avatar img {
border-color: $gold;
background-color: #ffe46a;
}
.winner__rank {
background-color: $gold;
}
}
&.-position2 {
order: 1;
transform: translate(20%, 35%);
.winner__avatar img {
border-color: $silver;
background-color: #d6d6d6;
}
.winner__rank {
background-color: $silver;
}
}
&.-position3 {
order: 3;
transform: translate(-20%, 35%);
.winner__avatar img {
border-color: $bronze;
background-color: #dca570; // in case avatar has a transparent bg
}
.winner__rank {
background-color: $bronze;
}
}
}
.ranking {
margin-top: 2rem;
}
.ranking-col-names {
position: sticky;
display: flex;
justify-content: space-between;
top: var(--header-offset);
background-color: var(--secondary);
border-bottom: 1px solid black;
.d-icon {
margin-right: 0.25rem;
}
&__sticky-border {
position: relative;
height: 2px;
margin-top: -1px;
background-color: var(--secondary);
}
}
.user {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1.25rem;
padding: 0.5rem 1.5rem;
background-color: rgb(var(--primary-rgb), 0.075);
border-radius: 20px;
&__rank {
flex-shrink: 0;
font-size: var(--font-up-5);
font-weight: bold;
font-family: monospace;
}
&__avatar {
margin: 0 1rem 0 1rem;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
img {
margin-right: 1rem;
border-radius: 100%;
}
}
&__name {
font-size: var(--font-up-2);
}
&__score {
flex-grow: 1;
text-align: right;
font-size: var(--font-up-4);
font-weight: bold;
}
&.-self {
margin-bottom: 2rem;
padding-top: 0.25rem;
padding-bottom: 0.25rem;
background-color: var(--tertiary);
color: var(--secondary);
.user__name {
flex-grow: 1;
text-align: center;
font-weight: bold;
}
.user__score {
flex-grow: 0;
}
}
&-highlight {
background-color: var(--tertiary);
color: var(--secondary);
}
}
&__not-ready {
display: flex;
align-items: center;
p {
margin-right: 0.5em;
}
}
}
.recalculate-scores-form-modal {
.modal-inner-container {
min-width: 25em;
.select-kit {
width: 100%;
}
}
.-custom-range {
display: flex;
align-items: baseline;
margin-top: 0.5rem;
> * {
flex: 1 1 0;
}
.date-picker-wrapper {
margin-left: auto;
}
.date-picker {
max-width: 400px;
}
}
.recalculate-modal__status.is-success .d-icon-check {
color: $success;
}
}
.recalculate-modal {
&__date-range {
margin-top: 0.5rem;
color: var(--primary-high);
}
&__footer-text {
margin-left: auto;
color: var(--primary-high);
}
}
@@ -0,0 +1,35 @@
.leaderboard {
.podium {
width: 60%;
margin-left: auto;
margin-right: auto;
}
.winner {
width: 23%;
&__name {
font-size: var(--font-up-1);
}
&__avatar {
img {
border-width: 4px;
}
}
&.-position1 {
width: 30%;
}
}
.ranking {
width: 75%;
margin-left: auto;
margin-right: auto;
}
.ranking-col-names {
padding: 1rem 1.5rem 0.25rem 1.5rem;
}
}
@@ -0,0 +1,52 @@
.leaderboard {
&__period-chooser.select-kit.dropdown-select-box {
display: flex;
margin: 1rem 0;
.period-chooser-header {
text-align: center;
}
}
.page__header button.-ghost,
&__settings {
font-size: var(--font-up-2);
padding: 0.5rem 0 0.5rem 0.5rem;
}
.winner {
width: 30%;
&__avatar {
img {
border-width: 3px;
}
}
&__rank {
width: 40px;
height: 40px;
font-size: 24px;
}
&__score {
font-size: var(--font-up-3);
}
&.-position1 {
width: 40%;
}
}
.ranking-col-names {
padding: 1rem 1rem 0.25rem 1rem;
}
.user {
padding: 0.5rem 1rem;
&__score {
font-size: var(--font-up-3);
}
}
}
@@ -0,0 +1,87 @@
# 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:
site_settings:
categories:
discourse_gamification: "Discourse Gamification"
js:
gamification_score: "الهتافات"
gamification:
score: "الهتافات"
you: "أنت"
leaderboard:
title: "لوحات المتصدرين"
info: "كيف تسير الأمور؟"
link_to_settings: "الإعدادات"
refresh: "تحديث"
modal:
title: "كيف تعمل لوحة المتصدرين؟"
text: "يتم منح النقاط مقابل التفاعل مع المجتمع، مثل الزيارة والإعجاب والنشر. يتم تحديث درجاتك كل بضع دقائق. لذا كُن مفيدًا ونشطًا وداعمًا، وارتقِ في الترتيب!"
name: "الاسم"
name_placeholder: "الاسم..."
new: "لوحة متصدرين جديدة"
create_success: "تم إنشاء لوحة المتصدرين"
delete_success: "تم حذف لوحة المتصدرين"
save_success: "تم حفظ لوحة الصدارة"
cta: "اصنع لوحة المتصدرين الأولى لك"
none: "لم يتم إنشاء أي لوحات متصدرين حتى الآن."
confirm_destroy: "هل تريد بالتأكيد حذف لوحة المتصدرين هذه؟"
date:
range: "نطاق التاريخ من/إلى"
from: "من التاريخ"
to: "إلى التاريخ"
helper: "إذا تم ترك التواريخ فارغة، فستعرض لوحة المتصدرين النقاط المكتسبة دون أي قيود زمنية."
visible_to_groups: "مرئية للمجموعات"
visible_to_groups_help: "سيتمكن المستخدمون في هذه المجموعات فقط من عرض لوحة المتصدرين. اتركه فارغًا للسماح للجميع."
included_groups: "المجموعات المتضمنة"
included_groups_help: "سيتم تضمين المستخدمين من هذه المجموعات فقط من عرض لوحة المتصدرين. اتركه فارغًا للسماح للجميع."
excluded_groups: "المجموعات المستثناة"
excluded_groups_help: "قم بحرمان المستخدمين في هذه المجموعات من التضمين في لوحة المتصدرين. اتركه فارغًا للسماح للجميع."
default_period: "الفترة الافتراضية"
default_period_help: "قم بتحديد الفترة الزمنية الافتراضية لعرضها في لوحة المتصدرين هذه."
period_filter_disabled: "إيقاف عامل تصفية الفترة الزمنية"
period:
all_time: "طوال الوقت"
yearly: "سنويًا"
quarterly: "ربع سنوي"
monthly: "شهريًا"
weekly: "أسبوعيًا"
daily: "يوميًا"
rank: "الترتيب"
create: "إنشاء"
cancel: "إلغاء"
close: "إغلاق"
delete: "حذف"
edit: "تعديل"
back: "الرجوع"
save: "حفظ"
apply: "تطبيق"
recalculate: "إعادة حساب النقاط"
recalculating: "جارٍ إعادة حساب النقاط..."
completed: "تم! تمت إعادة حساب النقاط بنجاح."
update_scores_help: "تحديث كل النقاط لجميع لوحات المتصدرين من"
update_range:
last_10_days: "آخر 10 أيام"
last_30_days: "آخر 30 يومًا"
last_90_days: "آخر 90 يومًا"
last_year: "العام الماضي"
all_time: "طوال الوقت"
custom_date_range: "نطاق مخصَّص"
custom_range_from: "من"
daily_update_scores_availability:
zero: "%{count} مرة إعادة حساب يومية متبقية"
one: "مرة إعادة حساب يومية واحدة (%{count}) متبقية"
two: "مرتا إعادة حساب يومية متبقيتان"
few: "%{count} مرات إعادة حساب يومية متبقية"
many: "%{count} مرة إعادة حساب يومية متبقية"
other: "%{count} مرة إعادة حساب يومية متبقية"
admin:
title: "التلعيب"
name: "الاسم"
period: "الفترة"
@@ -0,0 +1,34 @@
# 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:
gamification:
you: "Вы"
leaderboard:
link_to_settings: "Налады"
refresh: "абнавіць"
name: "імя"
period:
all_time: "За ўвесь час"
yearly: "штогод"
quarterly: "штоквартальна"
monthly: "штомесяц"
weekly: "штотыдзень"
daily: "штодня"
create: "стварыць"
cancel: "адмяніць"
close: "зачыніць"
delete: "выдаляць"
edit: "рэдагаваць"
back: "Назад"
save: "захаваць"
apply: "прымяніць"
update_range:
all_time: "За ўвесь час"
custom_range_from: "ад"
admin:
name: "імя"
@@ -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/
bg:
js:
gamification:
you: "Вие"
leaderboard:
link_to_settings: "Настройки"
refresh: "Обнови"
name: "Име"
period:
all_time: "От началото"
yearly: "Годишно"
quarterly: "Тримесечно"
monthly: "Месечно"
weekly: "Седмично"
daily: "Дневно"
create: "Създай"
cancel: "Прекрати"
close: "Затвори"
delete: "Изтрий"
edit: "Редактирай"
back: "Назад"
save: "Запази "
apply: "Приложи"
update_range:
last_10_days: "Последните 10 дни"
last_30_days: "Последните 30 дни"
last_90_days: "Последните 90 дни"
all_time: "От началото"
custom_range_from: "От"
admin:
name: "Име"
@@ -0,0 +1,34 @@
# 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:
gamification:
you: "Vi"
leaderboard:
link_to_settings: "Postavke"
refresh: "Refresh"
name: "Ime"
period:
all_time: "Oduvijek"
yearly: "Godišnje"
quarterly: "Kvartalno"
monthly: "Mesečno"
weekly: "Sedmično"
daily: "Dnevno"
create: "napravi"
cancel: "Odustani"
close: "Zatvori"
delete: "Delete"
edit: "Edit"
back: "Prethodno"
save: "Save"
apply: "Snimi"
update_range:
all_time: "Oduvijek"
custom_range_from: "Od"
admin:
name: "Ime"
@@ -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/
ca:
js:
gamification:
you: "Vós"
leaderboard:
link_to_settings: "Configuració"
refresh: "Actualitza"
name: "Nom"
period:
all_time: "Sempre"
yearly: "Anualment"
quarterly: "Trimestralment"
monthly: "Mensualment"
weekly: "Setmanalment"
daily: "Diàriament"
create: "Crea"
cancel: "Cancel·la"
close: "Tanca"
delete: "Suprimeix"
edit: "Edita"
back: "Enrere"
save: "Desa"
apply: "Aplica"
update_range:
last_10_days: "Els darrers 10 dies"
last_30_days: "Els darrers 30 dies"
last_90_days: "Els darrers 90 dies"
all_time: "Sempre"
custom_range_from: "De"
admin:
name: "Nom"
@@ -0,0 +1,85 @@
# 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:
site_settings:
categories:
discourse_gamification: "Discourse Gamification"
js:
gamification_score: "Ovace"
gamification:
score: "Ovace"
you: "Vy"
leaderboard:
title: "Žebříčky"
info: "Jak to funguje?"
link_to_settings: "Nastavení"
refresh: "Aktualizovat"
modal:
title: "Jak tento žebříček funguje?"
text: "Body se udělují za zapojení do komunity, například za návštěvy, lajkování a příspěvky. Vaše skóre se aktualizuje každých několik minut. Buďte tedy nápomocní, aktivní a podporující a stoupejte v žebříčku!"
name: "Jméno"
name_placeholder: "Název..."
new: "Nový žebříček"
create_success: "Žebříček vytvořen"
delete_success: "Žebříček odstraněn"
save_success: "Žebříček uložen"
cta: "Vytvořte si svůj první žebříček"
none: "Zatím nebyly vytvořeny žádné žebříčky."
confirm_destroy: "Opravdu chcete tento žebříček odstranit?"
date:
range: "Časové období od/do"
from: "Od data"
to: "Do data"
helper: "Pokud data zůstanou prázdná, ve výsledkové tabulce se zobrazí dosažené skóre bez jakéhokoli časového omezení."
visible_to_groups: "Viditelné pro skupiny"
visible_to_groups_help: "Žebříček budou moci zobrazit pouze uživatelé těchto skupin. Pokud chcete povolit zobrazení všem, ponechte prázdné."
included_groups: "Zahrnuté skupiny"
included_groups_help: "Do žebříčku budou zařazeni pouze uživatelé z těchto skupin. Chcete-li uvést všechny, ponechte prázdné."
excluded_groups: "Vyloučené skupiny"
excluded_groups_help: "Odeberte z žebříčku uživatele z těchto skupin. Ponechte prázdné, aby se v seznamu objevili všichni."
default_period: "Výchozí období"
default_period_help: "Nastavte výchozí časové období, pro které se má zobrazit tento žebříček."
period_filter_disabled: "Zakázat filtr časového období"
period:
all_time: "Za celou dobu"
yearly: "Ročně"
quarterly: "Čtvrtletně"
monthly: "Měsíčně"
weekly: "Týdně"
daily: "Denně"
rank: "Pořadí"
create: "Vytvořit"
cancel: "Zrušit"
close: "Zavřít"
delete: "Smazat"
edit: "Upravit"
back: "Zpět"
save: "Uložit"
apply: "Použít"
recalculate: "Přepočítat skóre"
recalculating: "Přepočítávání skóre..."
completed: "Hotovo! Skóre bylo úspěšně přepočítáno."
update_scores_help: "Aktualizovat všechna skóre pro všechny žebříčky od"
update_range:
last_10_days: "Posledních 10 dní"
last_30_days: "Posledních 30 dní"
last_90_days: "Posledních 90 dní"
last_year: "Minulý rok"
all_time: "Za celou dobu"
custom_date_range: "Vlastní rozsah"
custom_range_from: "Od koho"
daily_update_scores_availability:
one: "Zbývá %{count} denní přepočet"
few: "Zbývají %{count} denní přepočty"
many: "Zbývá %{count} denních přepočtů"
other: "Zbývá %{count} denních přepočtů"
admin:
title: "Gamifikace"
name: "Jméno"
period: "Období"
@@ -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/
da:
js:
gamification:
you: "Dig"
leaderboard:
link_to_settings: "Indstillinger"
refresh: "Genindlæs"
name: "Navn"
period:
all_time: "Alt"
yearly: "Årligt"
quarterly: "Kvartalvis"
monthly: "Månedligt"
weekly: "Ugentligt"
daily: "Dagligt"
create: "Opret"
cancel: "Annuller"
close: "Luk"
delete: "Slet"
edit: "Rediger"
back: "Tilbage"
save: "Gem"
apply: "Anvend"
update_range:
last_10_days: "Seneste 10 dage"
last_30_days: "Seneste 30 dage"
last_90_days: "Seneste 90 dage"
all_time: "Alt"
custom_range_from: "Fra"
admin:
name: "Navn"
@@ -0,0 +1,83 @@
# 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:
site_settings:
categories:
discourse_gamification: "Discourse Gamifizierung"
js:
gamification_score: "Beifall"
gamification:
score: "Beifall"
you: "Du"
leaderboard:
title: "Ranglisten"
info: "Wie funktioniert das?"
link_to_settings: "Einstellungen"
refresh: "Aktualisieren"
modal:
title: "Wie funktioniert die Rangliste?"
text: "Punkte werden für die Interaktion mit der Community vergeben, z. B. für Besuche, „Gefällt mir“ und Posten. Deine Punktzahl wird alle paar Minuten aktualisiert. Sei also hilfsbereit, aktiv sowie solidarisch und steige auf!"
name: "Name"
name_placeholder: "Name …"
new: "Neue Rangliste"
create_success: "Rangliste erstellt"
delete_success: "Rangliste gelöscht"
save_success: "Rangliste gespeichert"
cta: "Erstelle deine erste Rangliste"
none: "Es wurden noch keine Ranglisten erstellt."
confirm_destroy: "Bist du sicher, dass du diese Rangliste löschen möchtest?"
date:
range: "Datumsbereich von/bis"
from: "Ab Datum"
to: "Bis Datum"
helper: "Wenn die Daten leer gelassen werden, zeigt die Rangliste die erzielten Punktzahlen ohne Zeitbeschränkungen."
visible_to_groups: "Sichtbar für Gruppen"
visible_to_groups_help: "Nur Benutzer in diesen Gruppen können die Rangliste sehen. Leer lassen, um dies allen zu ermöglichen."
included_groups: "Eingeschlossene Gruppen"
included_groups_help: "Nur Benutzer in diesen Gruppen werden in die Rangliste aufgenommen. Leer lassen, um alle aufzulisten."
excluded_groups: "Ausgeschlossene Gruppen"
excluded_groups_help: "Benutzer in diesen Gruppen von der Rangliste entfernen. Leer lassen, um alle aufzulisten."
default_period: "Standard-Zeitraum"
default_period_help: "Lege den Standard-Zeitraum fest, der für diese Rangliste angezeigt werden soll."
period_filter_disabled: "Zeitraumfilter deaktivieren"
period:
all_time: "Gesamt"
yearly: "Jährlich"
quarterly: "Vierteljährlich"
monthly: "Monatlich"
weekly: "Wöchentlich"
daily: "Täglich"
rank: "Rang"
create: "Erstellen"
cancel: "Abbrechen"
close: "Schließen"
delete: "Löschen"
edit: "Bearbeiten"
back: "Zurück"
save: "Speichern"
apply: "Anwenden"
recalculate: "Scores neu berechnen"
recalculating: "Scores werden neu berechnet …"
completed: "Fertig! Die Scores wurden erfolgreich neu berechnet."
update_scores_help: "Alle Scores für sämtliche Ranglisten aktualisieren von"
update_range:
last_10_days: "Letzte 10 Tage"
last_30_days: "Letzte 30 Tage"
last_90_days: "Letzte 90 Tage"
last_year: "Letztes Jahr"
all_time: "Gesamt"
custom_date_range: "Benutzerdefinierter Bereich"
custom_range_from: "Von"
daily_update_scores_availability:
one: "%{count} tägliche Neuberechnung übrig"
other: "%{count} tägliche Neuberechnungen übrig"
admin:
title: "Gamifizierung"
name: "Name"
period: "Zeitraum"
@@ -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/
el:
js:
gamification:
you: "Εσείς"
leaderboard:
link_to_settings: "Ρυθμίσεις"
refresh: "Ανανέωση"
name: "Όνομα"
period:
all_time: "Από πάντα"
yearly: "Ετήσια"
quarterly: "Τριμηνιαία"
monthly: "Μηνιαίες"
weekly: "Εβδομαδιαίες"
daily: "Ημερήσιες"
create: "Δημιουργία"
cancel: "Ακύρωση"
close: "Κλείσιμο"
delete: "Σβήσιμο"
edit: "Επεξεργασία"
back: "Πίσω"
save: "Αποθήκευση"
apply: "Εφαρμογή"
update_range:
last_10_days: "Τελευταίες 10 ημέρες"
last_30_days: "Τελευταίες 30 ημέρες"
last_90_days: "Τελευταίες 90 ημέρες"
all_time: "Από πάντα"
custom_range_from: "Από"
admin:
name: "Όνομα"
@@ -0,0 +1,78 @@
en:
admin_js:
admin:
site_settings:
categories:
discourse_gamification: "Discourse Gamification"
js:
# directory column workaround
gamification_score: "Cheers"
gamification:
score: "Cheers"
you: "You"
leaderboard:
title: "Leaderboards"
info: "How does this work?"
link_to_settings: "Settings"
refresh: "Refresh"
modal:
title: "How does the leaderboard work?"
text: "Points are awarded for engaging with the community, such as visiting, liking, and posting. Your score is updated every few minutes. So go be helpful, active, and supportive, and rise through the ranks!"
name: "Name"
name_placeholder: "Name..."
new: "New leaderboard"
create_success: "Leaderboard created"
delete_success: "Leaderboard deleted"
save_success: "Leaderboard saved"
cta: "Make your first leaderboard"
none: "No leaderboards created yet."
confirm_destroy: "Are you sure you want to delete this leaderboard?"
date:
range: "From / To Date Range"
from: "From date"
to: "To date"
helper: "If dates are left empty the leaderboard will show scores earned without any time restrictions."
visible_to_groups: "Visible to groups"
visible_to_groups_help: "Only users on those groups will be able to view the leaderboard. Leave empty to allow everyone."
included_groups: "Included groups"
included_groups_help: "Only users on those groups will be included in the leaderboard. Leave empty to list everyone."
excluded_groups: "Excluded groups"
excluded_groups_help: "Remove users on those groups from being included in the leaderboard. Leave empty to list everyone."
default_period: "Default period"
default_period_help: "Set the default time period to display for this leaderboard."
period_filter_disabled: "Disable time period filter"
period:
all_time: "All Time"
yearly: "Yearly"
quarterly: "Quarterly"
monthly: "Monthly"
weekly: "Weekly"
daily: "Daily"
rank: "Rank"
create: "Create"
cancel: "Cancel"
close: "Close"
delete: "Delete"
edit: "Edit"
back: "Back"
save: "Save"
apply: "Apply"
recalculate: "Recalculate scores"
recalculating: "Recalculating scores..."
completed: "Done! The scores have been successfully recalculated."
update_scores_help: "Update all scores for all leaderboards from"
update_range:
last_10_days: "Last 10 days"
last_30_days: "Last 30 days"
last_90_days: "Last 90 days"
last_year: "Last year"
all_time: "All Time"
custom_date_range: "Custom range"
custom_range_from: "From"
daily_update_scores_availability:
one: "%{count} daily recalculation left"
other: "%{count} daily recalculations left"
admin:
title: "Gamification"
name: "Name"
period: "Period"
@@ -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:
@@ -0,0 +1,83 @@
# 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:
site_settings:
categories:
discourse_gamification: "Gamificación de Discourse"
js:
gamification_score: "Puntos"
gamification:
score: "Puntos"
you: "Tú"
leaderboard:
title: "Clasificaciones"
info: "¿Cómo funciona?"
link_to_settings: "Ajustes"
refresh: "Volver a cargar"
modal:
title: "¿Cómo funciona la clasificación?"
text: "Recibirás puntos por participar en la comunidad. Por ejemplo, cuando la visitas, usas el botón de me gusta o publicas cosas. Tu puntuación se actualiza cada pocos minutos. Así que ya sabes, ¡participa, ayuda y sube de posición!"
name: "Nombre"
name_placeholder: "Nombre..."
new: "Nueva tabla de clasificación"
create_success: "Tabla de clasificación creada"
delete_success: "Tabla de clasificación eliminada"
save_success: "Tabla de clasificación guardada"
cta: "Crea la primera tabla de clasificación"
none: "Todavía no hay ninguna tabla de clasificación."
confirm_destroy: "¿Estás seguro de que quieres eliminar esta tabla de clasificación?"
date:
range: "Fecha de inicio/fin"
from: "Desde la fecha"
to: "Hasta la fecha"
helper: "Si dejas las fechas vacías, la tabla incluirá puntuaciones obtenidas en cualquier momento"
visible_to_groups: "Visible para los grupos"
visible_to_groups_help: "Solo los usuarios de esos grupos podrán ver la tabla de clasificación. Déjalo vacío para permitir a todos."
included_groups: "Grupos incluidos"
included_groups_help: "Solo los usuarios de esos grupos se incluirán en la tabla de clasificación. Déjelo vacío para incluir a todos."
excluded_groups: "Grupos excluidos"
excluded_groups_help: "Elimina a los usuarios de esos grupos para que no se incluyan en la tabla de clasificación. Déjalo vacío para que aparezcan todos."
default_period: "Período predeterminado"
default_period_help: "Establece el período de tiempo predeterminado que se mostrará en esta tabla de clasificación."
period_filter_disabled: "Desactivar el filtro de periodo de tiempo"
period:
all_time: "Siempre"
yearly: "Anualmente"
quarterly: "Trimestralmente"
monthly: "Mensualmente"
weekly: "Semanalmente"
daily: "Diariamente"
rank: "Rango"
create: "Crear"
cancel: "Cancelar"
close: "Cerrar"
delete: "Eliminar"
edit: "Editar"
back: "Volver"
save: "Guardar"
apply: "Aplicar"
recalculate: "Recalcular puntuaciones"
recalculating: "Recalculando puntuaciones..."
completed: "¡Hecho! Las puntuaciones se han recalculado con éxito."
update_scores_help: "Actualiza todas las puntuaciones de todas las tablas de clasificación desde"
update_range:
last_10_days: "Últimos 10 días"
last_30_days: "Últimos 30 días"
last_90_days: "Últimos 90 días"
last_year: "El año pasado"
all_time: "Siempre"
custom_date_range: "Rango personalizado"
custom_range_from: "De"
daily_update_scores_availability:
one: "%{count} recálculo diario restante"
other: "%{count} recálculos diarios restantes"
admin:
title: "Gamification"
name: "Nombre"
period: "Período"
@@ -0,0 +1,34 @@
# 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:
gamification:
you: "Sina"
leaderboard:
link_to_settings: "Sätted"
refresh: "Värskenda"
name: "Nimi"
period:
all_time: "Alates algusest"
yearly: "Iga-aastaselt"
quarterly: "Kvartaalselt"
monthly: "Igakuiselt"
weekly: "Iganädalaselt"
daily: "Igapäevaselt"
create: "Loo"
cancel: "Tühista"
close: "Sulge"
delete: "Kustuta"
edit: "Muuda"
back: "Tagasi"
save: "Salvesta"
apply: "Rakenda"
update_range:
all_time: "Alates algusest"
custom_range_from: "Kellelt"
admin:
name: "Nimi"
@@ -0,0 +1,54 @@
# 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:
js:
gamification_score: "امتیازات"
gamification:
score: "امتیازات"
you: "شما"
leaderboard:
title: "تابلو امتیازات"
info: "این چطور کار می‌کنه؟"
link_to_settings: "تنظیمات"
refresh: "تازه‌سازی"
modal:
title: "تابلو امتیازات چطور کار می‌کنه؟"
name: "نام"
name_placeholder: "نام..."
confirm_destroy: "آیا مطمئنید که می‌خواهید این تابلوی امتیاز را حذف کنید؟"
date:
range: "از / تا محدوده تاریخ"
default_period: "بازه زمانی پیش‌فرض"
default_period_help: "بازه زمانی پیش‌فرض را برای نمایش این تابلوی امتیازات تنظیم کنید."
period:
all_time: "همیشه"
yearly: "سالیانه "
quarterly: "فصلی"
monthly: "ماهیانه"
weekly: "هفتگی"
daily: "روزانه"
rank: "رتبه"
create: "ایجاد"
cancel: "لغو"
close: "بستن"
delete: "حذف"
edit: "ویرایش"
back: "بازگشت"
save: "ذخیره"
apply: "اعمال کردن"
update_range:
last_10_days: "۱۰ روز گذشته"
last_30_days: "۳۰ روز گذشته"
last_90_days: "۹۰ روز گذشته"
last_year: "سال گذشته"
all_time: "همیشه"
custom_date_range: "محدوده سفارشی"
custom_range_from: "از طرف"
admin:
title: "بازی‌وارسازی"
name: "نام"
period: "دوره زمانی"
@@ -0,0 +1,83 @@
# 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:
site_settings:
categories:
discourse_gamification: "Discoursen Pelillistäminen"
js:
gamification_score: "Hurraukset"
gamification:
score: "Hurraukset"
you: "Sinä"
leaderboard:
title: "Tulostaulukot"
info: "Miten tämä toimii?"
link_to_settings: "Asetukset"
refresh: "Päivitä"
modal:
title: "Miten tulostaulukko toimii?"
text: "Pisteitä annetaan vuorovaikutuksesta yhteisön kanssa, kuten vierailusta, tykkäämisestä ja viestien lähettämisestä. Tuloksesi päivitetään muutaman minuutin välein. Ole siis avulias, aktiivinen ja tukea antava ja nouse sijoituksissa!"
name: "Nimi"
name_placeholder: "Nimi..."
new: "Uusi tulostaulukko"
create_success: "Tulostaulukko luotu"
delete_success: "Tulostaulukko poistettu"
save_success: "Tulostaulukko tallennettu"
cta: "Tee ensimmäinen tulostaulukkosi"
none: "Tulostaulukoita ei ole vielä luotu."
confirm_destroy: "Oletko varma, että haluat poistaa tämän tulostaulukon?"
date:
range: "Päivämääräalue (alkaen/päättyen)"
from: "Alkamispäivä"
to: "Päättymispäivä"
helper: "Jos päivämäärät jätetään tyhjiksi, tulostaulukko näyttää ansaitut pisteet ilman aikarajoituksia."
visible_to_groups: "Näkyy ryhmille"
visible_to_groups_help: "Vain näiden ryhmien käyttäjät voivat tarkastella tulostaulukkoa. Salli kaikki jättämällä tyhjäksi."
included_groups: "Sisältyvät ryhmät"
included_groups_help: "Vain näiden ryhmien käyttäjät sisältyvät tulostaulukkoon. Listaa kaikki jättämällä tyhjäksi."
excluded_groups: "Poissuljetut ryhmät"
excluded_groups_help: "Jätä näiden ryhmien käyttäjät pois tulostaulukosta. Listaa kaikki jättämällä tyhjäksi."
default_period: "Oletusjakso"
default_period_help: "Aseta näytettävä oletusaika tälle tulostaulukolle."
period_filter_disabled: "Poista ajanjaksosuodatin käytöstä"
period:
all_time: "Kaikilta ajoilta"
yearly: "Vuosittainen"
quarterly: "Neljännesvuosittainen"
monthly: "Kuukausittainen"
weekly: "Viikoittainen"
daily: "Päivittäinen"
rank: "Sijoitus"
create: "Luo"
cancel: "Peruuta"
close: "Sulje"
delete: "Poista"
edit: "Muokkaa"
back: "Takaisin"
save: "Tallenna"
apply: "Käytä"
recalculate: "Laske pisteet uudelleen"
recalculating: "Pisteitä lasketaan uudelleen..."
completed: "Valmista! Pisteet on laskettu uudelleen."
update_scores_help: "Päivitä kaikkien tulostaulukoiden kaikki tulokset alkaen"
update_range:
last_10_days: "Viimeiset 10 päivää"
last_30_days: "Viimeiset 30 päivää"
last_90_days: "Viimeiset 90 päivää"
last_year: "Viime vuosi"
all_time: "Kaikilta ajoilta"
custom_date_range: "Mukautettu ajanjakso"
custom_range_from: "Ajalta"
daily_update_scores_availability:
one: "%{count} päivittäinen uudelleenlasketa jäljellä"
other: "%{count} päivittäistä uudelleenlasketaa jäljellä"
admin:
title: "Pelillistäminen"
name: "Nimi"
period: "Jakso"
@@ -0,0 +1,83 @@
# 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:
site_settings:
categories:
discourse_gamification: "Gamification de Discourse"
js:
gamification_score: "Acclamations"
gamification:
score: "Acclamations"
you: "Vous"
leaderboard:
title: "Classements"
info: "Comment ça fonctionne ?"
link_to_settings: "Paramètres"
refresh: "Actualiser"
modal:
title: "Comment fonctionne le classement ?"
text: "Des points sont attribués pour l'engagement auprès de la communauté, par exemple pour les visites, les mentions J'aime et les publications. Votre score est mis à jour toutes les quelques minutes. Veillez donc à être aimable, actif(ve) et solidaire, et gravissez les échelons !"
name: "Nom"
name_placeholder: "Nom…"
new: "Nouveau classement"
create_success: "Classement créé"
delete_success: "Classement supprimé"
save_success: "Classement enregistré"
cta: "Créez votre premier classement"
none: "Aucun classement n'a encore été créé."
confirm_destroy: "Voulez-vous vraiment supprimer ce classement ?"
date:
range: "Plage de date du/au"
from: "Date de début"
to: "Date de fin"
helper: "Si les dates sont laissées vides, le classement affichera les scores obtenus sans aucune restriction de temps."
visible_to_groups: "Visible par les groupes"
visible_to_groups_help: "Seuls les utilisateurs de ces groupes pourront voir le classement. Laissez le champ vide pour autoriser tout le monde."
included_groups: "Groupes inclus"
included_groups_help: "Seuls les utilisateurs de ces groupes seront inclus dans le classement. Laissez ce champ vide pour lister tout le monde."
excluded_groups: "Groupes exclus"
excluded_groups_help: "Supprimez les utilisateurs de ces groupes afin qu'ils ne figurent pas dans le classement. Laissez ce champ vide pour lister tout le monde."
default_period: "Période par défaut"
default_period_help: "Définissez la période par défaut à afficher pour ce classement."
period_filter_disabled: "Désactiver le filtre de période"
period:
all_time: "Depuis toujours"
yearly: "Annuel"
quarterly: "Trimestriel"
monthly: "Mensuel"
weekly: "Hebdomadaire"
daily: "Quotidien"
rank: "Rang"
create: "Créer"
cancel: "Annuler"
close: "Fermer"
delete: "Supprimer"
edit: "Modifier"
back: "Retour"
save: "Enregistrer"
apply: "Appliquer"
recalculate: "Recalculer les scores"
recalculating: "Recalcul des scores..."
completed: "Terminé ! Les scores ont été recalculés avec succès."
update_scores_help: "Mettre à jour tous les scores pour tous les classements de"
update_range:
last_10_days: "10 derniers jours"
last_30_days: "30 derniers jours"
last_90_days: "90 derniers jours"
last_year: "L'année dernière"
all_time: "Depuis toujours"
custom_date_range: "Plage personnalisée"
custom_range_from: "De"
daily_update_scores_availability:
one: "%{count} recalcul quotidien restant"
other: "%{count} recalculs quotidiens restants"
admin:
title: "Gamification"
name: "Nom"
period: "Période"
@@ -0,0 +1,34 @@
# 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:
gamification:
you: "Vostede"
leaderboard:
link_to_settings: "Configuración"
refresh: "Actualizar"
name: "Nome"
period:
all_time: "Desde o principio"
yearly: "Anual"
quarterly: "Trimestral"
monthly: "Mensual"
weekly: "Semanal"
daily: "Diario"
create: "Crear"
cancel: "Cancelar"
close: "Pechar"
delete: "Eliminar"
edit: "Editar"
back: "Volver"
save: "Gardar"
apply: "Aplicar"
update_range:
all_time: "Desde o principio"
custom_range_from: "De"
admin:
name: "Nome"
@@ -0,0 +1,85 @@
# 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:
site_settings:
categories:
discourse_gamification: "Discourse משחוק"
js:
gamification_score: "תשועות"
gamification:
score: "תשועות"
you: "אני"
leaderboard:
title: "לוחות תוצאות"
info: "איך זה עובד?"
link_to_settings: "הגדרות"
refresh: "רענון"
modal:
title: "איך עובד לוח התוצאות?"
text: "נקודות מוענקות על מעורבות בקהילה, כגון ביקור, סימוני לייק ופרסום. הניקוד שלך מתעדכן כל כמה דקות. לכן, מומלץ להביא תועלת, לקדם פעילות ולתמוך, ככה הניקוד הולך ועולה!"
name: "שם"
name_placeholder: "שם…"
new: "לוח תוצאות חדש"
create_success: "לוח תוצאות נוצר"
delete_success: "לוח תוצאות נמחק"
save_success: "לוח תוצאות נשמר"
cta: "יצירת לוח התוצאות הראשון שלך"
none: "לא נוצרו עדיין לוחות תוצאות."
confirm_destroy: "למחוק את לוח התוצאות הזה?"
date:
range: "טווח תאריך מ/עד"
from: "מתאריך"
to: "עד תאריך"
helper: "אם התאריכים נשארים ריקים לוח התוצאות יציג ניקוד שהתקבל ללא מגבלות זמן."
visible_to_groups: "גלוי לקבוצות"
visible_to_groups_help: "רק משתמשים מהקבוצות האלו יוכלו לצפות בלוח התוצאות. ניתן ריק כדי שכולם יוכלו."
included_groups: "קבוצות שנכללות"
included_groups_help: "רק משתמשים מהקבוצות האלו יקחו חלק בלוח התוצאות. ניתן להשאיר ריק כדי שכולם יוכלו."
excluded_groups: "קבוצות מוחרגות"
excluded_groups_help: "ניתן להסיר משתמשים מהקבוצות האלו כדי שלא יהיו חלק מלוח התוצאות. או להשאיר ריק כדי להציג את כולם."
default_period: "פרק זמן כברירת מחדל"
default_period_help: "להגדיר את פרק הזמן כברירת המחדל כדי להציג ללוח התוצאות הזה."
period_filter_disabled: "השבתת מסנן פרק זמן"
period:
all_time: "כל הזמן"
yearly: "שנתי"
quarterly: "רבעוני"
monthly: "חודשי"
weekly: "שבועי"
daily: "יומי"
rank: "דירוג"
create: "יצירה"
cancel: "ביטול"
close: "סגירה"
delete: "מחיקה"
edit: "עריכה"
back: "חזרה"
save: "שמירה"
apply: "החלה"
recalculate: "חישוב ניקוד מחדש"
recalculating: "הניקוד מחושב מחדש…"
completed: "סיימנו! הניקוד חושב מחדש בהצלחה."
update_scores_help: "לעדכן את כל הניקוד בכל לוחות התוצאות מתוך"
update_range:
last_10_days: "10 הימים האחרונים"
last_30_days: "30 הימים האחרונים"
last_90_days: "90 הימים האחרונים"
last_year: "שנה שעברה"
all_time: "כל הזמן"
custom_date_range: "טווח מותאם אישית"
custom_range_from: "מאת"
daily_update_scores_availability:
one: "נותר חישוב ניקוד מחדש יומי אחד"
two: "נותרו %{count} חישובים מחדש יומיים"
many: "נותרו %{count} חישובים מחדש יומיים"
other: "נותרו %{count} חישובים מחדש יומיים"
admin:
title: "משחוק"
name: "שם"
period: "פרק זמן"
@@ -0,0 +1,49 @@
# 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:
gamification_score: "Živjeli"
gamification:
score: "Živjeli"
you: "Vi"
leaderboard:
title: "Ljestvice članova"
info: "Kako ovo radi?"
link_to_settings: "Postavke"
refresh: "Osvježi"
modal:
title: "Kako radi ploča s najboljim rezultatima?"
text: "Bodovi se dodjeljuju za interakciju sa zajednicom, kao što su posjete, sviđanje i objavljivanje. Vaš rezultat se ažurira svakih nekoliko minuta. Zato budite od pomoći, aktivni i podržavajte i napredujte kroz činove!"
name: "Ime"
name_placeholder: "Ime..."
confirm_destroy: "Jeste li sigurni da želite izbrisati ovu ploču s najboljim rezultatima?"
date:
range: "Od/do datumskog raspona"
period:
all_time: "Oduvijek"
yearly: "Godišnje"
quarterly: "Tromjesečno"
monthly: "Mjesečno"
weekly: "Tjedno"
daily: "Dnevno"
create: "Stvorite"
cancel: "Odustani"
close: "Zatvori"
delete: "Izbriši"
edit: "Uredi"
back: "Natrag"
save: "Spremi"
apply: "Primijeni"
update_range:
last_10_days: "Zadnjih 10 dana"
last_30_days: "Zadnjih 30 dana"
last_90_days: "Zadnjih 90 dana"
all_time: "Oduvijek"
custom_range_from: "Od"
admin:
title: "Gamifikacija"
name: "Ime i prezime"
@@ -0,0 +1,83 @@
# 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_gamification: "Discourse Gamifikáció"
js:
gamification_score: "Pontok"
gamification:
score: "Pontok"
you: "Ön"
leaderboard:
title: "Ranglisták"
info: "Hogyan működik?"
link_to_settings: "Beállítások"
refresh: "Újratöltés"
modal:
title: "Hogyan működik a ranglista?"
text: "Pontok járnak a közösséggel való kapcsolattartásért, például látogatásért, kedvelésért és bejegyzésért. A pontszám néhány percenként frissül. Legyen tehát segítőkész, aktív és támogató, és emelkedjen a ranglétrán!"
name: "Név"
name_placeholder: "Név..."
new: "Új ranglista"
create_success: "Ranglista létrehozva"
delete_success: "Ranglista törölve"
save_success: "Ranglista mentve"
cta: "Első ranglista létrehozása"
none: "Nincs még ranglista létrehozva."
confirm_destroy: "Biztosan törli ezt a ranglistát?"
date:
range: "Dátumtartomány"
from: "Ettől:"
to: "Mostanáig"
helper: "Ha a dátumok üresen maradnak, a ranglistán időkorlátozás nélkül megszerzett pontszámok jelennek meg."
visible_to_groups: "Csoportok számára látható"
visible_to_groups_help: "Csak ezeknek a csoportoknak a felhasználói láthatják a ranglistát. Hagyja üresen, hogy bárki megtekinthesse."
included_groups: "Csatolt csoportok"
included_groups_help: "Csak ezeknek a csoportoknak a felhasználói fognak szerepelni a ranglistán. Hagyja üresen, hogy mindenkit felsoroljon."
excluded_groups: "Kizárt csoportok"
excluded_groups_help: "Távolítsa el a csoportok felhasználóit, hogy ne szerepeljenek a ranglistán. Hagyja üresen, hogy mindenkit felsoroljon."
default_period: "Alapértelmezett időszak"
default_period_help: "Állítsa be a ranglistán megjelenítendő alapértelmezett időtartamot."
period_filter_disabled: "Az időszakszűrő letiltása"
period:
all_time: "Bármikor"
yearly: "Éves"
quarterly: "Negyedéves"
monthly: "Havi"
weekly: "Heti"
daily: "Napi"
rank: "Rangsor"
create: "Létrehozás"
cancel: "Mégse"
close: "Lezárás"
delete: "Törlés"
edit: "Szerkesztés"
back: "Vissza"
save: "Mentés"
apply: "Alkalmaz"
recalculate: "Pontszámok újraszámítása"
recalculating: "A pontszámok újraszámítása..."
completed: "Kész! A pontszámokat sikeresen újraszámoltuk."
update_scores_help: "Frissítse az összes eredményt az összes ranglistán az alábbiakból"
update_range:
last_10_days: "Elmúlt 10 nap"
last_30_days: "Elmúlt 30 nap"
last_90_days: "Elmúlt 90 nap"
last_year: "Tavaly"
all_time: "Bármikor"
custom_date_range: "Egyedi tartomány"
custom_range_from: "From"
daily_update_scores_availability:
one: "%{count} napi újraszámítás maradt"
other: "%{count} napi újraszámolás maradt"
admin:
title: "Gamifikáció"
name: "Név"
period: "Időszak"
@@ -0,0 +1,34 @@
# 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:
gamification:
you: "Դուք "
leaderboard:
link_to_settings: "Կարգավորումներ"
refresh: "Թարմացնել"
name: "Անուն"
period:
all_time: "Ամբողջ Ժամանակվա"
yearly: "Տարվա Ընթացքում"
quarterly: "Եռամսյակի Ընթացքում"
monthly: "Ամսվա Ընթացքում"
weekly: "Շաբաթվա Ընթացքում"
daily: "Օրվա Ընթացքում"
create: "Ստեղծել"
cancel: "Չեղարկել"
close: "Փակել"
delete: "Ջնջել"
edit: "Խմբագրել"
back: "Ետ"
save: "Պահպանել"
apply: "Կիրառել"
update_range:
all_time: "Ամբողջ Ժամանակվա"
custom_range_from: "Ում կողմից"
admin:
name: "Անուն"
@@ -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/
id:
js:
gamification:
you: "Anda"
leaderboard:
link_to_settings: "Pengaturan"
refresh: "Segarkan"
name: "Nama"
period:
all_time: "Sepanjang Waktu"
create: "Buat"
cancel: "Batal"
close: "Tutup"
delete: "Hapus"
edit: "Ubah"
save: "Simpan"
apply: "menerapkan"
update_range:
last_10_days: "10 hari terakhir"
last_30_days: "30 hari terakhir"
last_90_days: "90 hari terakhir"
all_time: "Sepanjang Waktu"
custom_range_from: "Dari"
admin:
name: "Nama"
@@ -0,0 +1,83 @@
# 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:
site_settings:
categories:
discourse_gamification: "Discourse Gamification"
js:
gamification_score: "Complimenti"
gamification:
score: "Complimenti"
you: "Tu"
leaderboard:
title: "Classifiche"
info: "Come funziona?"
link_to_settings: "Impostazioni"
refresh: "Aggiorna"
modal:
title: "Come funziona la classifica?"
text: "I punti vengono assegnati per il coinvolgimento con la community, ad esempio in base al numero di visite, Mi piace e pubblicazioni. Il tuo punteggio viene aggiornato ogni pochi minuti. Quindi cerca di essere propositivo, attivo e di supporto per scalare le classifiche!"
name: "Nome"
name_placeholder: "Nome..."
new: "Nuova classifica"
create_success: "Classifica creata"
delete_success: "Classifica eliminata"
save_success: "Classifica salvata"
cta: "Crea la tua prima classifica"
none: "Nessuna classifica ancora creata."
confirm_destroy: "Vuoi cancellare questa classifica?"
date:
range: "Intervallo di date dal/al"
from: "Dalla data"
to: "A oggi"
helper: "Se le date vengono lasciate vuote, la classifica mostrerà i punteggi ottenuti senza alcun limite di tempo."
visible_to_groups: "Visibile ai gruppi"
visible_to_groups_help: "Solo gli utenti di questi gruppi potranno visualizzare la classifica. Lascia vuota l'opzione per consentire a tutti la visione."
included_groups: "Gruppi inclusi"
included_groups_help: "Solo gli utenti di questi gruppi saranno inclusi in classifica. Lascia vuota l'opzione per elencare tutti."
excluded_groups: "Gruppi esclusi"
excluded_groups_help: "Gli utenti di questi gruppi saranno esclusi dalla classifica. Lascia vuota l'opzione per elencare tutti."
default_period: "Periodo predefinito"
default_period_help: "Imposta il periodo di tempo predefinito per visualizzare questa classifica."
period_filter_disabled: "Disattiva il filtro del periodo di tempo"
period:
all_time: "Di Sempre"
yearly: "Annuale"
quarterly: "Trimestrale"
monthly: "Mensile"
weekly: "Settimanale"
daily: "Giornaliero"
rank: "Posizione"
create: "Crea"
cancel: "Annulla"
close: "Chiudi"
delete: "Cancella"
edit: "Modifica"
back: "Indietro"
save: "Salva"
apply: "Applica"
recalculate: "Ricalcola i punteggi"
recalculating: "Ricalcolo dei punteggi..."
completed: "Fatto! I punteggi sono stati ricalcolati correttamente."
update_scores_help: "Aggiorna tutti i punteggi per tutte le classifiche da"
update_range:
last_10_days: "Ultimi 10 giorni"
last_30_days: "Ultimi 30 giorni"
last_90_days: "Ultimi 90 giorni"
last_year: "Ultimo anno"
all_time: "Di Sempre"
custom_date_range: "Intervallo personalizzato"
custom_range_from: "Da"
daily_update_scores_availability:
one: "%{count} ricalcolo giornaliero rimasto"
other: "%{count} ricalcoli giornalieri rimasti"
admin:
title: "Gamification"
name: "Nome"
period: "Periodo"
@@ -0,0 +1,82 @@
# 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:
site_settings:
categories:
discourse_gamification: "Discourse ゲーミフィケーション"
js:
gamification_score: "拍手"
gamification:
score: "拍手"
you: "あなた"
leaderboard:
title: "リーダーボード"
info: "仕組みは?"
link_to_settings: "設定"
refresh: "更新"
modal:
title: "リーダーボードの仕組みは?"
text: "アクセス、「いいね!」、投稿など、コミュニティーでのアクションに対してポイントが付与されます。スコアは数分ごとに更新されます。活発な協力を通じて、ランクを上げましょう!"
name: "名前"
name_placeholder: "名前..."
new: "新しいリーダーボード"
create_success: "リーダーボードが作成されました"
delete_success: "リーダーボードが削除されました"
save_success: "リーダーボードが保存されました"
cta: "最初のリーダーボードを作成しよう"
none: "リーダーボードはまだ作成されていません。"
confirm_destroy: "このリーダーボードを削除してもよろしいですか?"
date:
range: "開始日/終了日の範囲"
from: "開始日"
to: "終了日"
helper: "日付が空である場合、リーダーボードには期間制限なしで獲得されたスコアが表示されます。"
visible_to_groups: "グループに表示"
visible_to_groups_help: "これらのグループのユーザーのみがリーダーボードを表示できます。全員を許可する場合は、空白のままにします。"
included_groups: "含まれるグループ"
included_groups_help: "これらのグループのユーザーのみがリーダーボードに含まれます。全員を表示する場合は、空白のままにします。"
excluded_groups: "除外されるグループ"
excluded_groups_help: "これらのグループのユーザーは、リーダーボードに含まれません。全員を表示する場合は、空白のままにします。"
default_period: "デフォルトの期間"
default_period_help: "このリーダーボードに表示するデフォルトの期間を設定します。"
period_filter_disabled: "期間フィルタを無効化"
period:
all_time: "全期間"
yearly: "年間"
quarterly: "四半期"
monthly: "月間"
weekly: "週間"
daily: "日間"
rank: "ランク"
create: "作成"
cancel: "キャンセル"
close: "閉じる"
delete: "削除"
edit: "編集"
back: "戻る"
save: "保存"
apply: "適用"
recalculate: "スコアを再計算"
recalculating: "スコアを再計算中..."
completed: "完了!スコアが正常に再計算されました。"
update_scores_help: "次の時間からの全リーダーボードのすべてのスコアを更新します:"
update_range:
last_10_days: "過去 10 日間"
last_30_days: "過去 30 日間"
last_90_days: "過去 90 日間"
last_year: "昨年"
all_time: "全期間"
custom_date_range: "カスタム範囲"
custom_range_from: "開始"
daily_update_scores_availability:
other: "1 日の再計算回数は残り %{count} 回"
admin:
title: "ゲーミフィケーション"
name: "名前"
period: "期間"
@@ -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/
ko:
js:
gamification:
you: "사용자님"
leaderboard:
link_to_settings: "설정"
refresh: "새로 고침"
name: "그룹명"
period:
all_time: "전체 시간"
yearly: "연"
quarterly: "분기마다"
monthly: "월"
weekly: "주"
daily: "일"
create: "글"
cancel: "취소"
close: "닫기"
delete: "삭제하기"
edit: "편집"
back: "뒤로"
save: "저장하기"
apply: "적용"
update_range:
last_10_days: "지난 10일"
last_30_days: "지난 30일"
last_90_days: "지난 90일"
all_time: "전체 시간"
custom_range_from: "보내는사람"
admin:
name: "이름"
@@ -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/
lt:
js:
gamification:
you: "Jūs"
leaderboard:
link_to_settings: "Nustatymai"
refresh: "Atnaujinti"
name: "Vardas"
period:
all_time: "Per visą laiką"
yearly: "Kasmet"
quarterly: "Kas ketvirtį"
monthly: "Kas mėnesį"
weekly: "Kas savaitę"
daily: "Kasdien"
create: "Sukurti"
cancel: "Atšaukti"
close: "Uždaryti"
delete: "Pašalinti"
edit: "Redaguoti"
back: "Atgal"
save: "Išsaugoti"
apply: "Kandidatuoti"
update_range:
last_10_days: "Paskutinės 10 dienų"
last_30_days: "Paskutinės 30 dienų"
last_90_days: "Paskutinės 90 dienų"
all_time: "Per visą laiką"
custom_range_from: "Nuo"
admin:
name: "Vardas"
@@ -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/
lv:
js:
gamification:
you: "Tu"
leaderboard:
link_to_settings: "Iestatījumi"
refresh: "Pārlādēt"
name: "Vārds"
period:
all_time: "Vienmēr"
yearly: "Gada"
quarterly: "Ceturkšņa"
monthly: "Mēneša"
weekly: "Nedēļas"
daily: "Dienas"
create: "Izveidot"
cancel: "Atcelt"
close: "Aizvērt"
delete: "Dzēst"
edit: "Rediģēt"
back: "Atpakaļ"
save: "Saglabāt"
apply: "Pielietot"
update_range:
last_10_days: "Pēdējās 10 dienas"
last_30_days: "Pēdējās 30 dienas"
last_90_days: "Pēdējās 90 dienas"
all_time: "Vienmēr"
custom_range_from: "No"
admin:
name: "Vārds"
@@ -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/
nb_NO:
js:
gamification:
you: "Du"
leaderboard:
link_to_settings: "Instillinger"
refresh: "Last inn siden på nytt"
name: "Navn"
period:
all_time: "Totalt"
yearly: "Årlig"
quarterly: "Kvartalsvis"
monthly: "Månedlig"
weekly: "Ukentlig"
daily: "Daglig"
create: "Opprett"
cancel: "Avbryt"
close: "Lukk"
delete: "Slett"
edit: "Endre"
back: "Forrige"
save: "Lagre"
apply: "Bruk"
update_range:
last_10_days: "Siste 10 dager"
last_30_days: "Siste 30 dager"
last_90_days: "Siste 90 dager"
all_time: "Totalt"
custom_range_from: "Fra"
admin:
name: "Navn"
@@ -0,0 +1,83 @@
# 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:
site_settings:
categories:
discourse_gamification: "Discourse Gamification"
js:
gamification_score: "Aanmoedigingen"
gamification:
score: "Aanmoedigingen"
you: "Jij"
leaderboard:
title: "Klassementen"
info: "Hoe werkt dit?"
link_to_settings: "Instellingen"
refresh: "Vernieuwen"
modal:
title: "Hoe werkt het klassement?"
text: "Punten worden toegekend voor interactie met de community, zoals bezoeken, likes en berichten. Je score wordt om de paar minuten bijgewerkt. Dus wees behulpzaam, actief en ondersteunend en stijg in het klassement!"
name: "Naam"
name_placeholder: "Naam..."
new: "Nieuw klassement"
create_success: "Klassement gemaakt"
delete_success: "Klassement verwijderd"
save_success: "Klassement opgeslagen"
cta: "Maak je eerste klassement"
none: "Nog geen klassementen gemaakt."
confirm_destroy: "Weet je zeker dat je dit klassement wilt verwijderen?"
date:
range: "Datumbereik van/tot"
from: "Vanaf datum"
to: "Tot datum"
helper: "Als de datums leeg worden gelaten, toont het klassement behaalde scores zonder enige tijdsbeperking."
visible_to_groups: "Zichtbaar voor groepen"
visible_to_groups_help: "Alleen gebruikers van die groepen kunnen het klassement bekijken. Laat dit leeg om iedereen toe te staan."
included_groups: "Opgenomen groepen"
included_groups_help: "Alleen gebruikers van die groepen wordenopgenomen in het klassement. Laat dit leeg om iedereen toe te staan."
excluded_groups: "Uitgesloten groepen"
excluded_groups_help: "Sluit gebruikers van die groepen uit van het klassement. Laat dit leeg om iedereen toe te staan."
default_period: "Standaard periode"
default_period_help: "Stel de standaard periode in die voor dit klassement moet worden weergegeven."
period_filter_disabled: "Tijdsperiodefilter uitschakelen"
period:
all_time: "Sinds het begin"
yearly: "Jaarlijks"
quarterly: "Driemaandelijks"
monthly: "Maandelijks"
weekly: "Wekelijks"
daily: "Dagelijks"
rank: "Rang"
create: "Maken"
cancel: "Annuleren"
close: "Sluiten"
delete: "Verwijderen"
edit: "Bewerken"
back: "Terug"
save: "Opslaan"
apply: "Toepassen"
recalculate: "Scores herberekenen"
recalculating: "Scores herberekenen..."
completed: "Gereed! De scores zijn herberekend."
update_scores_help: "Werk alle scores bij voor alle klassementen van"
update_range:
last_10_days: "Afgelopen 10 dagen"
last_30_days: "Afgelopen 30 dagen"
last_90_days: "Afgelopen 90 dagen"
last_year: "Afgelopen jaar"
all_time: "Sinds het begin"
custom_date_range: "Aangepast bereik"
custom_range_from: "Van"
daily_update_scores_availability:
one: "%{count} dagelijkse herberekening resterend"
other: "%{count} dagelijkse herberekeningen resterend"
admin:
title: "Gamificatie"
name: "Naam"
period: "Periode"
@@ -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/
pl_PL:
js:
gamification:
you: "Ty"
leaderboard:
link_to_settings: "Ustawienia"
refresh: "Odśwież"
name: "Nazwa"
period:
all_time: "Przez cały czas"
yearly: "Rocznie"
quarterly: "Kwartalnie"
monthly: "Miesięcznie"
weekly: "Tygodniowo"
daily: "Dziennie"
create: "Utwórz"
cancel: "Anuluj"
close: "Zamknij"
delete: "Usuń"
edit: "Edytuj"
back: "Poprzednia"
save: "Zapisz"
apply: "Zastosuj"
update_range:
last_10_days: "Ostatnie 10 dni"
last_30_days: "Ostatnie 30 dni"
last_90_days: "Ostatnie 90 dni"
all_time: "Przez cały czas"
custom_range_from: "Od"
admin:
name: "Imię"
@@ -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/
pt:
js:
gamification:
you: "Você"
leaderboard:
link_to_settings: "Configurações"
refresh: "Atualizar"
name: "Nome"
period:
all_time: "Desde Sempre"
yearly: "Anual"
quarterly: "Trimestral"
monthly: "Mensal"
weekly: "Semanal"
daily: "Diário"
create: "Criar"
cancel: "Cancelar"
close: "Fechar"
delete: "Eliminar"
edit: "Editar"
back: "Retroceder"
save: "Guardar"
apply: "Aplicar"
update_range:
last_10_days: "Últimos 10 Dias"
last_30_days: "Últimos 30 Dias"
last_90_days: "Últimos 90 Dias"
all_time: "Desde Sempre"
custom_range_from: "De"
admin:
name: "Nome"
@@ -0,0 +1,83 @@
# 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:
site_settings:
categories:
discourse_gamification: "Discourse Gamification"
js:
gamification_score: "Saudações"
gamification:
score: "Saudações"
you: "Você"
leaderboard:
title: "Tabelas de classificação"
info: "Como isso funciona?"
link_to_settings: "Definições"
refresh: "Atualizar"
modal:
title: "Como funciona a tabela de classificação?"
text: "Os pontos são concedidos por se envolver com a comunidade, como visitar, curtir e postar. Sua pontuação é atualizada dentro de poucos minutos. Então, seja útil, ativo(a) e solidário(a), e suba na hierarquia!"
name: "Nome"
name_placeholder: "Nome..."
new: "Nova tabela de classificação"
create_success: "Tabela de classificação criada"
delete_success: "Tabela de classificação excluída"
save_success: "Tabela de classificação salva"
cta: "Faça sua primeira tabela de classificação"
none: "Nenhuma tabela de classificação foi criada ainda."
confirm_destroy: "Tem certeza de que deseja excluir essa tabela de classificação?"
date:
range: "Intervalo de datas De / Até"
from: "Data de início"
to: "Data final"
helper: "Se as datas forem deixadas em branco, a tabela de classificação mostrará as pontuações obtidas sem restrições de tempo."
visible_to_groups: "Visível para grupos"
visible_to_groups_help: "Apenas os usuários desses grupos poderão visualizar a tabela de classificação. Deixe vazio para permitir que todos visualizem."
included_groups: "Grupos incluídos"
included_groups_help: "Apenas os usuários desses grupos serão incluídos na tabela de classificação. Deixe vazio para listar todos."
excluded_groups: "Grupos excluídos"
excluded_groups_help: "Impeça que os usuários desses grupos sejam incluídos na tabela de classificação. Deixe em branco para listar todos."
default_period: "Período padrão"
default_period_help: "Defina o período de tempo padrão a ser exibido para este placar."
period_filter_disabled: "Desativar filtro de período"
period:
all_time: "Desde o início"
yearly: "Todo ano"
quarterly: "Todo semestre"
monthly: "Todo mês"
weekly: "A cada semana"
daily: "A cada dia"
rank: "Classificação"
create: "Criar"
cancel: "Cancelar"
close: "Fechar"
delete: "Excluir"
edit: "Editar"
back: "Voltar"
save: "Salvar"
apply: "Aplicar"
recalculate: "Recalcular pontuação"
recalculating: "Recalculando pontuação..."
completed: "Pronto! A pontuação foi recalculada com êxito."
update_scores_help: "Atualizar a pontuação de todos os placares"
update_range:
last_10_days: "Últimos 10 dias"
last_30_days: "Últimos 30 dias"
last_90_days: "Últimos 90 dias"
last_year: "Ano passado"
all_time: "Desde o início"
custom_date_range: "Intervalo personalizado"
custom_range_from: "De"
daily_update_scores_availability:
one: "%{count} recálculo diário restante"
other: "%{count} recálculo(s) diário(s) restante(s)"
admin:
title: "Gamificação"
name: "Nome"
period: "Período"
@@ -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/
ro:
js:
gamification:
you: "Tu"
leaderboard:
link_to_settings: "Opțiuni"
refresh: "Reîmprospătează"
name: "Nume"
period:
all_time: "Dintotdeauna"
yearly: "Anual"
quarterly: "Trimestrial"
monthly: "Lunar"
weekly: "Săptămânal"
daily: "Zilnic"
create: "Creează"
cancel: "Anulare"
close: "Închide sondajul"
delete: "Șterge"
edit: "Modifică"
back: "Înapoi"
save: "Salvare"
apply: "Aplică"
update_range:
last_10_days: "Ultimele 10 de zile"
last_30_days: "Ultimele 30 de zile"
last_90_days: "Ultimele 90 de zile"
all_time: "Dintotdeauna"
custom_range_from: "De la"
admin:
name: "Nume"
@@ -0,0 +1,85 @@
# 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:
site_settings:
categories:
discourse_gamification: "Плагин Discourse «Геймификация»"
js:
gamification_score: "Репутация"
gamification:
score: "Репутация"
you: "Вы"
leaderboard:
title: "Таблица лидеров"
info: "Как это работает?"
link_to_settings: "Настройки"
refresh: "Обновить"
modal:
title: "Как устроена таблица лидеров?"
text: "Баллы начисляются за участие в сообществе, например за посещение, за выраженные симпатии, за публикацию сообщений. Баллы обновляются каждые несколько минут. Чем более вы активны на форуме, помогая другим участникам сообщества, тем больше баллов вам будет начислено!"
name: "Имя"
name_placeholder: "Название…"
new: "Новая таблица лидеров"
create_success: "Таблица лидеров создана"
delete_success: "Таблица лидеров удалена."
save_success: "Таблица лидеров сохранена."
cta: "Создать первую таблицу лидеров"
none: "Таблицы лидеров ещё не созданы."
confirm_destroy: "Вы действительно хотите удалить эту таблицу лидеров?"
date:
range: "Диапазон дат (с/по)"
from: "С даты"
to: "По дату"
helper: "Если даты не указаны, в таблице лидеров будут отображаться баллы без каких-либо ограничений по времени."
visible_to_groups: "Видна группам"
visible_to_groups_help: "Только пользователи из этих групп смогут просматривать таблицу лидеров. Оставьте поле пустым, чтобы разрешить доступ всем участникам."
included_groups: "Включённые группы"
included_groups_help: "Только пользователи из этих групп будут включены в таблицу лидеров. Оставьте поле пустым, чтобы отображать в таблице всех участников."
excluded_groups: "Исключённые группы"
excluded_groups_help: "Пользователи из этих групп не будут включены в таблицу лидеров. Оставьте поле пустым, чтобы отображать в таблице всех участников."
default_period: "Период по умолчанию"
default_period_help: "Установите период времени по умолчанию для отображения таблицы лидеров."
period_filter_disabled: "Отключить фильтр по периоду времени"
period:
all_time: "За всё время"
yearly: "За год"
quarterly: "За квартал"
monthly: "За месяц"
weekly: "За неделю"
daily: "За день"
rank: "Рейтинг"
create: "Создать"
cancel: "Отмена"
close: "Закрыть"
delete: "Удалить"
edit: "Редактировать"
back: "Назад"
save: "Сохранить"
apply: "Применить"
recalculate: "Пересчитать баллы"
recalculating: "Пересчет баллов..."
completed: "Готово! Баллы пересчитаны."
update_scores_help: "Обновить все баллы для всех таблиц лидеров:"
update_range:
last_10_days: "За последние 10 дней"
last_30_days: "За последние 30 дней"
last_90_days: "За последние 90 дней"
last_year: "За последний год"
all_time: "За всё время"
custom_date_range: "Настраиваемый диапазон"
custom_range_from: "От"
daily_update_scores_availability:
one: "Остался %{count} ежедневный пересчет"
few: "Осталось %{count} ежедневных пересчета"
many: "Осталось %{count} ежедневных пересчетов"
other: "Осталось %{count} ежедневного пересчета"
admin:
title: "Геймификация"
name: "Название"
period: "Период"
@@ -0,0 +1,38 @@
# 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:
gamification:
you: "Vy"
leaderboard:
link_to_settings: "Nastavenia"
refresh: "Obnoviť"
name: "Meno"
name_placeholder: "Názov..."
period:
all_time: "Za celú dobu"
yearly: "Ročne"
quarterly: "Štvrťročne"
monthly: "Mesačne"
weekly: "Týždenne"
daily: "Denne"
create: "Vytvoriť"
cancel: "Zrušiť"
close: "Zavrieť"
delete: "Odstrániť"
edit: "Upraviť"
back: "Späť"
save: "Uložiť"
apply: "Použi"
update_range:
last_10_days: "Posledných 10 dní"
last_30_days: "Posledných 30 dní"
last_90_days: "Posledných 90 dní"
all_time: "Za celú dobu"
custom_range_from: "Od"
admin:
name: "Meno"
@@ -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/
sl:
js:
gamification:
you: "Vi"
leaderboard:
link_to_settings: "Nastavitve"
refresh: "Osveži"
name: "Ime"
period:
all_time: "Ves čas"
yearly: "V letu"
quarterly: "V četrtletju"
monthly: "Mesečno"
weekly: "Tedensko"
daily: "Dnevno"
create: "Ustvari"
cancel: "Prekliči"
close: "Zapri"
delete: "Izbriši"
edit: "Uredi"
back: "Nazaj"
save: "Shrani"
apply: "Uporabi"
update_range:
last_10_days: "Zadnjih 10 dni"
last_30_days: "Zadnjih 30 dni"
last_90_days: "Zadnjih 90 dni"
all_time: "Ves čas"
custom_range_from: "Od"
admin:
name: "Polno ime"
@@ -0,0 +1,33 @@
# 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:
gamification:
you: "Ju"
leaderboard:
link_to_settings: "Rregullimet"
refresh: "Rifresko"
name: "Emri"
period:
all_time: "Gjithë Kohës"
yearly: "Vjetore"
quarterly: "Tremujorsh"
monthly: "Mujore"
weekly: "Javore"
daily: "Ditore"
cancel: "Anulo"
close: "Mbyll"
delete: "Fshij"
edit: "Redakto"
back: "Kthehu mbrapa"
save: "Ruaj"
apply: "Apliko"
update_range:
all_time: "Gjithë Kohës"
custom_range_from: "Nga"
admin:
name: "Emri"
@@ -0,0 +1,35 @@
# 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:
gamification:
you: "Ti"
leaderboard:
link_to_settings: "Podešavanja"
refresh: "Osveži"
name: "Ime foruma"
period:
all_time: "Oduvek"
yearly: "Top godišnje"
quarterly: "Top kvartalne"
monthly: "Top mesečne"
weekly: "Top nedeljne"
daily: "Top dnevne"
cancel: "Odustani"
close: "Zatvori"
delete: "Obriši"
edit: "Izmeni"
back: "Nazad"
save: "Sačuvaj"
apply: "Primeni"
update_range:
last_10_days: "Последњих 10 дана"
last_30_days: "Последњих 30 дана"
last_90_days: "Последњих 90 дана"
all_time: "Oduvek"
admin:
name: "Ime foruma"
@@ -0,0 +1,59 @@
# 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:
js:
gamification_score: "Hurra"
gamification:
score: "Hurra"
you: "Du"
leaderboard:
title: "Topplistor"
info: "Hur fungerar detta?"
link_to_settings: "Inställningar"
refresh: "Uppdatera"
modal:
title: "Hur fungerar topplistan?"
text: "Poäng delas ut för att du engagerar dig i gemenskapen, till exempel genom att besöka, gilla och publicera. Dina poäng uppdateras med några minuters mellanrum. Var hjälpsam, aktiv och stödjande och stig sedan i graderna!"
name: "Namn"
name_placeholder: "Namn..."
cta: "Skapa din första topplista"
none: "Inga topplistor har skapats ännu."
confirm_destroy: "Är du säker på att du vill ta bort denna topplista?"
date:
range: "Från / Till datumintervall"
helper: "Om datum lämnas tomma kommer topplistan att visa intjänade poäng utan tidsbegränsningar."
visible_to_groups: "Synlig för grupper"
visible_to_groups_help: "Endast användare i dessa grupper kommer att kunna se topplistan. Lämna tomt för att tillåta alla."
included_groups: "Inkluderade grupper"
included_groups_help: "Endast användare i dessa grupper kommer att inkluderas i topplistan. Lämna tomt för att lista alla."
excluded_groups: "Exkluderade grupper"
excluded_groups_help: "Ta bort användare i dessa grupper från att inkluderas i topplistan. Lämna tomt för att lista alla."
period:
all_time: "Alltid"
yearly: "Årsvis"
quarterly: "Kvartalsvis"
monthly: "Månadsvis"
weekly: "Veckovis"
daily: "Dagligen"
create: "Skapa"
cancel: "Avbryt"
close: "Stäng"
delete: "Radera"
edit: "Redigera"
back: "Tillbaka"
save: "Spara"
apply: "Tillämpa"
update_range:
last_10_days: "Senaste 10 dagarna"
last_30_days: "Senaste 30 dagarna"
last_90_days: "Senaste 90 dagarna"
all_time: "Alltid"
custom_range_from: "Från"
admin:
title: "Gamification"
name: "Namn"
period: "Period"
@@ -0,0 +1,34 @@
# 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:
gamification:
you: "Wewe"
leaderboard:
link_to_settings: "Mipangilio"
refresh: "Rudisha Tena"
name: "Jina"
period:
all_time: "Wakati wote"
yearly: "Kila Mwaka"
quarterly: "Kila baada ya miezi mitatu"
monthly: "Klla mwezi"
weekly: "Kila wiki"
daily: "Kila siku"
create: "Tengeneza"
cancel: "Ghairi"
close: "Funga"
delete: "Futa"
edit: "Hariri"
back: "Iliyopita"
save: "Hifadhi"
apply: "Tumia"
update_range:
all_time: "Wakati wote"
custom_range_from: "Kutoka"
admin:
name: "Jina"
@@ -0,0 +1,34 @@
# 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:
gamification:
you: "మీరు"
leaderboard:
link_to_settings: "అమరికలు"
refresh: "తాజాపరుచు"
name: "పేరు"
period:
all_time: "ఆల్ టైమ్"
yearly: "వార్షిక"
quarterly: "త్రైమాసిక"
monthly: "నెలవారీ"
weekly: "వారానికోసారి"
daily: "రోజువారీ"
create: "సృష్టించండి"
cancel: "రద్దుచేయి"
close: "మూసివేయి"
delete: "తొలగించు"
edit: "సవరణ"
back: "వెనుకకు"
save: "భద్రపరుచు"
apply: "ఆపాదించు"
update_range:
all_time: "ఆల్ టైమ్"
custom_range_from: "నుండి"
admin:
name: "పేరు"
@@ -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/
th:
js:
gamification:
you: "คุณ"
leaderboard:
link_to_settings: "การตั้งค่า"
refresh: "รีเฟรช"
name: "ชื่อ"
period:
all_time: "ตลอดเวลา"
yearly: "รายปี"
quarterly: "รายไตรมาส"
monthly: "รายเดือน"
weekly: "รายสัปดาห์"
daily: "รายวัน"
create: "สร้าง"
cancel: "ยกเลิก"
close: "ปิด"
delete: "ลบ"
edit: "แก้ไข"
back: "กลับ"
save: "บันทึก"
apply: "นำไปใช้"
update_range:
last_10_days: "10 วันที่ผ่านมา"
last_30_days: "30 วันที่ผ่านมา"
last_90_days: "90 วันที่ผ่านมา"
all_time: "ตลอดเวลา"
custom_range_from: "จาก"
admin:
name: "ชื่อ"
@@ -0,0 +1,83 @@
# 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:
site_settings:
categories:
discourse_gamification: "Discourse Oyunlaştırma"
js:
gamification_score: "Tezahürat"
gamification:
score: "Tezahürat"
you: "Siz"
leaderboard:
title: "Liderlik Tabloları"
info: "Bu nasıl işler?"
link_to_settings: "Ayarlar"
refresh: "Yenile"
modal:
title: "Liderlik tablosu nasıl işler?"
text: "Ziyaret etme, beğenme ve gönderme gibi toplulukla etkileşim için puan verilir. Puanınız birkaç dakikada bir güncellenir. Bu yüzden yardımsever, aktif ve destekleyici olun ve sıralamada yükselin!"
name: "Ad"
name_placeholder: "Ad..."
new: "Yeni liderlik tablosu"
create_success: "Liderlik tablosu oluşturuldu"
delete_success: "Liderlik tablosu silindi"
save_success: "Liderlik tablosu kaydedildi"
cta: "İlk liderlik tablonuzu oluşturun"
none: "Henüz liderlik tablosu oluşturulmadı."
confirm_destroy: "Bu liderlik tablosunu silmek istediğinizden emin misiniz?"
date:
range: "Başlangıç / Bitiş Tarih Aralığı"
from: "Başlangıç tarihi"
to: "Bitiş tarihi"
helper: "Tarihler boş bırakılırsa liderlik tablosu herhangi bir zaman kısıtlaması olmaksızın kazanılan puanları gösterir."
visible_to_groups: "Gruplara görünür"
visible_to_groups_help: "Sadece bu gruplardaki kullanıcılar liderlik tablosunu görüntüleyebilir. Herkese izin vermek için boş bırakın."
included_groups: "Dahil edilen gruplar"
included_groups_help: "Yalnızca bu gruplardaki kullanıcılar liderlik tablosuna dahil edilir. Herkesi listelemek için boş bırakın."
excluded_groups: "Hariç tutulan gruplar"
excluded_groups_help: "Bu gruplardaki kullanıcıların liderlik tablosuna dahil edilmesini kaldırın. Herkesi listelemek için boş bırakın."
default_period: "Varsayılan dönem"
default_period_help: "Bu liderlik tablosu için gösterilecek varsayılan zaman aralığını ayarlayın."
period_filter_disabled: "Zaman aralığı filtresini devre dışı bırak"
period:
all_time: "Tüm Zamanlar"
yearly: "Yıllık"
quarterly: "Üç aylık"
monthly: "Aylık"
weekly: "Haftalık"
daily: "Günlük"
rank: "Sıra"
create: "Oluştur"
cancel: "İptal et"
close: "Kapat"
delete: "Sil"
edit: "Düzenle"
back: "Geri"
save: "Kaydet"
apply: "Uygula"
recalculate: "Puanları yeniden hesapla"
recalculating: "Puanlar yeniden hesaplanıyor..."
completed: "Bitti! Puanlar başarıyla yeniden hesaplandı."
update_scores_help: "Şuradan tüm skor tabloları için tüm puanları güncelleyin:"
update_range:
last_10_days: "Son 10 gün"
last_30_days: "Son 30 gün"
last_90_days: "Son 90 gün"
last_year: "Geçen yıl"
all_time: "Tüm Zamanlar"
custom_date_range: "Özel aralık"
custom_range_from: "Başlangıç:"
daily_update_scores_availability:
one: "%{count} günlük yeniden hesaplama kaldı"
other: "%{count} günlük yeniden hesaplama kaldı"
admin:
title: "Oyunlaştırma"
name: "Ad"
period: "Dönem"
@@ -0,0 +1,34 @@
# 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:
gamification:
leaderboard:
link_to_settings: "تەڭشەكلەر"
refresh: "يېڭىلا"
name: "ئىسمى"
name_placeholder: "ئىسمى…"
period:
all_time: "ھەممە ۋاقىت"
yearly: "يىللىق"
quarterly: "پەسىللىك"
monthly: "ئايلىق"
weekly: "ھەپتىلىك"
daily: "كۈندىلىك"
create: "قۇر"
cancel: "ۋاز كەچ"
close: "تاقا"
delete: "ئۆچۈر"
edit: "تەھرىر"
back: "كەينى"
save: "ساقلا"
apply: "قوللان"
update_range:
all_time: "ھەممە ۋاقىت"
custom_range_from: "كىمدىن"
admin:
name: "ئىسمى"
@@ -0,0 +1,38 @@
# 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:
js:
gamification:
you: "Ви"
leaderboard:
link_to_settings: "Налаштування"
refresh: "Оновити"
name: "Назва"
name_placeholder: "Назва..."
period:
all_time: "Весь час"
yearly: "Щорічно"
quarterly: "Щоквартала"
monthly: "Щомісяця"
weekly: "Щотижня"
daily: "За день"
create: "Створити"
cancel: "Скасувати"
close: "Закрити"
delete: "Видалити"
edit: "Редагувати"
back: "Назад"
save: "Зберегти"
apply: "Застосувати"
update_range:
last_10_days: "Останні 10 днів"
last_30_days: "Останні 30 днів"
last_90_days: "Останні 90 днів"
all_time: "Весь час"
custom_range_from: "Від"
admin:
name: "Назва"
@@ -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/
ur:
js:
gamification:
you: "آپ "
leaderboard:
link_to_settings: "ترتیبات"
refresh: "رِیفریش"
name: "نام"
period:
all_time: "تمام اوقات"
yearly: "سالانہ"
quarterly: "سہ ماہی"
monthly: "ماہانہ"
weekly: "ہفتہ وار"
daily: "روزانہ"
create: "بنائیں"
cancel: "منسوخ"
close: "بند کریں"
delete: "مٹائیں"
edit: "ترمیم کریں"
back: "واپس"
save: "محفوظ کریں"
apply: "لاگو کریں"
update_range:
last_10_days: "پچھلے 10 دن"
last_30_days: "پچھلے 30 دن"
last_90_days: "پچھلے 90 دن"
all_time: "تمام اوقات"
custom_range_from: "سے"
admin:
name: "نام"
@@ -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/
vi:
js:
gamification:
you: "Bạn"
leaderboard:
link_to_settings: "Cài đặt"
refresh: "Làm mới"
name: "Tên"
period:
all_time: "Từ trước tới nay"
yearly: "Hàng năm"
quarterly: "Hàng quý"
monthly: "Hàng tháng"
weekly: "Hàng tuần"
daily: "hằng ngày"
create: "Tạo"
cancel: "Huỷ"
close: "Đóng"
delete: "Xóa"
edit: "Sửa"
back: "Quay lại"
save: "Lưu lại"
apply: "Áp dụng"
update_range:
last_10_days: "10 ngày qua"
last_30_days: "30 ngày qua"
last_90_days: "90 ngày qua"
all_time: "Từ trước tới nay"
custom_range_from: "Từ"
admin:
name: "Tên"
@@ -0,0 +1,82 @@
# 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:
site_settings:
categories:
discourse_gamification: "Discourse Gamification"
js:
gamification_score: "点数"
gamification:
score: "点数"
you: "您"
leaderboard:
title: "排行榜"
info: "排行榜是如何运作的?"
link_to_settings: "设置"
refresh: "刷新"
modal:
title: "排行榜是如何运作的?"
text: "参与社区活动,如访问、点赞和发帖,都会获得积分。您的积分每几分钟就会更新一次。保持活跃,积极帮助并支持其他人来提高自己的排名!"
name: "名称"
name_placeholder: "名称…"
new: "新排行榜"
create_success: "排行榜已创建"
delete_success: "排行榜已删除"
save_success: "排行榜已保存"
cta: "制作您的第一个排行榜"
none: "尚未创建排行榜。"
confirm_destroy: "确定要删除此排行榜吗?"
date:
range: "开始/结束日期范围"
from: "起始日期"
to: "截止日期"
helper: "如果日期留空,排行榜将显示全部时间内获得的分数。"
visible_to_groups: "对以下群组可见"
visible_to_groups_help: "只有这些群组中的用户才能查看排行榜。留空以允许所有用户查看。"
included_groups: "包含的群组"
included_groups_help: "只有这些群组中的用户才会被包含到排行榜中。留空以让所有人都参与排行榜。"
excluded_groups: "排除的群组"
excluded_groups_help: "将这些群组中的用户从排行榜中移除。留空以让所有人都参与排行榜。"
default_period: "默认时间段"
default_period_help: "设置要为此排行榜显示的默认时间段。"
period_filter_disabled: "禁用时间段筛选器"
period:
all_time: "所有时间"
yearly: "每年"
quarterly: "每季度"
monthly: "每月"
weekly: "每周"
daily: "每天"
rank: "排名"
create: "创建"
cancel: "取消"
close: "关闭"
delete: "删除"
edit: "编辑"
back: "返回"
save: "保存"
apply: "应用"
recalculate: "重新计算分数"
recalculating: "正在重新计算分数…"
completed: "完成!分数已成功重新计算。"
update_scores_help: "更新以下时间范围内所有排行榜的所有分数:"
update_range:
last_10_days: "过去 10 天"
last_30_days: "过去 30 天"
last_90_days: "过去 90 天"
last_year: "去年"
all_time: "所有时间"
custom_date_range: "自定义范围"
custom_range_from: "从"
daily_update_scores_availability:
other: "剩余 %{count} 次每日重新计算"
admin:
title: "游戏化"
name: "名称"
period: "时间段"
@@ -0,0 +1,38 @@
# 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:
gamification:
you: "你"
leaderboard:
link_to_settings: "設定"
refresh: "重新整理"
name: "名字"
name_placeholder: "名字..."
period:
all_time: "所以時間"
yearly: "年"
quarterly: "季度"
monthly: "月"
weekly: "周"
daily: "日"
create: "創建"
cancel: "取消"
close: "關閉"
delete: "刪除"
edit: "編輯"
back: "上一步"
save: "保存"
apply: "套用"
update_range:
last_10_days: "過去 10 天"
last_30_days: "過去 30 天"
last_90_days: "過去 90 天"
all_time: "所有時間"
custom_range_from: "來自"
admin:
name: "名字"

Some files were not shown because too many files have changed in this diff Show More