Version bump

This commit is contained in:
Neil Lalonde 2018-09-10 19:43:35 -04:00
commit 187505d0ba
182 changed files with 2854 additions and 1229 deletions

View File

@ -102,6 +102,9 @@ Layout/EndAlignment:
Lint/RequireParentheses:
Enabled: true
Lint/ShadowingOuterLocalVariable:
Enabled: true
Layout/MultilineMethodCallIndentation:
Enabled: true
EnforcedStyle: indented

View File

@ -34,7 +34,7 @@ gem 'redis-namespace'
gem 'active_model_serializers', '~> 0.8.3'
gem 'onebox', '1.8.58'
gem 'onebox', '1.8.60'
gem 'http_accept_language', '~>2.0.5', require: false
@ -193,3 +193,4 @@ if ENV["IMPORT"] == "1"
end
gem 'webpush', require: false
gem 'colored2', require: false

View File

@ -257,7 +257,7 @@ GEM
omniauth-twitter (1.4.0)
omniauth-oauth (~> 1.1)
rack
onebox (1.8.58)
onebox (1.8.60)
htmlentities (~> 4.3)
moneta (~> 1.0)
multi_json (~> 1.11)
@ -458,6 +458,7 @@ DEPENDENCIES
bullet
byebug
certified
colored2
cppjieba_rb
danger
discourse_image_optim
@ -509,7 +510,7 @@ DEPENDENCIES
omniauth-oauth2
omniauth-openid
omniauth-twitter
onebox (= 1.8.58)
onebox (= 1.8.60)
openid-redis-store
pg
pry-nav

View File

@ -23,11 +23,6 @@ export default Ember.Controller.extend(PeriodComputationMixin, {
),
shouldDisplayDurability: Ember.computed.and("diskSpace"),
@computed
topReferredTopicsTopions() {
return { table: { total: false, limit: 8 } };
},
@computed
activityMetrics() {
return [
@ -48,9 +43,38 @@ export default Ember.Controller.extend(PeriodComputationMixin, {
};
},
@computed
topReferredTopicsOptions() {
return {
table: { total: false, limit: 8 }
};
},
@computed
topReferredTopicsFilters() {
return {
startDate: moment()
.subtract(6, "days")
.startOf("day"),
endDate: this.get("today")
};
},
@computed
trendingSearchFilters() {
return {
startDate: moment()
.subtract(6, "days")
.startOf("day"),
endDate: this.get("today")
};
},
@computed
trendingSearchOptions() {
return { table: { total: false, limit: 8 } };
return {
table: { total: false, limit: 8 }
};
},
usersByTypeReport: staticReport("users_by_type"),

View File

@ -84,7 +84,7 @@ export default Ember.Mixin.create({
this.$().on("keydown.setting-enter", ".input-setting-string", function(e) {
if (e.keyCode === 13) {
// enter key
self._save();
self.send("save");
}
});
}.on("didInsertElement"),
@ -122,7 +122,7 @@ export default Ember.Mixin.create({
resetDefault() {
this.set("buffered.value", this.get("setting.default"));
this._save();
this.send("save");
},
toggleSecret() {

View File

@ -3,7 +3,7 @@ import Backup from "admin/models/backup";
export default Ember.Route.extend({
activate() {
this.messageBus.subscribe("/admin/backups", backups =>
this.controller.set("model", backups)
this.controller.set("model", backups.map(backup => Backup.create(backup)))
);
},

View File

@ -5,33 +5,31 @@
{{nav-item route='admin.backups.index' label='admin.backups.menu.backups'}}
{{nav-item route='admin.backups.logs' label='admin.backups.menu.logs'}}
{{plugin-outlet name="downloader" tagName=""}}
<div class="backup-operations">
{{#if model.canRollback}}
{{d-button action="rollback"
class="btn-rollback"
label="admin.backups.operations.rollback.label"
title="admin.backups.operations.rollback.title"
icon="ambulance"
disabled=rollbackDisabled}}
{{/if}}
{{#if model.isOperationRunning}}
{{d-button action="cancelOperation"
class="btn-danger"
title="admin.backups.operations.cancel.title"
label="admin.backups.operations.cancel.label"
icon="times"}}
{{else}}
{{d-button action="startBackup"
class="btn-primary"
title="admin.backups.operations.backup.title"
label="admin.backups.operations.backup.label"
icon="rocket"}}
{{/if}}
</div>
<div class="admin-actions">
{{#if model.canRollback}}
{{d-button action="rollback"
class="btn-rollback"
label="admin.backups.operations.rollback.label"
title="admin.backups.operations.rollback.title"
icon="ambulance"
disabled=rollbackDisabled}}
{{/if}}
{{#if model.isOperationRunning}}
{{d-button action="cancelOperation"
class="btn-danger"
title="admin.backups.operations.cancel.title"
label="admin.backups.operations.cancel.label"
icon="times"}}
{{else}}
{{d-button action="startBackup"
class="btn-primary"
title="admin.backups.operations.backup.title"
label="admin.backups.operations.backup.label"
icon="rocket"}}
{{/if}}
</div>
</ul>
</nav>
</div>
<div class="admin-container">

View File

@ -7,7 +7,7 @@
{{#link-to 'adminUser' user.id user.username class="flag-user-username"}}
{{user.username}}
{{/link-to}}
<div class='flag-user-date'>
<div class='flag-user-date' title='{{raw-date date}}'>
{{format-age date}}
</div>
</div>

View File

@ -13,5 +13,5 @@
{{#if setting.secret}}
{{d-button action="toggleSecret" icon="eye-slash"}}
{{/if}}
{{d-button action="resetDefault" icon="undo" label="admin.settings.reset"}}
{{d-button class="undo" action="resetDefault" icon="undo" label="admin.settings.reset"}}
{{/if}}

View File

@ -154,12 +154,14 @@
<div class="section-column">
{{admin-report
filters=topReferredTopicsFilters
dataSourceName="top_referred_topics"
reportOptions=topReferredTopicsTopions}}
reportOptions=topReferredTopicsOptions}}
{{admin-report
dataSourceName="trending_search"
reportOptions=trendingSearchOptions
filters=trendingSearchFilters
isEnabled=logSearchQueriesEnabled
disabledLabel="admin.dashboard.reports.trending_search.disabled"}}
{{{i18n "admin.dashboard.reports.trending_search.more"}}}

View File

@ -1,9 +1,9 @@
<div class='admin-controls'>
<div class='controls'>
{{d-button action="toggleMenu" class="menu-toggle" icon="bars"}}
{{text-field value=filter placeholderKey="type_to_filter" class="no-blur"}}
{{d-button action="clearFilter" label="admin.site_settings.clear_filter"}}
{{text-field id="setting-filter" value=filter placeholderKey="type_to_filter" class="no-blur"}}
{{d-button id="clear-filter" action="clearFilter" label="admin.site_settings.clear_filter"}}
</div>
<div class='search controls'>
<label>

View File

@ -205,6 +205,11 @@ export default Ember.Controller.extend(
"accountChallenge"
);
const userFields = this.get("userFields");
const destinationUrl = this.get("authOptions.destination_url");
if (!Ember.isEmpty(destinationUrl)) {
$.cookie("destination_url", destinationUrl, { path: "/" });
}
// Add the userfields to the data
if (!Ember.isEmpty(userFields)) {
@ -255,10 +260,12 @@ export default Ember.Controller.extend(
this.get("rejectedPasswords").pushObject(attrs.accountPassword);
}
this.set("formSubmitted", false);
$.cookie("destination_url", null);
}
},
() => {
this.set("formSubmitted", false);
$.cookie("destination_url", null);
return this.flash(I18n.t("create_account.failed"), "error");
}
);

View File

@ -6,6 +6,8 @@ import { setting } from "discourse/lib/computed";
import { popupAjaxError } from "discourse/lib/ajax-error";
import showModal from "discourse/lib/show-modal";
import { findAll } from "discourse/models/login-method";
import { ajax } from "discourse/lib/ajax";
import { userPath } from "discourse/lib/url";
export default Ember.Controller.extend(
CanCheckEmails,
@ -85,8 +87,12 @@ export default Ember.Controller.extend(
return userId !== this.get("currentUser.id");
},
@computed()
canUpdateAssociatedAccounts() {
@computed("model.second_factor_enabled")
canUpdateAssociatedAccounts(secondFactorEnabled) {
if (secondFactorEnabled) {
return false;
}
return (
findAll(this.siteSettings, this.capabilities, this.site.isMobileDevice)
.length > 0
@ -194,6 +200,19 @@ export default Ember.Controller.extend(
});
},
toggleToken(token) {
Ember.set(token, "visible", !token.visible);
},
revokeAuthToken() {
ajax(
userPath(
`${this.get("model.username_lower")}/preferences/revoke-auth-token`
),
{ type: "POST" }
);
},
connectAccount(method) {
method.doLogin();
}

View File

@ -12,8 +12,8 @@ export default {
window.location.search.indexOf("?preview_theme_id=") === 0
) {
// force preview theme id to always be carried along
const themeId = window.location.search.slice(19).split("&")[0];
if (themeId.match(/^[a-z0-9-]+$/i)) {
const themeId = parseInt(window.location.search.slice(18).split("&")[0]);
if (!isNaN(themeId)) {
const patchState = function(f) {
const patched = window.history[f];

View File

@ -36,6 +36,9 @@ const bindings = {
"command+up": { handler: "goToFirstPost", anonymous: true },
j: { handler: "selectDown", anonymous: true },
k: { handler: "selectUp", anonymous: true },
// we use this odd routing here vs a postAction: cause like
// has an animation so the widget handles that
// TODO: teach controller how to trigger the widget animation
l: { click: ".topic-post.selected button.toggle-like" },
"m m": { handler: "setTrackingToMuted" }, // mark topic as muted
"m r": { handler: "setTrackingToRegular" }, // mark topic as regular
@ -301,10 +304,6 @@ export default {
$(".topic-post.selected article.boxed").data("post-id"),
10
);
if (!selectedPostId) {
// If no post was selected, automatically select the hovered post.
selectedPostId = parseInt($("article.boxed:hover").data("post-id"), 10);
}
if (selectedPostId) {
const topicController = container.lookup("controller:topic");
const post = topicController

View File

@ -38,7 +38,7 @@ import Sharing from "discourse/lib/sharing";
import { addComposerUploadHandler } from "discourse/components/composer-editor";
// If you add any methods to the API ensure you bump up this number
const PLUGIN_API_VERSION = "0.8.24";
const PLUGIN_API_VERSION = "0.8.25";
class PluginApi {
constructor(version, container) {

View File

@ -153,7 +153,12 @@ const DiscourseURL = Ember.Object.extend({
},
routeToTag(a) {
if (a && a.host !== document.location.host) {
// skip when we are provided nowhere to route to
if (!a || !a.href) {
return false;
}
if (a.host !== document.location.host) {
document.location = a.href;
return false;
}

View File

@ -60,7 +60,11 @@
{{#if model.showCategoryChooser}}
<div class="category-input">
{{category-chooser fullWidthOnMobile=true value=model.categoryId scopedCategoryId=scopedCategoryId tabindex="3"}}
{{category-chooser
fullWidthOnMobile=true
value=model.categoryId
scopedCategoryId=scopedCategoryId
tabindex="3"}}
{{popup-input-tip validation=categoryValidation}}
</div>
{{/if}}

View File

@ -100,7 +100,10 @@ function applyOnebox(state, silent) {
let options = state.md.options.discourse;
if (options.lookupInlineOnebox) {
onebox = options.lookupInlineOnebox(href);
onebox = options.lookupInlineOnebox(
href,
options.invalidateOneboxes
);
}
if (onebox && onebox.title) {

View File

@ -31,7 +31,8 @@ export function buildOptions(state) {
previewing,
linkify,
censoredWords,
mentionLookup
mentionLookup,
invalidateOneboxes
} = state;
let features = {
@ -80,7 +81,8 @@ export function buildOptions(state) {
markdownIt: true,
injectLineNumbersToPreview:
siteSettings.enable_advanced_editor_preview_sync,
previewing
previewing,
invalidateOneboxes
};
// note, this will mutate options due to the way the API is designed

View File

@ -2,7 +2,10 @@ import SelectKitComponent from "select-kit/components/select-kit";
import computed from "ember-addons/ember-computed-decorators";
import { on } from "ember-addons/ember-computed-decorators";
const { get, isNone, isEmpty, makeArray, run } = Ember;
import { applyOnSelectPluginApiCallbacks } from "select-kit/mixins/plugin-api";
import {
applyOnSelectPluginApiCallbacks,
applyOnSelectNonePluginApiCallbacks
} from "select-kit/mixins/plugin-api";
export default SelectKitComponent.extend({
pluginApiIdentifiers: ["multi-select"],
@ -253,6 +256,11 @@ export default SelectKitComponent.extend({
!computedContentItem ||
computedContentItem.__sk_row_type === "noneRow"
) {
applyOnSelectNonePluginApiCallbacks(
this.get("pluginApiIdentifiers"),
this
);
this._boundaryActionHandler("onSelectNone");
this.clearSelection();
return;
}

View File

@ -5,7 +5,10 @@ import {
} from "ember-addons/ember-computed-decorators";
const { get, isNone, isEmpty, isPresent, run, makeArray } = Ember;
import { applyOnSelectPluginApiCallbacks } from "select-kit/mixins/plugin-api";
import {
applyOnSelectPluginApiCallbacks,
applyOnSelectNonePluginApiCallbacks
} from "select-kit/mixins/plugin-api";
export default SelectKitComponent.extend({
pluginApiIdentifiers: ["single-select"],
@ -211,6 +214,11 @@ export default SelectKitComponent.extend({
!computedContentItem ||
computedContentItem.__sk_row_type === "noneRow"
) {
applyOnSelectNonePluginApiCallbacks(
this.get("pluginApiIdentifiers"),
this
);
this._boundaryActionHandler("onSelectNone");
this.clearSelection();
return;
}

View File

@ -47,6 +47,15 @@ function modifyCollectionHeader(pluginApiIdentifiers, contentFunction) {
_modifyCollectionHeaderCallbacks[pluginApiIdentifiers].push(contentFunction);
}
let _onSelectNoneCallbacks = {};
function onSelectNone(pluginApiIdentifiers, mutationFunction) {
if (Ember.isNone(_onSelectNoneCallbacks[pluginApiIdentifiers])) {
_onSelectNoneCallbacks[pluginApiIdentifiers] = [];
}
_onSelectNoneCallbacks[pluginApiIdentifiers].push(mutationFunction);
}
let _onSelectCallbacks = {};
function onSelect(pluginApiIdentifiers, mutationFunction) {
if (Ember.isNone(_onSelectCallbacks[pluginApiIdentifiers])) {
@ -102,6 +111,12 @@ export function applyOnSelectPluginApiCallbacks(identifiers, val, context) {
});
}
export function applyOnSelectNonePluginApiCallbacks(identifiers, context) {
identifiers.forEach(key => {
(_onSelectNoneCallbacks[key] || []).forEach(c => c(context));
});
}
export function modifySelectKit(pluginApiIdentifiers) {
return {
appendContent: content => {
@ -131,6 +146,10 @@ export function modifySelectKit(pluginApiIdentifiers) {
onSelect: callback => {
onSelect(pluginApiIdentifiers, callback);
return modifySelectKit(pluginApiIdentifiers);
},
onSelectNone: callback => {
onSelectNone(pluginApiIdentifiers, callback);
return modifySelectKit(pluginApiIdentifiers);
}
};
}
@ -142,6 +161,7 @@ export function clearCallbacks() {
_modifyHeaderComputedContentCallbacks = {};
_modifyCollectionHeaderCallbacks = {};
_onSelectCallbacks = {};
_onSelectNoneCallbacks = {};
}
const EMPTY_ARRAY = Object.freeze([]);

View File

@ -3,6 +3,7 @@
//= require ./ember-addons/macro-alias
//= require ./ember-addons/ember-computed-decorators
//= require_tree ./discourse-common
//= require i18n-patches
//= require_tree ./select-kit
//= require wizard/router
//= require wizard/wizard

View File

@ -0,0 +1,5 @@
export default Ember.Component.extend({
keyPress(e) {
e.stopPropagation();
}
});

View File

@ -19,14 +19,17 @@
//= require locales/en
//= require fake_xml_http_request
//= require route-recognizer
//= require pretender
//= require pretender/pretender
//= require ./wizard-pretender
// Trick JSHint into allow document.write
var d = document;
d.write('<div id="ember-testing-container"><div id="ember-testing"></div></div>');
d.write('<style>#ember-testing-container { position: absolute; background: white; bottom: 0; right: 0; width: 640px; height: 384px; overflow: auto; z-index: 9999; border: 1px solid #ccc; } #ember-testing { zoom: 50%; }</style>');
d.write(
'<div id="ember-testing-container"><div id="ember-testing"></div></div>'
);
d.write(
"<style>#ember-testing-container { position: absolute; background: white; bottom: 0; right: 0; width: 640px; height: 384px; overflow: auto; z-index: 9999; border: 1px solid #ccc; } #ember-testing { zoom: 50%; }</style>"
);
if (window.Logster) {
Logster.enabled = false;
@ -35,7 +38,12 @@ if (window.Logster) {
}
Ember.Test.adapter = window.QUnitAdapter.create();
var createPretendServer = requirejs('wizard/test/wizard-pretender', null, null, false).default;
var createPretendServer = requirejs(
"wizard/test/wizard-pretender",
null,
null,
false
).default;
var server;
QUnit.testStart(function() {
@ -46,13 +54,12 @@ QUnit.testDone(function() {
server.shutdown();
});
var _testApp = requirejs('wizard/test/helpers/start-app').default();
var _buildResolver = requirejs('discourse-common/resolver').buildResolver;
window.setResolver(_buildResolver('wizard').create({ namespace: _testApp }));
var _testApp = requirejs("wizard/test/helpers/start-app").default();
var _buildResolver = requirejs("discourse-common/resolver").buildResolver;
window.setResolver(_buildResolver("wizard").create({ namespace: _testApp }));
Object.keys(requirejs.entries).forEach(function(entry) {
if ((/\-test/).test(entry)) {
if (/\-test/.test(entry)) {
requirejs(entry, null, null, true);
}
});

View File

@ -498,7 +498,7 @@ $mobile-breakpoint: 700px;
content: "";
position: absolute;
right: 0px;
width: 30px;
width: 15px;
height: calc(100% - 5px);
background: linear-gradient(
to right,

View File

@ -17,18 +17,6 @@ $rollback-darker: darken($rollback, 20%) !default;
}
}
.backups {
.admin-controls {
.backup-operations {
margin-left: auto;
button {
display: flex;
margin-right: 20px;
}
}
}
}
.admin-backups {
table {
@media screen and (min-width: 550px) {

View File

@ -475,6 +475,16 @@ select {
.control-group {
@include clearfix;
.table {
width: 100%;
th,
td {
padding: 10px;
text-align: center;
}
}
}
.control-label {
@ -544,3 +554,51 @@ select {
.inline {
display: inline;
}
.pref-auth-tokens {
.control-label {
display: inline-block;
}
.row {
margin: 5px 0px;
}
.muted {
color: $primary-medium;
}
.perf-auth-token {
background-color: $primary-very-low;
color: $primary;
display: block;
padding: 5px;
margin-bottom: 10px;
}
.auth-token-summary {
padding: 0px 10px;
.auth-token-label,
.auth-token-value {
font-size: 1.2em;
margin-top: 5px;
}
}
.auth-token-details {
background: $secondary;
padding: 5px 10px;
margin: 10px 5px 5px 5px;
.auth-token-label {
color: $primary-medium;
}
}
.auth-token-label,
.auth-token-value {
float: left;
width: 50%;
}
}

View File

@ -247,6 +247,19 @@ aside.onebox {
// instagram fixes
.instagram-images {
clear: both;
position: relative;
.instagram-video-icon {
&:before {
font-family: FontAwesome;
font-size: $font-up-5;
content: "\f144";
}
bottom: 10px;
right: 10px;
position: absolute;
color: white;
}
.instagram-image {
padding: 5px 5px 5px 5px;

View File

@ -15,6 +15,8 @@
}
.names {
flex: 1 1 auto;
overflow: hidden;
span.first {
font-weight: bold;
}
@ -23,7 +25,6 @@
font-size: $font-0;
margin-right: 8px;
display: inline-block;
max-width: 280px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;

View File

@ -219,6 +219,9 @@
.avatar-flair {
bottom: 8px;
right: 2px;
.fa {
font-size: $font-0;
}
}
}
}

View File

@ -88,13 +88,17 @@
}
&:hover,
&.btn-hover {
color: #fff;
background: dark-light-choose($tertiary, $tertiary);
background: dark-light-choose(
scale-color($tertiary, $lightness: -20%),
scale-color($tertiary, $lightness: 20%)
);
}
&:active,
&.btn-active {
@include linear-gradient($tertiary, $tertiary);
color: $secondary;
@include linear-gradient(
scale-color($tertiary, $lightness: -20%),
$tertiary
);
}
&[disabled],
&.disabled {
@ -114,7 +118,10 @@
}
&:hover,
&.btn-hover {
background: scale-color($danger, $lightness: -20%);
background: dark-light-choose(
scale-color($danger, $lightness: -20%),
scale-color($danger, $lightness: 20%)
);
}
&:active,
&.btn-active {

View File

@ -4,6 +4,12 @@
}
}
thead {
.d-icon {
padding-left: 2px;
}
}
$filter-line-height: 1.5;
.groups-header-filters {

View File

@ -870,11 +870,11 @@ span.highlighted {
}
.read-state {
color: $tertiary-medium;
// We use absolute positioning here because we want it to display in the padding
position: absolute;
// We use absolute positioning here because we want it to display in the padding
align-self: center;
color: $tertiary-medium;
right: 0;
top: 2em;
font-size: 0.571em;
}

View File

@ -156,6 +156,14 @@ class ApplicationController < ActionController::Base
end
end
rescue_from ArgumentError do |e|
if e.message == "string contains null byte"
raise Discourse::InvalidParameters, e.message
else
raise e
end
end
rescue_from Discourse::InvalidParameters do |e|
message = I18n.t('invalid_params', message: e.message)
if (request.format && request.format.json?) || request.xhr? || !request.get?
@ -373,7 +381,8 @@ class ApplicationController < ActionController::Base
theme_ids = []
if preview_theme_id = request[:preview_theme_id]&.to_i
theme_ids << preview_theme_id
ids = [preview_theme_id]
theme_ids = ids if guardian.allow_themes?(ids, include_preview: true)
end
user_option = current_user&.user_option
@ -386,10 +395,9 @@ class ApplicationController < ActionController::Base
end
end
theme_ids = user_option&.theme_ids || [] if theme_ids.blank?
unless guardian.allow_themes?(theme_ids)
theme_ids = []
if theme_ids.blank?
ids = user_option&.theme_ids || []
theme_ids = ids if guardian.allow_themes?(ids)
end
if theme_ids.blank? && SiteSetting.default_theme_id != -1

View File

@ -118,10 +118,14 @@ class CategoriesController < ApplicationController
def create
guardian.ensure_can_create!(Category)
position = category_params.delete(:position)
@category = Category.create(category_params.merge(user: current_user))
@category =
begin
Category.new(category_params.merge(user: current_user))
rescue ArgumentError => e
return render json: { errors: [e.message] }, status: 422
end
if @category.save
@category.move_to(position.to_i) if position

View File

@ -219,8 +219,8 @@ class ListController < ApplicationController
discourse_expires_in 1.minute
@title = "#{@category.name} - #{SiteSetting.title}"
@link = "#{Discourse.base_url}#{@category.url}"
@atom_link = "#{Discourse.base_url}#{@category.url}.rss"
@link = "#{Discourse.base_url_no_prefix}#{@category.url}"
@atom_link = "#{Discourse.base_url_no_prefix}#{@category.url}.rss"
@description = "#{I18n.t('topics_in_category', category: @category.name)} #{@category.description}"
@topic_list = TopicQuery.new(current_user).list_new_in_category(@category)
@ -367,7 +367,6 @@ class ListController < ApplicationController
def build_topic_list_options
options = {}
params[:page] = params[:page].to_i rescue 1
params[:tags] = [params[:tag_id].parameterize] if params[:tag_id].present? && guardian.can_tag_pms?
TopicQuery.public_valid_options.each do |key|

View File

@ -173,7 +173,7 @@ class UserAvatarsController < ApplicationController
def render_blank
path = Rails.root + "public/images/avatar.png"
expires_in 10.minutes, public: true
response.headers["Last-Modified"] = 10.minutes.ago.httpdate
response.headers["Last-Modified"] = Time.new('1990-01-01').httpdate
response.headers["Content-Length"] = File.size(path).to_s
send_file path, disposition: nil
end

View File

@ -13,7 +13,8 @@ class UsersController < ApplicationController
:username, :update, :user_preferences_redirect, :upload_user_image,
:pick_avatar, :destroy_user_image, :destroy, :check_emails,
:topic_tracking_state, :preferences, :create_second_factor,
:update_second_factor, :create_second_factor_backup, :select_avatar
:update_second_factor, :create_second_factor_backup, :select_avatar,
:revoke_auth_token
]
skip_before_action :check_xhr, only: [
@ -103,7 +104,7 @@ class UsersController < ApplicationController
attributes.delete(:username)
if params[:user_fields].present?
attributes[:custom_fields] = {}
attributes[:custom_fields] ||= {}
fields = UserField.all
fields = fields.where(editable: true) unless current_user.staff?
@ -116,7 +117,7 @@ class UsersController < ApplicationController
val = val[0...UserField.max_length] if val
return render_json_error(I18n.t("login.missing_user_field")) if val.blank? && f.required?
attributes[:custom_fields]["user_field_#{f.id}"] = val
attributes[:custom_fields]["#{User::USER_FIELD_PREFIX}#{f.id}"] = val
end
end
@ -351,7 +352,7 @@ class UsersController < ApplicationController
if field_val.blank?
return fail_with("login.missing_user_field") if f.required?
else
fields["user_field_#{f.id}"] = field_val[0...UserField.max_length]
fields["#{User::USER_FIELD_PREFIX}#{f.id}"] = field_val[0...UserField.max_length]
end
end
@ -674,6 +675,8 @@ class UsersController < ApplicationController
if current_user.present?
if SiteSetting.enable_sso_provider && payload = cookies.delete(:sso_payload)
return redirect_to(session_sso_provider_url + "?" + payload)
elsif destination_url = cookies.delete(:destination_url)
return redirect_to(destination_url)
else
return redirect_to(path('/'))
end
@ -1097,6 +1100,19 @@ class UsersController < ApplicationController
end
end
def revoke_auth_token
user = fetch_user_from_params
guardian.ensure_can_edit!(user)
UserAuthToken.where(user_id: user.id).each(&:destroy!)
MessageBus.publish "/file-change", ["refresh"], user_ids: [user.id]
render json: {
success: true
}
end
private
def honeypot_value
@ -1153,6 +1169,7 @@ class UsersController < ApplicationController
:card_background
]
permitted << { custom_fields: User.editable_user_custom_fields } unless User.editable_user_custom_fields.blank?
permitted.concat UserUpdater::OPTION_ATTR
permitted.concat UserUpdater::CATEGORY_IDS.keys.map { |k| { k => [] } }
permitted.concat UserUpdater::TAG_NAMES.keys

View File

@ -119,11 +119,6 @@ module Jobs
RailsMultisite::ConnectionManagement.all_dbs
end
logster_env = {}
Logster.add_to_env(logster_env, :current_db, 'default')
Logster.add_to_env(logster_env, :job, self.class.to_s)
Thread.current[Logster::Logger::LOGSTER_ENV] = logster_env
exceptions = []
dbs.each do |db|
begin
@ -134,7 +129,11 @@ module Jobs
I18n.locale = SiteSetting.default_locale || "en"
I18n.ensure_all_loaded!
begin
logster_env = {}
Logster.add_to_env(logster_env, :job, self.class.to_s)
Logster.add_to_env(logster_env, :db, db)
Thread.current[Logster::Logger::LOGSTER_ENV] = logster_env
execute(opts)
rescue => e
exception[:ex] = e
@ -146,7 +145,6 @@ module Jobs
exception[:other] = { problem_db: db }
ensure
total_db_time += Instrumenter.stats.duration_ms
Thread.current[Logster::Logger::LOGSTER_ENV] = nil
end
end

View File

@ -0,0 +1,26 @@
module Jobs
class CorrectMissingDualstackUrls < Jobs::Onceoff
def execute_onceoff(args)
# s3 now uses dualstack urls, keep them around correctly
# in both uploads and optimized_image tables
base_url = Discourse.store.absolute_base_url
return if !base_url.match?(/s3\.dualstack/)
old = base_url.sub('s3.dualstack.', 's3-')
old_like = %"#{old}%"
DB.exec(<<~SQL, from: old, to: base_url, old_like: old_like)
UPDATE uploads
SET url = replace(url, :from, :to)
WHERE url ilike :old_like
SQL
DB.exec(<<~SQL, from: old, to: base_url, old_like: old_like)
UPDATE optimized_images
SET url = replace(url, :from, :to)
WHERE url ilike :old_like
SQL
end
end
end

View File

@ -23,6 +23,8 @@ module Jobs
.where(user_id: @user_id)
.where.not(raw_email: nil)
.update_all(raw_email: nil)
anonymize_user_fields
end
def ip_where(column = 'user_id')
@ -46,5 +48,15 @@ module Jobs
).update_all(ip_address: new_ip)
end
def anonymize_user_fields
user_field_ids = UserField.pluck(:id)
user = User.find(@user_id)
return if user_field_ids.blank? || user.blank?
user_field_ids.each do |field_id|
user.custom_fields.delete("#{User::USER_FIELD_PREFIX}#{field_id}")
end
user.save!
end
end
end

View File

@ -130,7 +130,7 @@ module Jobs
changes = { raw: raw, edit_reason: I18n.t("upload.edit_reason") }
post.revise(Discourse.system_user, changes, bypass_bump: true)
elsif has_downloaded_image || has_new_large_image || has_new_broken_image
post.trigger_post_process(true)
post.trigger_post_process(bypass_bump: true)
end
end

View File

@ -17,6 +17,8 @@ module Jobs
UserOption.ensure_consistency!
Tag.ensure_consistency!
CategoryTagStat.ensure_consistency!
User.ensure_consistency!
UserAvatar.ensure_consistency!
end
end
end

View File

@ -20,10 +20,15 @@ class UserNotifications < ActionMailer::Base
end
def signup_after_approval(user, opts = {})
locale = user_locale(user)
tips = I18n.t('system_messages.usage_tips.text_body_template',
base_url: Discourse.base_url,
locale: locale)
build_email(user.email,
template: 'user_notifications.signup_after_approval',
locale: user_locale(user),
new_user_tips: I18n.t('system_messages.usage_tips.text_body_template', base_url: Discourse.base_url, locale: locale))
locale: locale,
new_user_tips: tips)
end
def notify_old_email(user, opts = {})
@ -320,7 +325,7 @@ class UserNotifications < ActionMailer::Base
protected
def user_locale(user)
(user.locale.present? && I18n.available_locales.include?(user.locale.to_sym)) ? user.locale : nil
user.effective_locale
end
def email_post_markdown(post, add_posted_by = false)

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
class IncomingLink < ActiveRecord::Base
belongs_to :post
belongs_to :user
@ -15,7 +17,8 @@ class IncomingLink < ActiveRecord::Base
current_user = opts[:current_user]
username = opts[:username]
username = nil unless String === username
username = nil if !(String === username)
username = nil if username&.include?("\0")
if username
u = User.select(:id).find_by(username_lower: username.downcase)
user_id = u.id if u

View File

@ -44,7 +44,7 @@ InviteRedeemer = Struct.new(:invite, :username, :name, :password, :user_custom_f
user_fields.each do |f|
field_val = field_params[f.id.to_s]
fields["user_field_#{f.id}"] = field_val[0...UserField.max_length] unless field_val.blank?
fields["#{User::USER_FIELD_PREFIX}#{f.id}"] = field_val[0...UserField.max_length] unless field_val.blank?
end
user.custom_fields = fields
end

View File

@ -536,7 +536,7 @@ class Post < ActiveRecord::Base
QuotedPost.extract_from(self)
# make sure we trigger the post process
trigger_post_process(true)
trigger_post_process(bypass_bump: true)
publish_change_to_clients!(:rebaked)
@ -650,10 +650,10 @@ class Post < ActiveRecord::Base
end
# Enqueue post processing for this post
def trigger_post_process(bypass_bump = false)
def trigger_post_process(bypass_bump: false)
args = {
post_id: id,
bypass_bump: bypass_bump
bypass_bump: bypass_bump,
}
args[:image_sizes] = image_sizes if image_sizes.present?
args[:invalidate_oneboxes] = true if invalidate_oneboxes.present?
@ -784,6 +784,34 @@ class Post < ActiveRecord::Base
locked_by_id.present?
end
def link_post_uploads(fragments: nil)
upload_ids = []
fragments ||= Nokogiri::HTML::fragment(self.cooked)
fragments.css("a/@href", "img/@src").each do |media|
if upload = Upload.get_from_url(media.value)
upload_ids << upload.id
end
end
upload_ids |= Upload.where(id: downloaded_images.values).pluck(:id)
values = upload_ids.map! { |upload_id| "(#{self.id},#{upload_id})" }.join(",")
PostUpload.transaction do
PostUpload.where(post_id: self.id).delete_all
if values.size > 0
DB.exec("INSERT INTO post_uploads (post_id, upload_id) VALUES #{values}")
end
end
end
def downloaded_images
JSON.parse(self.custom_fields[Post::DOWNLOADED_IMAGES].presence || "{}")
rescue JSON::ParserError
{}
end
private
def parse_quote_into_arguments(quote)

View File

@ -242,7 +242,7 @@ class Report
report_about report, User.real, :count_by_signup_date
end
add_prev_data report, User.real, :count_by_signup_date, report.prev_start_date, report.prev_end_date
# add_prev_data report, User.real, :count_by_signup_date, report.prev_start_date, report.prev_end_date
end
def self.report_new_contributors(report)
@ -262,7 +262,7 @@ class Report
if report.facets.include?(:prev_period)
prev_period_data = User.real.count_by_first_post(report.prev_start_date, report.prev_end_date)
report.prev_period = prev_period_data.sum { |k, v| v }
report.prev_data = prev_period_data.map { |k, v| { x: k, y: v } }
# report.prev_data = prev_period_data.map { |k, v| { x: k, y: v } }
end
data.each do |key, value|

View File

@ -292,11 +292,11 @@ SQL
topic_id: topic.id
}
channels.each do |channel, user_ids|
channels.each do |channel, ids|
MessageBus.publish(
channel,
message.as_json,
user_ids: user_ids
user_ids: ids
)
end
end

View File

@ -7,6 +7,8 @@ require_dependency "file_store/local_store"
require_dependency "base62"
class Upload < ActiveRecord::Base
SHA1_LENGTH = 40
belongs_to :user
has_many :post_uploads, dependent: :destroy
@ -24,6 +26,12 @@ class Upload < ActiveRecord::Base
validates_with ::Validators::UploadValidator
after_destroy do
User.where(uploaded_avatar_id: self.id).update_all(uploaded_avatar_id: nil)
UserAvatar.where(gravatar_upload_id: self.id).update_all(gravatar_upload_id: nil)
UserAvatar.where(custom_upload_id: self.id).update_all(custom_upload_id: nil)
end
def thumbnail(width = self.thumbnail_width, height = self.thumbnail_height)
optimized_images.find_by(width: width, height: height)
end
@ -148,10 +156,10 @@ class Upload < ActiveRecord::Base
if url =~ /(upload:\/\/)?([a-zA-Z0-9]+)(\..*)?/
sha1 = Base62.decode($2).to_s(16)
if sha1.length > 40
if sha1.length > SHA1_LENGTH
nil
else
sha1.rjust(40, '0')
sha1.rjust(SHA1_LENGTH, '0')
end
end
end
@ -169,12 +177,12 @@ class Upload < ActiveRecord::Base
end
return if uri&.path.blank?
path = uri.path[/(\/original\/\dX\/[\/\.\w]+)/, 1]
return if path.blank?
Upload.find_by("url LIKE ?", "%#{path}")
data = uri.path.match(/(\/original\/\dX\/[\/\.\w]+\/([a-zA-Z0-9]+)[\.\w]+)/)
return if data.blank?
sha1 = data[2]
upload = nil
upload = Upload.find_by(sha1: sha1) if sha1&.length == SHA1_LENGTH
upload || Upload.find_by("url LIKE ?", "%#{data[1]}")
end
def self.migrate_to_new_scheme(limit = nil)

View File

@ -47,6 +47,7 @@ class User < ActiveRecord::Base
has_many :email_change_requests, dependent: :destroy
has_many :directory_items, dependent: :delete_all
has_many :user_auth_tokens, dependent: :destroy
has_many :user_auth_token_logs, dependent: :destroy
has_many :group_users, dependent: :destroy
has_many :groups, through: :group_users
@ -221,6 +222,24 @@ class User < ActiveRecord::Base
end
end
def self.plugin_editable_user_custom_fields
@plugin_editable_user_custom_fields ||= {}
end
def self.register_plugin_editable_user_custom_field(custom_field_name, plugin)
plugin_editable_user_custom_fields[custom_field_name] = plugin
end
def self.editable_user_custom_fields
fields = []
plugin_editable_user_custom_fields.each do |k, v|
fields << k if v.enabled?
end
fields.uniq
end
def self.plugin_staff_user_custom_fields
@plugin_staff_user_custom_fields ||= {}
end
@ -965,13 +984,15 @@ class User < ActiveRecord::Base
result
end
USER_FIELD_PREFIX ||= "user_field_"
def user_fields
return @user_fields if @user_fields
user_field_ids = UserField.pluck(:id)
if user_field_ids.present?
@user_fields = {}
user_field_ids.each do |fid|
@user_fields[fid.to_s] = custom_fields["user_field_#{fid}"]
@user_fields[fid.to_s] = custom_fields["#{USER_FIELD_PREFIX}#{fid}"]
end
end
@user_fields
@ -1283,6 +1304,20 @@ class User < ActiveRecord::Base
true
end
def self.ensure_consistency!
DB.exec <<~SQL
UPDATE users
SET uploaded_avatar_id = NULL
WHERE uploaded_avatar_id IN (
SELECT u1.uploaded_avatar_id FROM users u1
LEFT JOIN uploads up
ON u1.uploaded_avatar_id = up.id
WHERE u1.uploaded_avatar_id IS NOT NULL AND
up.id IS NULL
)
SQL
end
end
# == Schema Information

View File

@ -11,8 +11,19 @@ class UserAuthToken < ActiveRecord::Base
# used when token did not arrive at client
URGENT_ROTATE_TIME = 1.minute
USER_ACTIONS = ['generate']
attr_accessor :unhashed_auth_token
before_destroy do
UserAuthToken.log(action: 'destroy',
user_auth_token_id: self.id,
user_id: self.user_id,
user_agent: self.user_agent,
client_ip: self.client_ip,
auth_token: self.auth_token)
end
def self.log(info)
if SiteSetting.verbose_auth_token_logging
UserAuthTokenLog.create!(info)

View File

@ -1,4 +1,5 @@
class UserAuthTokenLog < ActiveRecord::Base
belongs_to :user
end
# == Schema Information

View File

@ -123,6 +123,31 @@ class UserAvatar < ActiveRecord::Base
tempfile.close! if tempfile && tempfile.respond_to?(:close!)
end
def self.ensure_consistency!
DB.exec <<~SQL
UPDATE user_avatars
SET gravatar_upload_id = NULL
WHERE gravatar_upload_id IN (
SELECT u1.gravatar_upload_id FROM user_avatars u1
LEFT JOIN uploads up
ON u1.gravatar_upload_id = up.id
WHERE u1.gravatar_upload_id IS NOT NULL AND
up.id IS NULL
)
SQL
DB.exec <<~SQL
UPDATE user_avatars
SET custom_upload_id = NULL
WHERE custom_upload_id IN (
SELECT u1.custom_upload_id FROM user_avatars u1
LEFT JOIN uploads up
ON u1.custom_upload_id = up.id
WHERE u1.custom_upload_id IS NOT NULL AND
up.id IS NULL
)
SQL
end
end
# == Schema Information

View File

@ -0,0 +1,71 @@
module UserAuthTokensMixin
extend ActiveSupport::Concern
included do
attributes :id,
:client_ip,
:os,
:device_name,
:icon,
:created_at
end
def client_ip
object.client_ip.to_s
end
def os
case object.user_agent
when /Android/i
'Android'
when /iPhone|iPad|iPod/i
'iOS'
when /Macintosh/i
'macOS'
when /Linux/i
'Linux'
when /Windows/i
'Windows'
else
I18n.t('staff_action_logs.unknown')
end
end
def device_name
case object.user_agent
when /Android/i
I18n.t('user_auth_tokens.devices.android')
when /iPad/i
I18n.t('user_auth_tokens.devices.ipad')
when /iPhone/i
I18n.t('user_auth_tokens.devices.iphone')
when /iPod/i
I18n.t('user_auth_tokens.devices.ipod')
when /Mobile/i
I18n.t('user_auth_tokens.devices.mobile')
when /Macintosh/i
I18n.t('user_auth_tokens.devices.mac')
when /Linux/i
I18n.t('user_auth_tokens.devices.linux')
when /Windows/i
I18n.t('user_auth_tokens.devices.windows')
else
I18n.t('user_auth_tokens.devices.unknown')
end
end
def icon
case os
when 'Android'
'android'
when 'macOS', 'iOS'
'apple'
when 'Linux'
'linux'
when 'Windows'
'windows'
else
'question'
end
end
end

View File

@ -0,0 +1,16 @@
class UserAuthTokenLogSerializer < ApplicationSerializer
include UserAuthTokensMixin
attributes :action
def action
case object.action
when 'generate'
I18n.t('log_in')
when 'destroy'
I18n.t('unsubscribe.log_out')
else
I18n.t('staff_action_logs.unknown')
end
end
end

View File

@ -0,0 +1,5 @@
class UserAuthTokenSerializer < ApplicationSerializer
include UserAuthTokensMixin
attributes :seen_at
end

View File

@ -112,7 +112,9 @@ class UserSerializer < BasicUserSerializer
:muted_usernames,
:mailing_list_posts_per_day,
:can_change_bio,
:user_api_keys
:user_api_keys,
:user_auth_tokens,
:user_auth_token_logs
untrusted_attributes :bio_raw,
:bio_cooked,
@ -193,6 +195,22 @@ class UserSerializer < BasicUserSerializer
keys.length > 0 ? keys : nil
end
def user_auth_tokens
ActiveModel::ArraySerializer.new(
object.user_auth_tokens.order(:seen_at).reverse_order,
each_serializer: UserAuthTokenSerializer
)
end
def user_auth_token_logs
ActiveModel::ArraySerializer.new(
object.user_auth_token_logs.where(
action: UserAuthToken::USER_ACTIONS
).order(:created_at).reverse_order,
each_serializer: UserAuthTokenLogSerializer
)
end
def bio_raw
object.user_profile.bio_raw
end

View File

@ -24,6 +24,8 @@ class WebHookUserSerializer < UserSerializer
can_change_bio
user_api_keys
group_users
user_auth_tokens
user_auth_token_logs
}.each do |attr|
define_method("include_#{attr}?") do
false

View File

@ -2,12 +2,13 @@
# we are capturing all log output into a log array to return
# to the rake task rather than using `puts` statements.
class DestroyTask
def self.destroy_topics(category)
c = Category.find_by_slug(category)
def self.destroy_topics(category, parent_category = nil)
c = Category.find_by_slug(category, parent_category)
log = []
return "A category with the slug: #{category} could not be found" if c.nil?
descriptive_slug = parent_category ? "#{parent_category}/#{category}" : category
return "A category with the slug: #{descriptive_slug} could not be found" if c.nil?
topics = Topic.where(category_id: c.id, pinned_at: nil).where.not(user_id: -1)
log << "There are #{topics.count} topics to delete in #{category} category"
log << "There are #{topics.count} topics to delete in #{descriptive_slug} category"
topics.each do |topic|
log << "Deleting #{topic.slug}..."
first_post = topic.ordered_posts.first
@ -24,7 +25,7 @@ class DestroyTask
categories = Category.all
log = []
categories.each do |c|
log << destroy_topics(c.slug)
log << destroy_topics(c.slug, c.parent_category&.slug)
end
log
end

View File

@ -176,14 +176,16 @@ class SearchIndexer
attr_reader :scrubbed
def initialize
def initialize(strip_diacritics: false)
@scrubbed = +""
# for now we are disabling this per: https://meta.discourse.org/t/discourse-should-ignore-if-a-character-is-accented-when-doing-a-search/90198/16?u=sam
@strip_diacritics = strip_diacritics
end
def self.scrub(html)
def self.scrub(html, strip_diacritics: false)
return +"" if html.blank?
me = new
me = new(strip_diacritics: strip_diacritics)
Nokogiri::HTML::SAX::Parser.new(me).parse("<div>#{html}</div>")
me.scrubbed
end
@ -201,7 +203,8 @@ class SearchIndexer
DIACRITICS ||= /([\u0300-\u036f]|[\u1AB0-\u1AFF]|[\u1DC0-\u1DFF]|[\u20D0-\u20FF])/
def characters(str)
scrubbed << " #{HtmlScrubber.strip_diacritics(str)} "
str = HtmlScrubber.strip_diacritics(str) if @strip_diacritics
scrubbed << " #{str} "
end
end
end

View File

@ -2,11 +2,11 @@
<head>
<%= discourse_stylesheet_link_tag :wizard, theme_ids: nil %>
<%= preload_script 'ember_jquery' %>
<%= preload_script "locales/#{I18n.locale}" %>
<%= preload_script 'wizard-vendor' %>
<%= preload_script 'wizard-application' %>
<%= preload_script "locales/#{I18n.locale}" %>
<%= render partial: "common/special_font_face" %>
<script src="<%= Discourse.base_uri %>/extra-locales/wizard"></script>
<script src="<%= ExtraLocalesController.url("wizard") %>"></script>
<%= csrf_meta_tags %>
<meta name="discourse-base-uri" content="<%= Discourse.base_uri %>">

View File

@ -92,6 +92,7 @@ module Discourse
if Rails.env == "development" || Rails.env == "test"
config.assets.paths << "#{config.root}/test/javascripts"
config.assets.paths << "#{config.root}/test/stylesheets"
config.assets.paths << "#{config.root}/node_modules"
end
# Allows us to skip minifincation on some files

View File

@ -53,9 +53,9 @@ if (Rails.env.production? && SiteSetting.logging_provider == 'lograge') || ENV["
params[:file] = file.headers
end
if (files = params[:files])
params[:files] = files.map do |file|
file.respond_to?(:headers) ? file.headers : file
if (files = params[:files]) && files.respond_to?(:map)
params[:files] = files.map do |f|
f.respond_to?(:headers) ? f.headers : f
end
end

View File

@ -212,7 +212,7 @@ ar:
banner:
enabled: 'اجعل هذا إعلانا %{when}. سوف يظهر اعلى جميع الصفحات حتى يتم الغاؤه بواسطة المستخدم.'
disabled: 'أزل هذا الإعلان %{when}. لن يظهر بعد الآن في أعلى كلّ صفحة.'
topic_admin_menu: "صلاحيات المدير علي الموضوعات"
topic_admin_menu: "صلاحيات المدير علي المواضيع"
wizard_required: "مرحبًا في نسختك الجديدة من دسكورس! فلنبدأ مع <a href='%{url}' data-auto-route='true'>مُرشد الإعدادات</a> ✨"
emails_are_disabled: "لقد عطّل أحد المدراء الرّسائل الصادرة للجميع. لن تُرسل إشعارات عبر البريد الإلكتروني أيا كان نوعها."
themes:
@ -287,7 +287,7 @@ ar:
many: "{{count}} محرفا"
other: "{{count}} حرف"
suggested_topics:
title: "الموضوعات المقترحة"
title: "المواضيع المقترحة"
pm_title: "رسائل مقترحة "
about:
simple_title: "عنّا"
@ -298,7 +298,7 @@ ar:
stat:
all_time: "منذ التأسيس"
like_count: "الإعجابات"
topic_count: "الموضوعات"
topic_count: "المواضيع"
post_count: "المنشورات"
user_count: "الأعضاء"
active_user_count: "الأعضاء النشطون"
@ -393,10 +393,10 @@ ar:
likes_given: "المعطاة"
likes_received: "المتلقاة"
topics_entered: "المُشاهدة"
topics_entered_long: "الموضوعات المُشاهدة"
topics_entered_long: "المواضيع التي تمت مشاهدتها"
time_read: "وقت القراءة"
topic_count: "الموضوعات"
topic_count_long: "الموضوعات المنشورة"
topic_count: "المواضيع"
topic_count_long: "المواضيع المنشورة"
post_count: "الردود"
post_count_long: "الردود المنشورة"
no_results: "لا نتائج."
@ -490,7 +490,7 @@ ar:
filter_placeholder: "اسم المستخدم"
remove_owner: "حذف كمالك"
owner: "المالك"
topics: "الموضوعات"
topics: "المواضيع"
posts: "المنشورات"
mentions: "الإشارات"
messages: "الرسائل"
@ -531,7 +531,7 @@ ar:
"1": "الإعجابات المعطاة"
"2": "الإعجابات المتلقاة"
"3": "العلامات المرجعية"
"4": "الموضوعات"
"4": "المواضيع"
"5": "الردود"
"6": "الردود"
"7": "الإشارات"
@ -552,8 +552,8 @@ ar:
apply_all: "تطبيق"
position: "مكان"
posts: "المنشورات"
topics: "الموضوعات"
latest: "آخر الموضوعات"
topics: "المواضيع"
latest: "آخر المواضيع"
latest_by: "الاحدث بـ"
toggle_ordering: "تبديل التحكم في الترتيب"
subcategories: "أقسام فرعية"
@ -625,7 +625,7 @@ ar:
dismiss_notifications_tooltip: "اجعل كل الإشعارات مقروءة"
first_notification: "إشعارك الأول! قم بالضغط عليه للبدء."
disable_jump_reply: "لا تنتقل إلى منشوري بعد النشر"
dynamic_favicon: "أظهر عدد الموضوعات الجديدة/المحدّثة في أيقونة المتصفح"
dynamic_favicon: "أظهر عدد المواضيع الجديدة/المحدّثة في أيقونة المتصفح"
theme_default_on_all_devices: "اجعل هذة الواجهة افتراضية على جميع اجهزتي"
external_links_in_new_tab: "فتح الروابط الخارجية في تبويب جديد"
enable_quoting: "فعل خاصية إقتباس النصوص المظللة"
@ -651,7 +651,7 @@ ar:
few_per_day: "أرسل لي رسالة لكل منشور جديد (تقريبا إثنتان يوميا)"
tag_settings: "الأوسمة"
watched_tags: "مراقب"
watched_tags_instructions: "ستراقب آليا كل الموضوعات التي تستخدم هذه الأوسمة. ستصلك إشعارات بالمنشورات و الموضوعات الجديدة، وسيظهر أيضا عدّاد للمنشورات الجديدة بجانب كل موضوع."
watched_tags_instructions: "ستراقب آليا كل المواضيع التي تستخدم هذه الأوسمة. ستصلك إشعارات بالمنشورات و الموضوعات الجديدة، وسيظهر أيضا عدّاد للمنشورات الجديدة بجانب كل موضوع."
tracked_tags: "متابع"
tracked_tags_instructions: "ستتابع آليا كل الموضوعات التي تستخدم هذه الأوسمة. وسيظهر أيضا عدّاد للمنشورات الجديدة بجانب كل موضوع."
muted_tags: "مكتوم"
@ -1150,7 +1150,7 @@ ar:
category_page_style:
categories_only: "الأقسام فقط"
categories_with_featured_topics: "أقسام ذات مواضيع مُميزة"
categories_and_latest_topics: "الأقسام و الموضوعات الأخيرة"
categories_and_latest_topics: "الأقسام والمواضيع الأخيرة"
shortcut_modifier_key:
shift: 'Shift'
ctrl: 'Ctrl'
@ -1886,7 +1886,7 @@ ar:
abandon:
confirm: "أمتأكد من التخلي عن المنشور؟"
no_value: "لا، أبقها"
yes_value: "نعم، لا أريدها"
yes_value: "نعم، لا أريده"
via_email: "وصل هذا المنشور عبر البريد"
via_auto_generated_email: "وصل هذا المنشور عبر بريد مولّد آلياً"
whisper: "هذا المنشور سري خاص بالمشرفين"
@ -3598,7 +3598,7 @@ ar:
topic_title: "موضوع"
post_id: "رقم المشاركة"
post_title: "مشاركة"
category_id: "رقم القسم"
category_id: "رقم التصنيف"
category_title: "قسم"
external_url: "رابط خارجي"
delete_confirm: هل أنت متأكد من حذف هذا الرابط الثابت ؟
@ -3613,7 +3613,8 @@ ar:
next: "التالي"
step: "%{current} من %{total}"
upload: "رفع"
uploading: "يرفع..."
uploading: "جاري الرفع..."
upload_error: "عذرا ، حدث خطأ أثناء تحميل هذا الملف. حاول مرة اخرى."
quit: "ربما لاحقا"
invites:
add_user: "أضف"
@ -3622,3 +3623,7 @@ ar:
admin: "مدير"
moderator: "مشرف"
regular: "مستخدم عادي"
previews:
topic_title: "موضوع النقاش"
share_button: "شاركها"
reply_button: "رد"

View File

@ -196,6 +196,7 @@ de:
privacy_policy: "Datenschutzrichtlinie"
privacy: "Datenschutz"
tos: "Nutzungsbedingungen"
rules: "Regeln"
mobile_view: "Mobile Ansicht"
desktop_view: "Desktop Ansicht"
you: "Du"
@ -252,6 +253,9 @@ de:
drafts:
resume: "Fortsetzen"
remove: "Entfernen"
new_topic: "Neues Thema Entwurf"
new_private_message: "Neuer privater Nachrichten Entwurf"
topic_reply: "Antwort Entwurf"
topic_count_latest:
one: "Zeige {{count}} neues oder aktualisiertes Thema"
other: "Zeige {{count}} neue oder aktualisierte Themen"
@ -491,6 +495,7 @@ de:
"12": "Gesendete Objekte"
"13": "Posteingang"
"14": "Ausstehend"
"15": "Entwürfe"
categories:
all: "Alle Kategorien"
all_subcategories: "alle"
@ -513,7 +518,7 @@ de:
one: "1 Thema"
other: "%{count} Themen"
topic_stat_sentence:
one: "1 neues Thema seit 1 %{unit}."
one: "%{count} neues Thema seit 1 %{unit}."
other: "%{count} neue Themen seit 1 %{unit}."
more: "(%{count}mehr) …"
ip_lookup:
@ -532,6 +537,7 @@ de:
post_count: "# Beiträge"
confirm_delete_other_accounts: "Bist du sicher, dass du diese Konten löschen willst?"
powered_by: "powered by <a href='https://ipinfo.io'>ipinfo.io</a>"
copied: "kopiert"
user_fields:
none: "(wähle eine Option aus)"
user:
@ -634,6 +640,7 @@ de:
revoke_access: "Entziehe Zugriffsrecht"
undo_revoke_access: "Entziehe Zugriffsrecht widerrufen"
api_approved: "Genehmigt:"
api_last_used_at: "Zuletzt benutzt am:"
theme: "Design"
home: "Standard-Startseite"
staged: "Vorbereitet"
@ -673,6 +680,7 @@ de:
choose_new: "Wähle ein neues Passwort"
choose: "Wähle ein Passwort"
second_factor_backup:
title: "Zwei-Faktor-Wiederherstellungs-Codes"
regenerate: "Erneuern"
disable: "Deaktivieren"
enable: "Aktivieren"
@ -681,6 +689,9 @@ de:
copied_to_clipboard: "Wurde in Zwischenablage kopiert"
copy_to_clipboard_error: "Beim Kopieren in die Zwischenablage trat ein Fehler auf"
remaining_codes: "Du hast noch <strong>{{count}}</strong> Wiederherstellungscodes übrig."
codes:
title: "Wiederherstellungscodes generiert"
description: "Jeder dieser Wiederherstellungscodes kann nur einmal benutzt werden. Bewahre diese an einem sicheren aber verfügbaren Ort auf."
second_factor:
title: "Zwei-Faktor-Authentifizierung"
disable: "Zwei-Faktor-Authentifizierung deaktivieren"
@ -768,6 +779,18 @@ de:
any: "(keine Einschränkung)"
password_confirmation:
title: "Wiederholung des Passworts"
auth_tokens:
title: "Kürzlich benutzte Geräte"
title_logs: "Authentifizierungs-Logs"
ip_address: "IP Adresse"
created: "Erzeugt"
first_seen: "Erstmalig gesehen"
last_seen: "Zuletzt gesehen"
operating_system: "Betriebssystem"
location: "Ort"
action: "Aktion"
login: "Einloggen"
logout: "Überall ausloggen"
last_posted: "Letzter Beitrag"
last_emailed: "Letzte E-Mail"
last_seen: "Zuletzt gesehen"
@ -999,6 +1022,7 @@ de:
hide_session: "Erinnere mich morgen"
hide_forever: "Nein danke"
hidden_for_session: "In Ordnung, ich frag dich morgen wieder. Du kannst dir auch jederzeit unter „Anmelden“ ein Benutzerkonto erstellen."
intro: "Hallo! Es sieht so aus, als ob dir diese Diskussion gefällt, aber Du hast bislang noch kein Konto angelegt."
value_prop: "Wenn du ein Benutzerkonto anlegst, merken wir uns, was du gelesen hast, damit du immer dort fortsetzen kannst, wo du aufgehört hast. Du kannst auch Benachrichtigungen hier oder per E-Mail erhalten, wenn jemand auf deine Beiträge antwortet. Beiträge, die dir gefallen, kannst du mit einem Like versehen und diese Freude mit allen teilen. :heartpulse:"
summary:
enabled_description: "Du siehst gerade eine Zusammenfassung des Themas: die interessantesten Beiträge, die von der Community bestimmt wurden."
@ -1013,6 +1037,8 @@ de:
disable: "Gelöschte Beiträge anzeigen"
private_message_info:
title: "Nachricht"
invite: "Lade andere ein"
edit: "Hinzufügen oder Entfernen"
leave_message: "Möchtest du diese Nachricht wirklich verlassen?"
remove_allowed_user: "Willst du {{name}} wirklich aus dieser Unterhaltung entfernen?"
remove_allowed_group: "Willst du {{name}} wirklich aus dieser Unterhaltung entfernen?"
@ -1148,6 +1174,7 @@ de:
default_header_text: Auswählen…
no_content: Keine Treffer gefunden
filter_placeholder: Suchen…
filter_placeholder_with_any: Suchen oder erzeugen
create: "Erstelle: '{{content}}'"
max_content_reached:
one: "Du kannst nur einen Eintrag auswählen."
@ -1297,6 +1324,9 @@ de:
shared_draft:
label: "Gemeinsame Vorlage"
desc: "Entwerfe ein Thema, das nur für das Team sichtbar ist."
toggle_topic_bump:
label: "Bump des Themas umschalten"
desc: "Antworten ohne das Bump-Datum des Themas zu ändern."
notifications:
tooltip:
regular:
@ -1684,6 +1714,7 @@ de:
reset_read: "„Gelesen“ zurücksetzen"
make_public: "Umwandeln in öffentliches Thema"
make_private: "in Nachricht umwandeln"
reset_bump_date: "Bump-Datum zurücksetzen"
feature:
pin: "Thema anheften"
unpin: "Thema loslösen"
@ -2556,6 +2587,8 @@ de:
moderation_tab: "Moderation"
disabled: Deaktiviert
timeout_error: "Entschuldige bitte, die Warteschlange ist zu lang, bitte wähle ein kürzeres Intervall"
exception_error: "Entschuldige, ein Fehler während der Ausführung des Query ist aufgetreten"
too_many_requests: Du hast diese Aktion zu oft ausgeführt. Bitte warte bevor Du es erneut probierst.
reports:
trend_title: "%{percent} Veränderung. Aktuell %{current}, war %{prev} in vorherigem Zeitraum."
today: "Heute"
@ -2922,9 +2955,16 @@ de:
revert: "Änderungen verwerfen"
revert_confirm: "Möchtest du wirklich deine Änderungen verwerfen?"
theme:
theme: "Theme"
component: "Komponente"
components: "Komponenten"
import_theme: "Design importieren"
customize_desc: "Anpassen:"
title: "Designs"
modal_title: "Erstelle Theme"
create: "Erstelle"
create_type: "Typ:"
create_name: "Name:"
long_title: "Farben, CSS und HTML-Inhalte deiner Seite erweitern"
edit: "Bearbeiten"
edit_confirm: "Dies ist ein Remote-Theme, wenn du CSS/HTML bearbeitest, werden deine Änderungen zurückgesetzt, wenn du das Theme das nächste Mal aktualisierst."
@ -2939,6 +2979,10 @@ de:
color_scheme_select: "Wähle Farben für dieses Theme"
custom_sections: "Benutzerdefinierte Abschnitte:"
theme_components: "Theme-Komponenten"
switch_component: "Erzeuge Theme"
switch_component_alert: "Bist Du sicher, dass Du diese Komponente in ein Theme konvertieren möchtest? Dies wird es zu einem unabhängigen Theme machen und es wird als Kind von allen Themes entfernt werden."
switch_theme: "Komponente erzeugen"
switch_theme_alert: "Bist Du sicher, dass Du dieses Theme zu einer Komponente konvertieren möchtest? Es will als Elternteil von all Komponenten entfernt werden."
uploads: "Uploads"
no_uploads: "Du kannst Medieninhalte hochladen, die zu deinem Theme gehören, wie etwa Schriftarten und Bilder."
add_upload: "Upload hinzufügen"
@ -2961,6 +3005,7 @@ de:
public_key: "Gewähre dem folgenden öffentlichen Schlüssel den Zugriff auf das Repository:"
about_theme: "Über das Theme"
license: "Lizenz"
component_of: "Komponente von:"
update_to_latest: "Aktualisieren auf neueste Version"
check_for_updates: "Nach Aktualisierungen suchen"
updating: "Wird aktualisiert..."
@ -2968,9 +3013,11 @@ de:
add: "Hinzufügen"
theme_settings: "Theme-Einstellungen"
no_settings: "Dieses Theme hat keine Einstellungen."
empty: "Keine Teile"
commits_behind:
one: "Theme liegt 1 Commit zurück!"
other: "Theme liegt {{count}} Commits zurück!"
compare_commits: "(Siehe neue Beiträge)"
scss:
text: "CSS"
title: "Gib benutzerdefiniertes CSS ein, wir akzeptieren alle gültigen CSS und SCSS-Stile"

View File

@ -194,6 +194,7 @@ en:
ap_southeast_1: "Asia Pacific (Singapore)"
ap_southeast_2: "Asia Pacific (Sydney)"
cn_north_1: "China (Beijing)"
cn_northwest_1: "China (Ningxia)"
eu_central_1: "EU (Frankfurt)"
eu_west_1: "EU (Ireland)"
eu_west_2: "EU (London)"
@ -863,6 +864,19 @@ en:
password_confirmation:
title: "Password Again"
auth_tokens:
title: "Recently Used Devices"
title_logs: "Authentication Logs"
ip_address: "IP Address"
created: "Created"
first_seen: "First Seen"
last_seen: "Last Seen"
operating_system: "Operating System"
location: "Location"
action: "Action"
login: "Log in"
logout: "Log out everywhere"
last_posted: "Last Post"
last_emailed: "Last Emailed"
last_seen: "Seen"

View File

@ -196,6 +196,7 @@ es:
privacy_policy: "Política de Privacidad"
privacy: "Privacidad"
tos: "Condiciones de uso"
rules: "Reglas"
mobile_view: "Versión móvil"
desktop_view: "Versión de escritorio"
you: "Tú"
@ -536,6 +537,7 @@ es:
post_count: "# posts"
confirm_delete_other_accounts: "¿Seguro que quieres eliminar estas cuentas?"
powered_by: "powered by <a href='https://ipinfo.io'>ipinfo.io</a>"
copied: "copiado"
user_fields:
none: "(selecciona una opción)"
user:
@ -638,6 +640,7 @@ es:
revoke_access: "Revocar acceso"
undo_revoke_access: "Deshacer revocación de acceso"
api_approved: "Aprobado:"
api_last_used_at: "Fecha de último uso:"
theme: "Theme"
home: "Página de inicio por defecto"
staged: "Temporal"
@ -776,6 +779,18 @@ es:
any: "cualquiera"
password_confirmation:
title: "Introduce de nuevo la contraseña"
auth_tokens:
title: "Dispositivos utilizados recientemente"
title_logs: "Logs de Autenticación"
ip_address: "Dirección IP"
created: "Creado"
first_seen: "Primera vez"
last_seen: "Última vez"
operating_system: "Sistema operativo"
location: "Ubicación"
action: "Acción"
login: "Inicio de sesión"
logout: "Cierre de sesión en todos los dispositivos"
last_posted: "Último post"
last_emailed: "Último Enviado por email"
last_seen: "Visto por última vez"
@ -1007,6 +1022,8 @@ es:
hide_session: "Recordar mañana"
hide_forever: "no, gracias"
hidden_for_session: "Vale, te preguntaremos mañana. Recuerda que también puedes usar el botón 'Iniciar sesión' para crear una cuenta en cualquier momento."
intro: "¡Hola! Parece que estás disfrutando del debate, pero no tienes una cuenta registrada aún."
value_prop: "Cuando registras una cuenta, recordamos exactamente lo que has leído, para que puedas volver justo donde estabas leyendo. También recibes notificaciones, por aquí y por email, cuando alguien responde a tus mensajes. ¡También puedes darle a \"Me gusta\" a los mensajes para compartir amor! :heartpulse:"
summary:
enabled_description: "Estás viendo un resumen de este tema: los posts más interesantes determinados por la comunidad."
description: "Hay <b>{{replyCount}}</b> respuestas."
@ -1020,6 +1037,8 @@ es:
disable: "Mostrar Posts Eliminados"
private_message_info:
title: "Mensaje"
invite: "Invitar a otros..."
edit: "Añadir o quitar..."
leave_message: "¿Realmente quieres dejar este mensaje?"
remove_allowed_user: "¿Seguro que quieres eliminar a {{name}} de este mensaje?"
remove_allowed_group: "¿Seguro que quieres eliminar a {{name}} de este mensaje?"
@ -1090,7 +1109,7 @@ es:
change_email: "Cambiar dirección de email"
provide_new_email: "Poner un nuevo email, y te reenviaremos una confirmación de email."
submit_new_email: "Actualizar Dirección de Email"
sent_activation_email_again: "Te hemos enviado otro e-mail de activación a <b>{{currentemail}}</b>. Podría tardar algunos minutos en llegar; asegúrate de revisar tu carpeta de spam."
sent_activation_email_again: "Te hemos enviado otro e-mail de activación a <b>{{currentEmail}}</b>. Podría tardar algunos minutos en llegar; asegúrate de revisar tu carpeta de spam."
to_continue: "Por favor, inicia sesión"
preferences: "Debes tener una sesión iniciada para cambiar tus preferencias de usuario."
forgot: "No me acuerdo de los detalles de mi cuenta."
@ -1155,6 +1174,7 @@ es:
default_header_text: Seleccionar...
no_content: Ninguna coincidencia encontrada
filter_placeholder: Buscar...
filter_placeholder_with_any: Buscar o crear...
create: "Crear: '{{content}}'"
max_content_reached:
one: "Puedes seleccionar únicamente {{count}} item."
@ -1304,6 +1324,9 @@ es:
shared_draft:
label: "Borrador Compartido"
desc: "Haz borrador al tema que será visible únicamente por el staff"
toggle_topic_bump:
label: "Alternar BUMP del tema"
desc: "Responder sin alterar la fecha dump del tema"
notifications:
tooltip:
regular:
@ -1691,6 +1714,7 @@ es:
reset_read: "Restablecer datos de lectura"
make_public: "Convertir en tema público"
make_private: "Crear Mensaje Personal"
reset_bump_date: "Resetear fecha Bump"
feature:
pin: "Destacar tema"
unpin: "Dejar de destacar tema"
@ -2568,6 +2592,7 @@ es:
disabled: Desactivado
timeout_error: "Lo sentimos, la solicitud está durando demasiado, por favor selecciona un periodo más corto"
exception_error: "Lo siento, ha ocurrido un error al ejecutar la consulta"
too_many_requests: "Has realizado esta acción demasiadas veces. Por favor, espera antes de intentarlo de nuevo."
reports:
trend_title: "%{percent} de cambio. Actualmente %{current}, era %{prev} en el periodo previo."
today: "Hoy"
@ -2934,9 +2959,16 @@ es:
revert: "Revertir los cambios"
revert_confirm: "¿Estás seguro de querer revertir los cambios?"
theme:
theme: "Tema"
component: "Componente"
components: "Componentes"
import_theme: "Importar Theme"
customize_desc: "Personalizar:"
title: "Themes"
modal_title: "Crear tema"
create: "Crear"
create_type: "Tipo:"
create_name: "Nombre:"
long_title: "Modifique los colores, los CSS y los contenidos HTML de su sitio"
edit: "Editar"
edit_confirm: "Este es un theme remoto, si editas CSS/HTML, los cambios serán borrados en la próxima actualización al theme."
@ -2951,6 +2983,10 @@ es:
color_scheme_select: "Selecciona colores para ser usados en el theme"
custom_sections: "Personalizaciones:"
theme_components: "Componentes del Theme"
switch_component: "Convertir a tema"
switch_component_alert: "¿Seguro que quieres convertir este componente en theme? Esto lo convertirá en un theme independiente y se eliminará como un secundario de todos los themes."
switch_theme: "Hacer componente"
switch_theme_alert: "¿Seguro que quieres convertir este theme en componente? Se eliminará como principal de todos los componentes."
uploads: "Subidos"
no_uploads: "Puedes subir archivos asociados con tu theme como fuentes e imágenes "
add_upload: "Agregar Subido"
@ -2973,6 +3009,7 @@ es:
public_key: "Conceda la siguiente clave pública de acceso para el repositorio:"
about_theme: "Acerca del Theme"
license: "Licencia"
component_of: "Componente de:"
update_to_latest: "Actualizar a lo último"
check_for_updates: "Verificar por Actualizaciones"
updating: "Actualizando..."
@ -2980,9 +3017,11 @@ es:
add: "Agregar"
theme_settings: "Ajustes del Theme"
no_settings: "Este theme no tiene ajustes."
empty: "Sin items"
commits_behind:
one: "Theme está 1 commit detrás!"
other: "Theme está {{count}} commits detrás!"
compare_commits: "(Ver nuevos commits)"
scss:
text: "CSS"
title: "Ingresa tu CSS, aceptamos estilos válidos de CSS y SCSS"

View File

@ -196,6 +196,7 @@ fr:
privacy_policy: "Politique de confidentialité"
privacy: "Confidentialité"
tos: "Conditions générales d'utilisation"
rules: "Règles"
mobile_view: "Vue mobile"
desktop_view: "Vue bureau"
you: "Vous"
@ -536,6 +537,7 @@ fr:
post_count: "# messages"
confirm_delete_other_accounts: "Êtes-vous sûr de vouloir supprimer tous ces comptes ?"
powered_by: "propulsé par <a href='https://ipinfo.io'>ipinfo.io</a>"
copied: "copié"
user_fields:
none: "(choisir une option)"
user:
@ -638,6 +640,7 @@ fr:
revoke_access: "Révoquer l'accès"
undo_revoke_access: "Annuler la révocation d'accès"
api_approved: "Approuvé :"
api_last_used_at: "Dernièrement utilisé le :"
theme: "Thème"
home: "Page d'accueil par défaut"
staged: "Distant"
@ -777,6 +780,18 @@ fr:
any: "tous"
password_confirmation:
title: "Confirmation du mot de passe"
auth_tokens:
title: "Appareils utilisés récemment"
title_logs: "Journaux d'authentification"
ip_address: "Adresse IP"
created: "Créé"
first_seen: "Première utilisation"
last_seen: "Dernière utilisation"
operating_system: "Système d'exploitation"
location: "Localisation"
action: "Action"
login: "Connecter"
logout: "Déconnecter tout"
last_posted: "Dernier message"
last_emailed: "Dernier courriel"
last_seen: "Vu"
@ -1009,6 +1024,7 @@ fr:
hide_forever: "non merci"
hidden_for_session: "Très bien, je vous demanderai demain. Vous pouvez toujours cliquer sur « Se connecter » pour créer un compte."
intro: "Bonjour ! Vous semblez apprécier la discussion, mais n'avez pas encore créé de compte."
value_prop: "Quand vous créez votre compte, nous stockons ce que vous avez lu pour qu'à votre retour vous puissiez continuer là on vous vous êtes arrêtés. Vous recevez aussi des notifications, ici et par courriel, dès que quelqu'un vous répond. Et vous pouvez aimer les messages pour partager vos coups de cœurs. :heartpulse:"
summary:
enabled_description: "Vous visualisez un résumé de ce sujet : les messages les plus intéressants choisis par la communauté."
description: "Il y a <b>{{replyCount}}</b> réponses."
@ -1022,6 +1038,8 @@ fr:
disable: "Afficher les messages supprimés"
private_message_info:
title: "Message direct"
invite: "Inviter d'autres utilisateurs…"
edit: "Ajouter ou supprimer…"
leave_message: "Êtes-vous sûr de vouloir quitter cette conversation ?"
remove_allowed_user: "Êtes-vous sûr de vouloir supprimer {{name}} de ce message direct ?"
remove_allowed_group: "Êtes-vous sûr de vouloir supprimer {{name}} de ce message direct ?"
@ -1157,6 +1175,7 @@ fr:
default_header_text: Sélectionner…
no_content: Aucune correspondance trouvée
filter_placeholder: Rechercher...
filter_placeholder_with_any: Rechercher ou créer…
create: "Créer : '{{content}}'"
max_content_reached:
one: "Vous ne pouvez séléctionner que {{count}} élément."
@ -1306,6 +1325,9 @@ fr:
shared_draft:
label: "Ebauche partagée"
desc: "Créer ébauche d'un sujet qui ne sera visible qu'aux responsables"
toggle_topic_bump:
label: "Basculer l'actualisation des sujets"
desc: "Répondre sans modifier la date d'actualisation du sujet"
notifications:
tooltip:
regular:
@ -1693,6 +1715,7 @@ fr:
reset_read: "Réinitialiser les données de lecture"
make_public: "Rendre le sujet public"
make_private: "Transformer en message direct"
reset_bump_date: "Réinitialiser la date d'actualisation"
feature:
pin: "Épingler le sujet"
unpin: "Désépingler le sujet"
@ -2570,6 +2593,7 @@ fr:
disabled: Désactivé
timeout_error: "Désolé, la requête prend trop de temps, veuillez sélectionner un intervalle plus court"
exception_error: "Désolé, une erreur s'est produite à l'exécution de la requête"
too_many_requests: Vous avez effectué cette action trop de fois. Merci d'attendre avant de ressayer.
reports:
trend_title: "%{percent} modifié. Actuellement %{current}, était %{prev} pour la période précédente."
today: "Aujourd'hui"
@ -2936,9 +2960,16 @@ fr:
revert: "Annuler les changements"
revert_confirm: "Êtes-vous sûr de vouloir annuler vos changements ?"
theme:
theme: "Thème"
component: "Composant"
components: "Composants"
import_theme: "Importer un thème"
customize_desc: "Personaliser :"
title: "Thèmes"
modal_title: "Créer un thème"
create: "Créer"
create_type: "Type :"
create_name: "Nom :"
long_title: "Modifier les couleurs, le CSS et le contenu HTML de votre site"
edit: "Modifier"
edit_confirm: "Ceci est un thème distant, si vous modifiez le CSS/HTML vos modifications seront écrasées la prochaine fois que vous le mettez à jour."
@ -2953,6 +2984,10 @@ fr:
color_scheme_select: "Sélectionner les couleurs utilisées par le thème"
custom_sections: "Sections personnalisées :"
theme_components: "Composants du thème :"
switch_component: "Convertir en thème"
switch_component_alert: "Êtes-vous sûr de vouloir convertir ce composant en thème ? Cela va en faire un thème indépendant et sera supprimé des tous les thèmes l'utilisant."
switch_theme: "Convertir en composant"
switch_theme_alert: "Êtes-vous sûr de vouloir convertir ce thème en composant ? Il sera supprimé comme thème parent des composants utilisés."
uploads: "Uploads"
no_uploads: "Vous pouvez envoyer des ressources associées à votre thème comme des polices ou des images"
add_upload: "Ajouter un fichier"
@ -2975,6 +3010,7 @@ fr:
public_key: "Accorder un accès dépôt à la clef publique suivante :"
about_theme: "À propos du thème"
license: "Licence"
component_of: "Composant de :"
update_to_latest: "Mettre à jour"
check_for_updates: "Vérifier les mises à jour"
updating: "Mise à jour…"
@ -2982,9 +3018,11 @@ fr:
add: "Ajouter"
theme_settings: "Paramètres thème"
no_settings: "Ce thème n'a pas de paramètres."
empty: "Aucun élément"
commits_behind:
one: "Le thème est en retard de 1 commit !"
other: "Le thème est en retard de {{count}} commits !"
compare_commits: "(Voir les nouveaux changements)"
scss:
text: "CSS"
title: "Entrez du CSS personnalisé, nous acceptons tous les styles CSS et SCSS valides."

View File

@ -183,11 +183,13 @@ pl_PL:
enabled: 'wylistowanie %{when}'
disabled: 'odlistowanie %{when}'
banner:
enabled: 'Ten temat został ustawiony jako baner . Będzie widoczny na górze każdej strony, póki nie zostanie ukryty przez użytkownika.'
enabled: 'ustawił ten baner %{when}. Będzie widoczny na górze każdej strony, póki nie zostanie ukryty przez użytkownika.'
disabled: 'Ten temat nie jest już banerem. Nie będzie dalej wyświetlany na górze każdej strony.'
topic_admin_menu: "akcje administratora"
wizard_required: "Witaj na Twoim na nowym forum Discourse! Zacznijmy od <a href='%{url}' data-auto-route='true'>kreatora ustawień</a> ✨"
emails_are_disabled: "Wysyłanie e-maili zostało globalnie wyłączone przez administrację. Powiadomienia e-mail nie będą dostarczane."
bootstrap_mode_enabled: "Aby ułatwić uruchomienie Twojej strony, jesteś w trybie bootstrap. Wszyscy nowi użytkownicy otrzymają poziom zaufania 1 i będą otrzymywać codzienne wiadomości e-mail. To zostanie automatyczonie wyłączone, kiedy %{min_users}osób dołączy."
bootstrap_mode_disabled: "Tryb bootstrap zostanie wyłączony w ciągu 24 godzin."
themes:
default_description: "Domyślny"
s3:
@ -678,6 +680,7 @@ pl_PL:
revoke_access: "Zablokuj dostęp"
undo_revoke_access: "Cofnij zablokowanie dostępu"
api_approved: "Zatwierdzony:"
api_last_used_at: "Ostatnio użyto:"
theme: "Motyw"
home: "Domyślna strona domowa"
staff_counters:
@ -2581,6 +2584,7 @@ pl_PL:
page_views: "Wyświetlenia strony"
page_views_short: "Wyświetlenia strony"
show_traffic_report: "Pokaż szczegółowy raport ruchu"
activity_metrics: Metryka aktywności
reports:
today: "Dzisiaj"
yesterday: "Wczoraj"

View File

@ -48,6 +48,9 @@ pt:
x_seconds:
one: "1s"
other: "%{count}s"
less_than_x_minutes:
one: "< 1m"
other: "< %{count}m"
x_minutes:
one: "1m"
other: "%{count}m"
@ -57,6 +60,9 @@ pt:
x_days:
one: "1d"
other: "%{count}d"
x_months:
one: "1 mês"
other: "%{count}meses"
about_x_years:
one: "1a"
other: "%{count}a"
@ -119,6 +125,7 @@ pt:
user_left: "%{who} removeram-se desta mensagem %{when}"
removed_user: "removeu %{who} %{when}"
removed_group: "removeu %{who} %{when}"
autobumped: "automaticamente colidido %{when}"
autoclosed:
enabled: 'fechado %{when}'
disabled: 'aberto %{when}'
@ -143,6 +150,8 @@ pt:
topic_admin_menu: "Ações administrativas dos Tópicos"
wizard_required: "Bem-vindo ao seu novo Discourse! Vamos começar com <a href='%{url}' data-auto-route='true'>o assistente de configuração</a> ✨"
emails_are_disabled: "Todos os envios de e-mail foram globalmente desativados por um administrador. Nenhum e-mail de notificação será enviado."
bootstrap_mode_enabled: "Para que o inicio do teu Site seja o mais simples possivél, estás agora em modo de inicialização simples. A todos os novos utilizadores será concedido o Nível de Confiança 1 e o resumo por e-mail enviado diariamente estará ativado. Isto será automaticamente desligado quando %{min_users}utilizadores se tiverem juntado ao fórum."
bootstrap_mode_disabled: "O modo de inicialização simples será desactivado em 24 horas."
themes:
default_description: "Predefinição"
s3:

View File

@ -164,7 +164,6 @@ zh_TW:
guidelines: "守則"
privacy_policy: "隱私權政策"
privacy: "隱私"
terms_of_service: "服務條款"
mobile_view: "手機版網站"
desktop_view: "電腦版網站"
you: "你"
@ -438,8 +437,6 @@ zh_TW:
reorder:
title: "重新排序分類"
title_long: "重新排序分類列表"
fix_order: "固定位置"
fix_order_tooltip: "並非所有的分類皆有唯一的位置參數, 可能會有出乎意料之外的結果."
save: "儲存順序"
apply_all: "申請"
position: "位置"
@ -630,7 +627,6 @@ zh_TW:
upload_title: "上傳你的圖片"
upload_picture: "上傳圖片"
image_is_not_a_square: "警告:我們裁切了你的圖片,因為該圖片不是正方形的。"
cache_notice: "更改了頭像成功,但是鑒於瀏覽器緩存可能需要一段時間後才會生效。"
change_profile_background:
title: "基本資料背景圖案"
instructions: "個人資料背景會被置中且默認寬度為850px。"
@ -804,7 +800,6 @@ zh_TW:
most_liked_users: "讚誰最多"
most_replied_to_users: "最多回覆至"
no_likes: "暫無讚"
associated_accounts: "登入"
ip_address:
title: "最近的 IP 位址"
registration_ip_address:
@ -955,9 +950,6 @@ zh_TW:
preferences: "需要登入後更改設置"
forgot: "我記不清賬號詳情了"
not_approved: "你的帳號尚未獲得批准。一旦你的帳號獲得批准,你會收到一封電子郵件。"
google:
title: "使用 Google 帳號"
message: "使用 Google 帳號認証 (請確定你的網頁瀏覽器未阻擋彈出視窗)"
google_oauth2:
title: "使用 Google 帳號"
message: "使用 Google 帳號認證 ( 請確定你的網頁瀏覽器不會阻擋彈出視窗 )"
@ -1090,13 +1082,26 @@ zh_TW:
title: "你忘記添加收信人了嗎?"
body: "目前該私信只發給了你自己!"
admin_options_title: "此討論話題可選用之工作人員設定選項"
composer_actions:
reply_as_private_message:
label: 新增訊息
desc: 建立一則私訊
reply_to_topic:
label: 回復到討論話題
desc: 回到到討論話題,但不是特定貼文
create_topic:
label: "新增討論話題"
notifications:
title: "當有人以「@用戶名稱」提及您、回覆您的貼文、或是傳送訊息給您的時候通知您的設定。"
none: "目前無法載入通知訊息。"
empty: "未找到任何通知。"
more: "檢視較舊的通知"
total_flagged: "所有被投訴的文章"
invitee_accepted: "<span>{{username}}</span> 接受了您的邀請"
moved_post: "<span>{{username}}</span> 移動 {{description}}"
linked: "<span>{{username}}</span> {{description}}"
granted_badge: "得到 '{{description}}'"
topic_reminder: "<span>{{username}}</span> {{description}}"
popup:
mentioned: '{{username}}在“{{topic}}”提到了你 - {{site_title}}'
group_mentioned: '{{username}}在“{{topic}}”提到了你 - {{site_title}}'
@ -1104,6 +1109,7 @@ zh_TW:
replied: '{{username}}在“{{topic}}”回覆了你 - {{site_title}}'
posted: '{{username}}在“{{topic}}”中發佈了帖子 - {{site_title}}'
linked: '{{username}}在“{{topic}}”中連結了你的帖子 - {{site_title}}'
confirm_body: '成功! 通知已啟用'
upload_selector:
title: "加入一張圖片"
title_with_attachments: "加入一張圖片或一個檔案"
@ -1118,20 +1124,29 @@ zh_TW:
uploading: "正在上傳"
select_file: "選取檔案"
image_link: "連結你的圖片將指向"
default_image_alt_text: 圖像
search:
sort_by: "排序"
relevance: "最相關"
latest_post: "最新發帖"
latest_topic: "最新的討論話題"
most_viewed: "最多閲讀"
most_liked: "最多讚"
select_all: "選擇全部"
clear_all: "清除全部"
too_short: "你的搜索詞太短。"
title: "搜尋討論話題、文章、用戶或分類"
full_page_title: "搜尋討論話題或貼文"
no_results: "未找到任何結果。"
no_more_results: "沒有找到更多的結果。"
searching: "正在搜尋..."
post_format: "#{{post_number}} {{username}}"
results_page: "'{{term}}' 的搜尋結果"
start_new_topic: "或許開始一個新的討論話題?"
or_search_google: "或是嘗試利用 Google 搜尋:"
search_google: "嘗試利用 Google 搜尋:"
search_google_button: "Google"
search_google_title: "搜尋這個網站"
context:
user: "搜尋 @{{username}} 的文章"
category: "搜索 #{{category}} 分類"
@ -1141,19 +1156,28 @@ zh_TW:
title: 高級搜索
posted_by:
label: 發帖人
in_category:
label: 已分類
in_group:
label: 在該群組中
with_badge:
label: 有該徽章
with_tags:
label: 已標記
filters:
likes: 我給了讚的
posted: 我參與發帖
watching: 我正在關注
tracking: 我正在追蹤
private: 在我的訊息
first: 是第一帖
pinned: 是置頂的
unpinned: 不是置頂的
seen: 我已讀的
unseen: 我還未讀的
wiki: 公共編輯
images: 包含圖像
all_tags: 以上所有的標籤
statuses:
label: 當主題
open: 是開放的
@ -1189,8 +1213,10 @@ zh_TW:
dismiss_new: "設定新文章為已讀"
toggle: "批量切換選擇討論話題"
actions: "批量操作"
change_category: "設定分類"
close_topics: "關閉討論話題"
archive_topics: "已封存的討論話題"
notification_level: "通知"
choose_new_category: "為主題選擇新類別:"
selected:
other: "你已選擇了 <b>{{count}}</b> 個討論話題。"
@ -1229,6 +1255,7 @@ zh_TW:
other: "本主題中的 {{count}} 帖"
create: '新討論話題'
create_long: '建立新討論話題'
open_draft: "開啟草稿"
private_message: '發送訊息'
archive_message:
help: '移動消息到存檔'
@ -1236,6 +1263,9 @@ zh_TW:
move_to_inbox:
title: '移動到收件箱'
help: '移動消息到收件箱'
edit_message:
help: '編輯這個訊息的第一篇貼文'
title: '編輯訊息'
list: '討論話題'
new: '新討論話題'
unread: '未讀'
@ -1275,6 +1305,34 @@ zh_TW:
jump_reply_up: jump to earlier reply
jump_reply_down: jump to later reply
deleted: "此討論話題已被刪除"
auto_update_input:
tomorrow: "明天"
this_weekend: "這週末"
next_week: "下週"
two_weeks: "兩週"
next_month: "下個月"
three_months: "三個月"
six_months: "六個月"
one_year: "一年"
forever: "永久"
pick_date_and_time: "挑選日期與時間"
set_based_on_last_post: "依照上一篇貼文來關閉"
publish_to_category:
title: "定時發表"
temp_open:
title: "暫時開啟"
auto_reopen:
title: "自動開啟討論話題"
temp_close:
title: "暫時關閉"
auto_close:
title: "自動關閉討論話題"
label: "自動關閉討論話題的期限:"
error: "請輸入一個有效的值。"
auto_delete:
title: "自動刪除討論話題"
reminder:
title: "提醒我"
auto_close_title: '自動關閉設定'
auto_close_immediate:
other: "主題中的最後一帖是 %{hours} 小時前發出的,所以主題將會立即關閉。"
@ -1351,6 +1409,7 @@ zh_TW:
visible: "出現在列表上"
reset_read: "重置讀取資料"
make_public: "設置為公共主題"
make_private: "設置為私訊"
feature:
pin: "置頂主題"
unpin: "取消置頂主題"
@ -1424,6 +1483,7 @@ zh_TW:
success_email: "我們發了一封郵件邀請<b>{{emailOrUsername}}</b>。邀請被接受後你會收到通知。檢查用戶頁中的邀請標籤頁來追蹤你的邀請。"
success_username: "我們已經邀請該使用者加入此主題討論"
error: "抱歉,我們不能邀請這個人。可能他已經被邀請了?(邀請有頻率限制)"
success_existing_email: "已經有一個有此電子郵件 <b>{{emailOrUsername}}</b> 的使用者存在。我們已邀請那位使用者來參與這個話題。"
login_reply: '登入以發表回應'
filters:
n_posts:
@ -1455,6 +1515,7 @@ zh_TW:
instructions:
other: "請選擇一位新用戶作為此 {{count}} 篇由 <b>{{old_user}}</b> 撰寫之文章的擁有者。"
change_timestamp:
title: "變更時間標籤..."
action: "變更時間戳記"
invalid_timestamp: "時間戳記不能為將來的時刻。"
error: "更改主題時間時發生錯誤。"
@ -1462,6 +1523,10 @@ zh_TW:
multi_select:
select: '選取'
selected: '選取了 ({{count}})'
select_post:
label: '選擇'
selected_post:
label: '已選取'
delete: 刪除選取的文章
cancel: 取消選取
select_all: 選擇全部
@ -1546,7 +1611,6 @@ zh_TW:
inappropriate: "撤回投訴"
bookmark: "移除書籤"
like: "撤回讚"
vote: "撤回投票"
people:
off_topic: "投訴為離題內容"
spam: "投訴為垃圾內容"
@ -1555,7 +1619,6 @@ zh_TW:
notify_user: "已送出一則訊息"
bookmark: "收藏"
like: "讚了它"
vote: "已投票"
by_you:
off_topic: "你已投訴此文章偏離討論話題"
spam: "你已投訴此文章為垃圾"
@ -1564,7 +1627,6 @@ zh_TW:
notify_user: "您已送出訊息給這位用戶"
bookmark: "你已將此文章加上書籤"
like: "你已在此文章按讚"
vote: "你已在此文章投票支持"
by_you_and_others:
off_topic:
other: "你與其他 {{count}} 人已投訴此文章為離題內容"
@ -1580,8 +1642,6 @@ zh_TW:
other: "你與 {{count}} 個人將此文章加上書籤"
like:
other: "你與其他 {{count}} 人對此按讚"
vote:
other: "你與其他 {{count}} 人已投票給此文章"
by_others:
off_topic:
other: "{{count}} 人已投訴此文章為離題內容"
@ -1597,8 +1657,6 @@ zh_TW:
other: "{{count}} 個人將此文章加上書籤"
like:
other: "{{count}} 人對此按讚"
vote:
other: "{{count}} 人已投票給此文章"
revisions:
controls:
first: "第一版"
@ -2084,6 +2142,8 @@ zh_TW:
start_date: "開始日期"
end_date: "結束日期"
groups: "所有群組"
trending_search:
more: '<a href="/admin/logs/search_logs"> 搜尋記錄檔 </a>'
commits:
latest_changes: "最近的變更:請經常更新!"
by: "由"
@ -2091,6 +2151,10 @@ zh_TW:
title: "投訴"
agree: "同意"
agree_title: "確認此投訴為有效且正確"
agree_flag_hide_post: "隱藏貼文"
agree_flag_restore_post: "同意並還原貼文"
agree_flag_suspend: "停權使用者"
agree_flag_silence: "靜音使用者"
delete: "刪除"
delete_title: "刪除此標記文章。"
delete_post_defer_flag_title: "刪除文章,如果刪除的是討論話題的第一則文章,討論話題也將一併刪除"
@ -2421,7 +2485,6 @@ zh_TW:
address_placeholder: "name@example.com"
type_placeholder: "digest, signup..."
reply_key_placeholder: "回覆金鑰"
skipped_reason_placeholder: "原因"
logs:
title: "記錄"
action: "動作"

View File

@ -39,6 +39,11 @@ de:
bad_color_scheme: "Kann Motiv nicht aktualisieren, ungültiges Farbschema"
other_error: "Etwas ist schief gelaufen beim Aktualisieren des Theme"
error_importing: "Fehler beim Klonen des git-Repository, Zugriff verboten oder Repository nicht gefunden."
errors:
component_no_user_selectable: "Theme Komponenten können nicht Benutzer auswählbar sein "
component_no_default: "Theme Komponenten können nicht Standard Theme sein"
component_no_color_scheme: "Theme Komponenten können kein Farb-Schema haben"
no_multilevels_components: "Themes mit Unter-Themes können nicht selber Unter-Themes sein"
settings_errors:
invalid_yaml: "Der YAML-Code ist ungültig."
data_type_not_a_number: "Der eingestellte Datentyp `%{name}` wird nicht unterstützt. Unterstützte Datentypen sind `integer`, `bool`, `list` und `enum`."
@ -225,6 +230,7 @@ de:
topic_not_found: "Etwas ist schief gelaufen. Wurde das Thema eventuell geschlossen oder gelöscht, während du es angeschaut hast?"
not_accepting_pms: "Entschuldige, %{username} akzeptiert gerade keine Nachrichten."
max_pm_recepients: "Sorry, du kannst eine Nachricht nur an maximal %{recipients_limit} Emfpänger senden."
pm_reached_recipients_limit: "Entschuldige, aber Du kannst nicht mehr als %{recipients_limit} Empfänger in einer Nachricht haben."
just_posted_that: "ist einer einer vor Kurzem von dir geschriebenen Nachricht zu ähnlich"
invalid_characters: "enthält ungültige Zeichen"
is_invalid: "scheint unklar, ist das ein ganzer Satz?"
@ -252,7 +258,7 @@ de:
top_daily: "Top-Themen der letzten 24 Stunden"
posts: "Letzte Beiträge"
private_posts: "Neueste Nachrichten"
group_posts: "Letzte Beiträge von %(Gruppen_name)"
group_posts: "Neueste Beiträge von %{group_name}"
group_mentions: "Neueste Nennungen von %{group_name}"
user_posts: "Neueste Beiträge von @%{username}"
user_topics: "Neueste Themen von @%{username}"
@ -474,7 +480,7 @@ de:
topic_exists:
one: "Diese Kategorie kann nicht gelöscht werden, weil sie %{count} Themen enthält. Das älteste Thema ist %{topic_link}."
other: "Diese Kategorie kann nicht gelöscht werden, weil sie %{count} Themen enthält. Das älteste Thema ist %{topic_link}."
topic_exists_no_oldest: "Diese Kategorie kann nicht gelöscht werden, weil sie #{category.topic_count} Themen enthält."
topic_exists_no_oldest: "Diese Kategorie kann nicht gelöscht werden, weil sie %{count} Themen enthält."
uncategorized_description: "Themen welche keine Kategorie benötigen oder in keine existierende Kategorie passen."
trust_levels:
newuser:
@ -602,6 +608,17 @@ de:
email_login:
invalid_token: "Entschuldige, aber der Link zum Anmelden ist zu alt. Wähle die Anmelden-Schaltfläche und nutze „Ich habe mein Passwort vergessen“, um einen neuen Link zu erhalten."
title: "E-Mail-Anmeldung"
user_auth_tokens:
devices:
android: 'Android Gerät'
linux: 'Linux Computer'
windows: 'Windows Computer'
mac: 'Mac'
iphone: 'iPhone'
ipad: 'iPad'
ipod: 'iPod'
mobile: 'Mobiles Gerät'
unknown: 'Unbekanntes Gerät'
change_email:
confirmed: "Deine E-Mail-Adresse wurde aktualisiert."
please_continue: "Weiter zu %{site_name}"
@ -675,6 +692,7 @@ de:
self: "Du hast noch keine Aktivität."
others: "Keine Aktivität."
no_bookmarks:
self: "Du hast keine Beiträge mit Lesezeichen; Lesezeichen erlauben es Dir schnell zu einem spezifischen Beitrag zu referenzieren."
others: "Keine Lesezeichen."
no_likes_given:
self: "Du hast noch keine Beiträge mit einem „Like“ markiert."
@ -682,6 +700,9 @@ de:
no_replies:
self: "Du hast auf keine Beiträge geantwortet."
others: "Keine Antworten."
no_drafts:
self: "Du hast keine Entwürfe; beginne eine Antwort in einem beliebigen Thema und es wird automatisch als neuer Entwurf gespeichert."
others: "Du hast keine Berechtigung die Entwürfe dieses Benutzers zu sehen."
topic_flag_types:
spam:
title: 'Spam'
@ -746,6 +767,7 @@ de:
default:
labels:
count: Anzahl
percent: Prozent
day: Tag
post_edits:
title: "Beitragsbearbeitungen"
@ -763,6 +785,7 @@ de:
topic_count: Erstellte Themen
post_count: Erstellte Beiträge
pm_count: Erstellte Nachrichten
revision_count: Revisionen
flags_status:
title: "Meldungsstatus"
values:
@ -990,6 +1013,7 @@ de:
poll_pop3_timeout: "Die Verbindung zum POP3-Server schlägt mit einer Zeitüberschreitung fehl. Eingehende E-Mails konnten nicht abgerufen werden. Überprüfe deine <a href='/admin/site_settings/category/email'>POP3-Einstellungen</a>."
poll_pop3_auth_error: "Die Verbindung zum POP3-Server schlägt mit einem Authentisierungsfehler fehl. Überprüfe deine <a href='/admin/site_settings/category/email'>POP3-Einstellungen</a>."
force_https_warning: "Deine Webseite verwendet SSL, aber die `<a href='/admin/site_settings/category/all_results?filter=force_https'>force_https</a>` ist in deinen Website-Einstellungen noch nicht aktiviert."
out_of_date_themes: "Updates sind zu folgenden Themes verfügbar:"
site_settings:
censored_words: "Wörter, die automatisch durch &#9632;&#9632;&#9632;&#9632; ersetzt werden"
delete_old_hidden_posts: "Automatisch alle Beiträge löschen, die länger als 30 Tage versteckt bleiben."
@ -1170,6 +1194,8 @@ de:
enable_google_oauth2_logins: "Google OAuth2-Authentifizierung aktivieren. Dies ist der momentan von Google unterstützte Authentifizierungs-Mechanismus. Benötigt Client-ID und Secret."
google_oauth2_client_id: "Client-ID deiner Google-Anwendung."
google_oauth2_client_secret: "Client-Secret deiner Google-Anwendung."
google_oauth2_prompt: "Eine optionale Leerzeichen getrennte Liste von Zeichen Werten die angeben ob der Berechtigungsserver den Benutzer zur Re-Authentifizierung auffordert. Siehe https://developers.google.com/identity/protocols/OpenIDConnect#prompt für mögliche Werte."
google_oauth2_hd: "Eine optionale Google Apps gehostete Domain zu welches das Anmelden limitiert sein wird. Siehe https://developers.google.com/identity/protocols/OpenIDConnect#hd-param für weitere Informationen."
enable_twitter_logins: "Aktiviere Twitter-Authentifizierung (benötigt twitter_consumer_key und twitter_consumer_secret)."
twitter_consumer_key: "Consumer Key für Twitter-Authentifizierung, registriert auf https://apps.twitter.com/"
twitter_consumer_secret: "Consumer Secret für Twitter-Authentifizierung, registriert auf https://apps.twitter.com"
@ -1241,8 +1267,8 @@ de:
external_system_avatars_url: "URL des externen Profilbild-Dienstes. Erlaubte Platzhalter sind {username} {first_letter} {color} {size}"
selectable_avatars_enabled: "Forciere Benutzer, ein Avatar aus der Liste auszuwählen."
selectable_avatars: "Liste von Avataren, aus der Benutzer wählen können."
default_opengraph_image_url: "URL des standardmäßigen Open-Graph-Bildes."
twitter_summary_large_image_url: "URL des Bildes, das standardmäßig in Twitter-Cards angezeigt wird (sollte mindestens 280 Pixel breit und 150 Pixel hoch sein)."
default_opengraph_image_url: "Standard-OpenGraph-Bild, das verwendet wird, wenn die Seite kein anderes passendes Bild oder Seitenlogo hat."
twitter_summary_large_image_url: "Standard-Bild für die Twitter-Zusammenfassungskarte (sollte mindestens 280px breit und 150px hoch sein)."
allow_all_attachments_for_group_messages: "Erlaube alle E-Mail-Anhänge für Gruppen-Nachrichten."
png_to_jpg_quality: "Qualität der umgewandelten JPG-Datei (1 ist die niedrigste, 99 die beste Qualität, 100 deaktiviert die Funktion)."
allow_staff_to_upload_any_file_in_pm: "Erlaube Team-Mitgliedern, in Nachrichten alle Dateien hochzuladen."
@ -1364,6 +1390,7 @@ de:
unsubscribe_via_email: "Erlaube es Benutzern eine E-Mail mit dem Betreff oder Text: \"unsubscribe\" zum Abbestellen der E-Mails zu senden."
unsubscribe_via_email_footer: "Füge einen `mailto:`-Link zum Abbestellen im Fußbereich ausgehender E-Mails hinzu"
delete_email_logs_after_days: "Lösche E-Mail Logs nach (N) Tagen. 0 um sie für immer zu behalten."
disallow_reply_by_email_after_days: "Verbiete Antworten per E-Mail nach (N) Tagen. 0 um es unbegrenzt zu lassen."
max_emails_per_day_per_user: "Maximale Zahl an E-Mails, die Benutzern gesendet werden. 0 zum Deaktivieren der Grenze."
enable_staged_users: "Erstelle automatisch vorbereitete Benutzer, wenn eingehende E-Mails verarbeitet werden."
maximum_staged_users_per_email: "Maximale Anzahl vorbereiteter Benutzer, wenn eine eingehende E-Mail bearbeitet wird."
@ -1442,6 +1469,7 @@ de:
dominating_topic_minimum_percent: "Anteil der Nachrichten eines Themas in Prozent, die ein einzelner Benutzer verfassen darf, bevor dieser Benutzer darauf hingewiesen wird, dass er dieses Thema dominiert."
disable_avatar_education_message: "Deaktiviert den Hinweis für Benutzer, dass sie ihr Profilbild ändern können"
suppress_uncategorized_badge: "Zeige kein Abzeichen für unkategorisierte Themen in der Themenliste."
header_dropdown_category_count: "Wie viele Kategorien im der Header Dropdown-Liste angezeigt werden können."
permalink_normalizations: "Diesen regulären Ausdruck anwenden, bevor Permalinks verarbeitet werden; Beispiel: /(topic.*)\\?.*/\\1 wird Query-Strings von Themen-Routen entfernen. Format: regulärer Ausdruck + String, benutze \\1 usw. um Teilausdrücke zu verwenden"
global_notice: "Zeigt allen Besuchern eine DRINGENDE NOTFALL-NACHRICHT als global sichtbares Banner an. Deaktiviert bei leerer Nachricht. (HTML ist erlaubt.)"
disable_edit_notifications: "Unterdrückt Bearbeitungshinweise durch den System-Benutzer, wenn die 'download_remote_images_to_local' Einstellung aktiviert ist."
@ -1518,6 +1546,7 @@ de:
min_trust_level_for_user_api_key: "Erforderliche Vertrauensstufe für die Generierung von Benutzer API Schlüsseln"
allowed_user_api_auth_redirects: "Erlaubte URL für die Authentifizierungs-Umleitung von Benutzer API Schlüsseln"
allowed_user_api_push_urls: "Erlaubte URL für Server-Push zur Benutzer API"
expire_user_api_keys_days: "Anzahl Tage bevor ein Benutzer API Schlüssel automatisch abläuft. (0 für niemals)"
tagging_enabled: "Schlagwörter für Themen aktivieren"
min_trust_to_create_tag: "Minimale Vertrauensstufe, um ein Schlagwort zu erstellen."
max_tags_per_topic: "Maximale Anzahl an Schlagwörtern, die einem Thema hinzugefügt werden können."
@ -1603,6 +1632,8 @@ de:
existing_topic_moderator_post:
one: "Ein Beitrag wurde in ein neues Thema verschoben: %{topic_link}"
other: "%{count} Beiträge wurden in ein neues Thema verschoben: %{topic_link}"
change_owner:
post_revision_text: "Eigentümer übertragen."
topic_statuses:
archived_enabled: "Dieses Thema ist nun archiviert. Es ist eingefroren und kann in keiner Weise mehr verändert werden."
archived_disabled: "Dieses Thema wurde aus dem Archiv geholt. Es ist nicht länger eingefroren und kann verändert werden."
@ -1688,7 +1719,16 @@ de:
second_factor_title: "Zwei-Faktor Authentifizierung"
second_factor_description: "Bitte gib den erforderlichen Authentifizierungscode aus deiner App ein:"
second_factor_backup_description: "Bitte gib einen deiner Wiederherstellungs-Codes ein:"
second_factor_backup_title: "Zwei-Faktoren Wiederherstellungs-Code"
invalid_second_factor_code: "Ungültiger Authentifizierungscode. Jeder Code kann nur einmal benutzt werden."
second_factor_toggle:
totp: "Benutze stattdessen eine Authentifizierungs-App"
backup_code: "Benutze stattdessen einen Backup Code"
admin:
email:
sent_test: "gesendet!"
sent_test_disabled: "kann nicht gesendet werden da E-Mails deaktiviert sind"
sent_test_disabled_for_non_staff: "kann nicht gesendet werden da E-Mails für Nicht-Moderatoren deaktiviert sind"
user:
deactivated: "Deaktiviert wegen zu vielen unzustellbaren E-Mails an '%{email}'."
deactivated_by_staff: "Deaktiviert vom Team"
@ -1961,6 +2001,18 @@ de:
flags_agreed_and_post_deleted:
title: "Gemeldeter Beitrag von Team entfernt"
subject_template: "Gemeldeter Beitrag von Team entfernt"
text_body_template: |
Hallo,
Dies ist eine automatische Nachricht von %{site_name} um Dich wissen zu lassen, dass Dein Beitrag entfernt wurde.
<%{base_url}%{url}>
%{flag_reason}
Dieser Beitrag war von der Gemeinschaft gemeldet worden und ein Moderationsmitglied den Beitrag entfernt.
Bitte sieh Dir unsere [Verhaltensregeln](%{base_url}/guidelines) für weitere Informationen an.
usage_tips:
text_body_template: |
Für einige schnelle Tipps, um als neuer Benutzer loszulegen, [schaue einmal diesen Blog-Beitrag an](https://blog.discourse.org/2016/12/discourse-new-user-tips-and-tricks/).
@ -2181,6 +2233,13 @@ de:
%{post_error}
Bitte versuch es erneut, wenn du das Problem beheben kannst.
email_reject_post_too_short:
title: "E-Mail Ablehnungsbeitrag zu kurz"
subject_template: "[%{email_prefix}] Email Problem -- Beitrag zu kurz"
text_body_template: |
Es tut uns leid aber Deine E-Mail Nachricht an %{destination} (betitelt mit %{former_title}) funktionierte nicht.
Um eine tiefere Konversation zu fördern sind kurze Antworten nicht erlaubt. Bitte antworte mit mindestens %{count} Zeichen? Alternativ kannst Du auch einen Beitrag per E-Mail mit "+1" beantworten.
email_reject_invalid_post_action:
title: "E-Mail abgelehnt weil fehlerhafte Beitragsaktion"
subject_template: "[%{email_prefix}] E-Mail-Problem -- Beitragsaktion ungültig"
@ -2205,20 +2264,24 @@ de:
email_reject_old_destination:
title: "E-Mail abgelehnt weil alter Empfänger"
subject_template: "[%{email_prefix}] E-Mail-Problem -- Du versuchst, auf eine alte Benachrichtigung zu antworten"
text_body_template: |
Es tut uns leid aber Deine E-Mail Nachricht an %{destination} (betitelt mit %{former_title}) funktionierte nicht.
Wir akzeptieren nur Anworten auf die Original Nachrichten für %{number_of_days} Tage. Bitte [besuche das Thema](%{short_url}) um mit der Konversation fortzufahren.
email_reject_topic_not_found:
title: "E-Mail abgelehnt weil Thema nicht gefunden"
subject_template: "[%{email_prefix}] E-Mail-Problem -- Thema nicht gefunden"
text_body_template: |
Entschuldige, aber deine E-Mail-Nachricht an %{destination} (betitelt mit %{former_title}) konnte nicht zugestellt werden.
Das Thema, auf das du geantwortet hast, gibt es nicht mehr -- vielleicht wurde es gelöscht? Wenn du glaubst, dass dies ein Irrtum ist, nimm bitte Kontakt mit einem Team-Mitglied auf.
Das Thema, auf das du geantwortet hast, gibt es nicht mehr -- vielleicht wurde es gelöscht? Wenn du glaubst, dass dies ein Irrtum ist, nimm bitte [Kontakt mit einem Team-Mitglied](%{base_url}/about) auf.
email_reject_topic_closed:
title: "E-Mail abgelehnt weil Thema geschlossen"
subject_template: "[%{email_prefix}] E-Mail-Problem -- Thema geschlossen"
text_body_template: |
Entschuldige, aber deine E-Mail-Nachricht an %{destination} (betitelt mit %{former_title}) konnte nicht zugestellt werden.
Das Thema, auf das du geantwortet hast, ist derzeit geschlossen und akzeptiert keine Antworten mehr. Wenn du glaubst, dass dies ein Irrtum ist, nimm Bitte Kontakt mit einem Team-Mitglied auf.
Das Thema, auf das du geantwortet hast, ist derzeit geschlossen und akzeptiert keine Antworten mehr. Wenn du glaubst, dass dies ein Irrtum ist, nimm bitte [Kontakt mit einem Team-Mitglied](%{base_url}/about) auf.
email_reject_auto_generated:
title: "E-Mail abgelehnt weil automatisch generierte Antwort"
subject_template: "[%{email_prefix}] E-Mail-Problem -- Antwort automatisch generiert"
@ -2242,9 +2305,24 @@ de:
Bitte stelle sicher, dass du die POP-Zugangsdaten in [den Einstellungen](%{base_url}/admin/site_settings/category/email) korrekt konfiguriert hast.
Wenn es eine Weboberfläche für das POP-E-Mail-Postfach gibt, möchtest du dich eventuell dort anmelden und die Einstellungen überprüfen.
email_revoked:
title: "E-Mail widerrufen"
subject_template: "Ist Deine E-Mail-Adresse korrekt?"
text_body_template: |
Es tut uns leid aber wir haben Probleme Dich per E-Mail zu erreichen. Unsere letzten E-Mails an Dich sind alle als unzustellbar zurück gekommen.
Bitte stelle sicher, dass [E-Mail Adresse](%{base_url}/my/preferences/email) gültig und aktiv ist? Füge bitte unsere E-Mail Adresse in Deinem Adressbuch hinzu, damit die Zustellbarkeit verbessert wird.
too_many_spam_flags:
title: "Neues Konto gesperrt wegen zu viel Spam"
subject_template: "Neues Konto gesperrt"
text_body_template: |
Hello,
Dies ist eine automatische Nachricht von %{site_name} um Dich wissen zu lassen, dass deine Beiträge temporär ausgeblendet wurden, weil diese von der Gemeinschaft gemeldet wurden.
Als Vorsichtsmaßnahme wurde Dein Konto stumm geschaltet und Du kannst nun nicht mehr antworten neue Themen erzeugen bis ein Team Mitglied Dein Konto überprüft hat. Wir bitten diese Unannehmlichkeit zu entschuldigen.
Für weitere Hinweise, schaue Dir bitte unsere [Verhaltensregeln](%{base_url}/guidelines) an.
too_many_tl3_flags:
title: "E-Mail abgelehnt wegen Meldungen durch Vertrauensstufe 3"
subject_template: "Neues Konto gesperrt"
@ -2280,7 +2358,7 @@ de:
Der Schwellenwert kann über die Einstellung `silence_new_user` geändert werden.
spam_post_blocked:
title: "Spam-Beitrag ausgeblendet"
subject_template: "Beiträge des neuen Benutzers ${username} wegen mehrfacher Verlinkung blockiert"
subject_template: "Beiträge des neuen Benutzers %{username} wegen mehrfacher Verlinkung blockiert"
text_body_template: |
Dies ist eine automatisierte Nachricht.
@ -2306,7 +2384,7 @@ de:
text_body_template: |
Es warten neuen Benutzer auf ihre Freigabe.
[Bitte bewerte diese im Administrationsbereich](/admin/users/list/pending).
[Bitte überprüfe diese im Administrationsbereich](%{base_url}/admin/users/list/pending).
download_remote_images_disabled:
title: "Download von externen Bildern deaktiviert"
subject_template: "Download von externen Bildern deaktiviert"
@ -2558,7 +2636,7 @@ de:
title: "Konto stummgeschaltet"
subject_template: "[%{email_prefix}] Dein Konto wurde stummgeschaltet"
text_body_template: |
Du wurdest im Forum stummgeschaltet bis %{suspended_till}.
Du wurdest im Forum stummgeschaltet bis %{silenced_till}.
%{reason}
@ -2678,16 +2756,16 @@ de:
%{new_email}
signup_after_approval:
title: "Konto bestätigen nach Genehmigung"
subject_template: "You've been approved on %{site_name}!"
subject_template: "Dein Konto bei %{site_name} wurde genehmigt!"
text_body_template: |
Willkommen bei%{site_name}!
Ein Team-Mitglied hat dein Benutzerkonto auf %{site_name} bestätigt.
Ein Team-Mitglied hat dein Benutzerkonto auf %{site_name} genehmigt.
Du kannst dein neues Konto nun verwenden, indem du dich hier anmeldest:
%{base_url}/u/activate-account/%{email_token}
%{base_url}
Wenn sich der obenstehende Link nicht anklicken lässt, versuche ihn zu kopieren und in die Adresszeile deines Webbrowsers einzufügen.
Wenn sich der obenstehende Link nicht anklicken lässt, versuche ihn zu kopieren und in die Adresszeile deines Browsers einzufügen.
%{new_user_tips}
@ -2710,6 +2788,7 @@ de:
recent_topics: "Aktuell"
see_more: "Mehr"
search_title: "Diese Site durchsuchen"
search_button: "Suche"
offline:
title: "App kann nicht geladen werden"
offline_page_message: "Sieht so aus als wärst du offline! Bitte überprüfe deine Netzwerkverbindung und probiere es nochmal."
@ -2761,6 +2840,8 @@ de:
sender_message_blank: "Nachricht ist leer"
sender_message_to_blank: "message.to ist leer"
sender_text_part_body_blank: "text_part.body ist leer"
sender_body_blank: "Textkörper ist leer"
sender_post_deleted: "Beitrag wurde gelöscht"
color_schemes:
base_theme_name: "Basis"
light: "Helles Schema"
@ -2892,7 +2973,7 @@ de:
tos_topic:
title: "Nutzungsbedingungen"
body: |
SiteDie folgenden Geschäftsbedingungen sind maßgebend für die gesamte Webseite und alle Inhalte, Dienstleistungen und Produkte, die auf oder über die Webseite zur Verfügung gestellt werden, einschließlich, aber nicht beschränkt auf die %{company_domain} Foren-Software, die %{company_domain} Support-Foren und den %{company_domain} Hosting-Dienst („Hosting”), (zusammengefasst: die „Webseite“). Die Webseite gehört und wird betrieben von („“). Die Website wird angeboten vorbehaltlich der uneingeschränkten Zustimmung aller hierin enthaltenen Bedingungen und aller anderen sonstigen betrieblichen Regeln, Richtlinien (einschließlich, ohne Einschränkung, der [Datenschutzrichtlinien](/privacy) and [Community-Richtlinien](/faq)) von %{company_domain} und Verfahren, die von Zeit zu Zeit auf dieser Webseite von %{company_name} veröffentlicht werden können (zusammen die „Vereinbarung“).
Die folgenden Geschäftsbedingungen sind maßgebend für die gesamte Webseite und alle Inhalte, Dienstleistungen und Produkte, die auf oder über die Webseite zur Verfügung gestellt werden, einschließlich, aber nicht beschränkt auf die %{company_domain} Foren-Software, die %{company_domain} Support-Foren und den %{company_domain} Hosting-Dienst („Hosting”), (zusammengefasst: die „Webseite“). Die Webseite gehört und wird betrieben von %{company_full_name} („%{company_name}“). Die Website wird angeboten vorbehaltlich der uneingeschränkten Zustimmung aller hierin enthaltenen Bedingungen und aller anderen sonstigen betrieblichen Regeln, Richtlinien (einschließlich, ohne Einschränkung, der [Datenschutzrichtlinien](/privacy) and [Community-Richtlinien](/faq)) von %{company_domain} und Verfahren, die von Zeit zu Zeit auf dieser Webseite von veröffentlicht werden können (zusammen die „Vereinbarung“).
Bitte lies diese Vereinbarung sorgfältig durch, bevor du die Webseite verwendest oder darauf zugreifst. Durch die Benutzung oder den Zugriff auf einen Teil der Webseite stimmst du den Geschäftsbedingungen dieser Vereinbarung zu. Wenn du nicht allen Geschäftsbedingungen in dieser Vereinbarung zustimmst, dann darfst du weder auf die Seite zugreifen noch irgendwelche Dienstleistungen in Anspruch nehmen. Wenn diese Geschäftsbedingungen als Angebot von %{company_name} erachtet werden, beschränkt sich die Zustimmung ausdrücklich auf diese Bedingungen. Die Website wird nur für Benutzer angeboten, die mindestens 13 Jahre alt sind.

View File

@ -698,6 +698,18 @@ en:
invalid_token: "Sorry, that email login link is too old. Select the Log In button and use 'I forgot my password' to get a new link."
title: "Email login"
user_auth_tokens:
devices:
android: 'Android Device'
linux: 'Linux Computer'
windows: 'Windows Computer'
mac: 'Mac'
iphone: 'iPhone'
ipad: 'iPad'
ipod: 'iPod'
mobile: 'Mobile Device'
unknown: 'Unknown device'
change_email:
confirmed: "Your email has been updated."
please_continue: "Continue to %{site_name}"
@ -1398,8 +1410,8 @@ en:
selectable_avatars_enabled: "Force users to choose an avatar from the list."
selectable_avatars: "List of avatars users can choose from."
default_opengraph_image_url: "URL of the default opengraph image."
twitter_summary_large_image_url: "URL of the default Twitter summary card image (should be at least 280px in width, and at least 150px in height)."
default_opengraph_image_url: "Default opengraph image, used when the page has no other suitable image or site logo."
twitter_summary_large_image_url: "Default Twitter summary card image (should be at least 280px in width, and at least 150px in height)."
allow_all_attachments_for_group_messages: "Allow all email attachments for group messages."

View File

@ -42,6 +42,7 @@ es:
errors:
component_no_user_selectable: "Los componentes de tema no pueden ser seleccionables por los usuarios"
component_no_default: "Los componentes de temas no pueden ser el tema por defecto"
component_no_color_scheme: "Los componentes de tema no pueden tener esquemas de color"
no_multilevels_components: "Loa temas con temas hijos no pueden ser hijos de otros temas"
settings_errors:
invalid_yaml: "YAML provisto es inválido."
@ -627,6 +628,17 @@ es:
email_login:
invalid_token: "Lo sentimos, ese enlace de inicio de sesión de correo electrónico es demasiado viejo. Seleccione el botón Iniciar sesión y use 'Olvidé mi contraseña' para obtener un nuevo enlace."
title: "Email login"
user_auth_tokens:
devices:
android: 'Dispositivo Android'
linux: 'Ordenador Linux'
windows: 'Ordenador Windows'
mac: 'Mac'
iphone: 'iPhone'
ipad: 'iPad'
ipod: 'iPod'
mobile: 'Dispositivo móvil'
unknown: 'Dispositivo desconocido'
change_email:
confirmed: "Tu email ha sido actualizado."
please_continue: "Continuar a %{site_name}"
@ -1202,6 +1214,8 @@ es:
enable_google_oauth2_logins: "Habilita la autenticación con Google Oauth2. Este es el método de autenticación a la que Google da soporte actualmente. Requiere de una clave de cliente y una clave secreta."
google_oauth2_client_id: "Client ID de tu aplicación Google."
google_oauth2_client_secret: "Client secret de tu aplicación Google."
google_oauth2_prompt: "Una lista opcional delimitada por espacios de valores de cadena que especifica si el servidor de autorizaciones solicita al usuario la reautenticación y el consentimiento. Consulte https://developers.google.com/identity/protocols/OpenIDConnect#prompt para conocer los valores posibles."
google_oauth2_hd: "Un dominio opcional hospedado de Google Apps al que se limitará el inicio de sesión. Consulte https://developers.google.com/identity/protocols/OpenIDConnect#hd-param para más detalles."
enable_twitter_logins: "Activar autenticación por Twitter, requiere una twitter_consumer_key y un twitter_consumer_secret"
twitter_consumer_key: "Clave del consumidor para la autenticación de Twitter, registrado en https://apps.twitter.com/"
twitter_consumer_secret: "Secreto del consumidor para la autenticación de Twitter, registrado en https://apps.twitter.com/"
@ -1222,6 +1236,8 @@ es:
backup_frequency: "El número de días entre backups."
enable_s3_backups: "Sube copias de seguridad a S3 cuando complete. IMPORTANTE: requiere credenciales validas de S3 puestas Archivos configuración."
s3_backup_bucket: "El bucket remoto para mantener copias de seguridad. AVISO: Asegúrate de que es un bucket privado."
s3_endpoint: "El punto final se puede modificar para realizar una copia de seguridad en un servicio compatible con S3 como DigitalOcean Spaces o Minio. ADVERTENCIA: utilice el valor predeterminado si usa AWS S3"
s3_force_path_style: "Haga cumplir el direccionamiento de estilo de ruta para su punto final personalizado. IMPORTANTE: se requiere para usar cargas y copias de seguridad desde Minio."
s3_disable_cleanup: "Desactivar el eliminado de backups de S3 cuando se eliminen de forma local."
backup_time_of_day: "Hora UTC del día cuando debería ejecutarse el backup."
backup_with_uploads: "Incluir archivos adjuntos en los backups programados. Desactivando esta opción tan solo se ejecutará una copia de seguridad de la base de datos."
@ -1271,8 +1287,8 @@ es:
external_system_avatars_url: "Dirección URL del servicio externo para los avatares. Sustituciones permitidas: {username} {first_letter} {color} {size}"
selectable_avatars_enabled: "Forzar a los usuarios a elegir una foto de perfil de la lista."
selectable_avatars: "Lista de fotos de perfil de la cual los usuarios pueden elegir."
default_opengraph_image_url: "URL de la imagen opengraph por defecto."
twitter_summary_large_image_url: "URL de la imagen por defecto para la tarjeta resumen de Twitter (debería ser al menos de 280px de ancho y 150 px de alto)."
default_opengraph_image_url: "URL de la imagen opengraph por defecto, utilizado cuando la página no tiene otra imagen o logotipo del sitio adecuado."
twitter_summary_large_image_url: "URL de la imagen por defecto para la tarjeta resumen de Twitter (debería ser al menos de 280px de ancho y 150px de alto)."
allow_all_attachments_for_group_messages: "Permitir todos los archivos adjuntos de email para los mensajes a grupos."
png_to_jpg_quality: "Calidad del archivo JPG convertido (1 es calidad mínima, 99 es máxima calidad, 100 para deshabilitar conversión)."
allow_staff_to_upload_any_file_in_pm: "Permitir a los miembros del staff subir cualquier archivo en MP."
@ -1394,6 +1410,7 @@ es:
unsubscribe_via_email: "Permitir a los usuarios darse de baja de los emails respondiendo con el texto 'unsubscribe' en el asunto o el cuerpo del mensaje"
unsubscribe_via_email_footer: "Adjuntar un enlace para darse de baja al pie de los emails enviados"
delete_email_logs_after_days: "Eliminar logs de email después de (N) días. Si es 0 permanecerán de forma indefinida."
disallow_reply_by_email_after_days: "Deshabilitar la respuesta por email después de (N) días. Si es 0 quedará de forma indefinida."
max_emails_per_day_per_user: "Máximo número de emails a enviar a los usuarios por día. Establece 0 para desactivar el límite"
enable_staged_users: "Crear cuentas provisionales automáticamente al procesar emails entrantes."
maximum_staged_users_per_email: "Máximo número de usuarios provisionales creados al procesar un email entrante."
@ -1724,6 +1741,10 @@ es:
second_factor_toggle:
totp: "Usar una aplicación de verificación en su lugar"
backup_code: "Usar un código de respaldo en su lugar"
admin:
email:
sent_test: "¡enviado!"
sent_test_disabled: "no se ha podido enviar el email porque están desactivados"
user:
deactivated: "Ha sido desactivado a causa de muchos rebotes al email '%{email}'."
deactivated_by_staff: "Desactivado por el staff"
@ -2294,6 +2315,13 @@ es:
Si dispones de una interfaz de usuario web para la cuenta de correo POP, es posible que debas iniciar sesión allí y comprobar su configuración.
email_revoked:
title: "Email revocado"
subject_template: "¿Es tu correo electrónico correcto?"
text_body_template: |
Lo sentimos, pero estamos teniendo problemas para contactarte por correo. Nuestros últimos mensajes nos han sido devueltos como imposibles de entregar.
¿Podrías comprobar que [tu dirección de email](%{base_url}/my/preferences/email) es válida y funciona? También puedes añadir nuestra dirección a tu agenda para mejorar la entregabilidad.
too_many_spam_flags:
title: "Demasiadas banderas por Spam"
subject_template: "Nueva cuenta retenida"

View File

@ -42,6 +42,7 @@ fr:
errors:
component_no_user_selectable: "Les composants du thème ne peuvent pas être sélectionnés par les utilisateurs"
component_no_default: "Les composants du thème ne peuvent pas être un thème par défaut"
component_no_color_scheme: "Les composants de thème ne peuvent pas avoir de palette de couleur"
no_multilevels_components: "Les thèmes incluant d'autres thèmes ne peuvent pas être inclus eux-mêmes"
settings_errors:
invalid_yaml: "Le YAML est invalide"
@ -229,6 +230,7 @@ fr:
topic_not_found: "Une erreur est survenue. Peut-être que ce sujet a été fermé ou supprimé pendant que vous le regardiez ?"
not_accepting_pms: "Désolé, %{username} n'accepte pas de messages pour le moment."
max_pm_recepients: "Désolé, vous pouvez envoyer un message à un maximum de %{recipients_limit} destinataires."
pm_reached_recipients_limit: "Désolé, vous ne pouvez pas avoir plus de %{recipients_limit} destinataires dans un message."
just_posted_that: "est trop similaire à ce que vous avez récemment posté"
invalid_characters: "contient des caractères invalides"
is_invalid: "ne semble pas clair, est-ce une phrase complète ?"
@ -458,6 +460,27 @@ fr:
Vous voulez peut-être fermer ce sujet via l'administration :wrench: (dans le coin supérieur droit et le bas) afin que des réponses ne s'accumulent pas après une annonce.
lounge_welcome:
title: "Bienvenue dans le salon"
body: |2
Félicitations ! :confetti_ball:
Si vous voyez ce sujet, vous avez été promu **habitué** (niveau de confiance 3).
Dorénavant vous pouvez &hellip;
* Modifier le titre de n'importe quel sujet
* Modifier la catégorie de n'importe quel sujet
* Avoir des liens qui sont suivis ([automatic nofollow](http://fr.wikipedia.org/wiki/Nofollow) est ôté)
* Accéder à la catégorie privée Salon qui est réservée aux utilisateurs de niveau de confiance 3 et plus
* Cacher du spam avec un seul signalement
Voilà la [liste des habitués actuels](/badges/3/regular). N'hésitez pas à dire bonjour.
Merci pour votre contribution à notre communauté !
(Pour plus d'informations sur les niveaux de confiance, [voir ce sujet][trust]. Notez que seul les utilisateurs qui continuent de remplir les conditions gardent leur statut d'habitués.)
[trust]: https://blog.discourse.org/2018/06/understanding-discourse-trust-levels/
category:
topic_prefix: "À propos de la catégorie %{category}"
replace_paragraph: "(Remplacez ce premier paragraphe par une brève description de votre nouvelle catégorie. Ce guide apparaîtra dans la zone de sélection de la catégorie, alors essayez de rester en dessous de 200 caractères. **Cette catégorie n'apparaîtra pas sur la page des catégories jusqu'à ce que vous ayez modifié cette description ou créé des sujets.**"
@ -474,6 +497,9 @@ fr:
cannot_delete:
uncategorized: "Vous ne pouvez pas supprimer Sans Catégorie"
has_subcategories: "Vous ne pouvez pas supprimer cette catégorie car elle a des sous-catégories."
topic_exists:
one: "Vous ne pouvez pas supprimer cette catégorie car elle contient 1 sujet. Le plus vieux sujet est %{topic_link}."
other: "Vous ne pouvez pas supprimer cette catégorie car elle contient %{count} sujets. Le plus vieux sujet est %{topic_link}."
topic_exists_no_oldest: "Vous ne pouvez pas supprimer cette catégorie car le nombre de sujet est de %{count}."
uncategorized_description: "Sujets qui n'ont pas besoin d'une catégorie ou qui ne correspondent à aucune catégorie existante."
trust_levels:
@ -602,6 +628,17 @@ fr:
email_login:
invalid_token: "Désolé, ce lien de connexion courriel est trop vieux. Séléctionner le bouton 'Se connecter' et utiliser 'Mot de passe oublié' pour obtenir un nouveau lien."
title: "Connexion courriel"
user_auth_tokens:
devices:
android: 'Appareil Android'
linux: 'Ordinateur Linux'
windows: 'Ordinateur Windows'
mac: 'Mac'
iphone: 'iPhone'
ipad: 'iPad'
ipod: 'iPod'
mobile: 'Appareil portable'
unknown: 'Appareil inconnu'
change_email:
confirmed: "Votre adresse de courriel a été mise à jour."
please_continue: "Continuer vers %{site_name}"
@ -611,6 +648,8 @@ fr:
authorizing_old:
title: "Merci d'avoir confirmé votre adresse de courriel"
description: "Nous envoyons un courriel sur votre nouvelle adresse pour confirmation."
associated_accounts:
revoke_failed: "Echec de révocation de votre compte avec %{provider_name}."
activation:
action: "Cliquer ici pour activer votre compte"
already_done: "Désolé, ce lien de confirmation n'est plus valide. Votre compte est peut-être déjà activé ?"
@ -673,6 +712,7 @@ fr:
self: "Vous n'avez aucune activité pour le moment."
others: "Aucune activité."
no_bookmarks:
self: "Vous n'avez pas de sujet dans vos signets; les signets vous permettent de retrouver rapidement des sujets d'intérêt."
others: "Aucun signet."
no_likes_given:
self: "Vous n'avez aimé aucun message"
@ -680,19 +720,25 @@ fr:
no_replies:
self: "Vous n'avez répondu à aucun message."
others: "Aucune réponse."
no_drafts:
self: "Vous n'avez pas d'ébauche; commencez à répondre à un sujet et la réponse sera automatiquement sauvegardée comme nouvelle ébauche."
others: "Vous n'avez pas la permission de consulter les ébauches de cet utilisateur."
topic_flag_types:
spam:
title: 'Spam'
description: 'Ce message est une publicité. Il n''est pas utile ou pertinent pour ce site, mais de nature promotionnelle.'
long_form: 'signalé comme spam'
short_description: 'Ceci est une publicité'
inappropriate:
title: 'Inapproprié'
description: 'Ce message contient du contenu qu''une personne raisonnable jugerait offensant, abusif ou en violation de la <a href="/guidelines">charte de notre communauté</a>.'
long_form: 'signalé comme inapproprié'
short_description: 'Une transgression de <a href="/guidelines">notre charte communautaire'
notify_moderators:
title: "Autre chose"
description: 'Ce sujet demande l''attention des responsables d''après la <a href=''/fguidelines''>charte de la communauté</a>, <a href=''%{tos_url}''>les conditions générales de service</a> ou pour une autre raison.'
long_form: 'signalé pour modération'
short_description: 'Nécessite l''attention du staff pour une autre raison'
email_title: 'Le sujet « %{title} » nécessite l''attention d''un modérateur'
email_body: "%{link}\n\n%{message}"
flagging:
@ -1011,6 +1057,7 @@ fr:
search_recent_posts_size: "Combien de messages récents à garder dans l'index"
log_search_queries: "Archiver les requêtes de recherche des utilisateurs"
search_query_log_max_size: "Nombre maximum de requêtes de recherche à conserver"
search_query_log_max_retention_days: "Durée maximum de conservation de requêtes de recherche"
allow_uncategorized_topics: "Autorise la création de sujets sans catégorie. ATTENTION : S'il existe des sujets non-catégorisés, vous devez les catégoriser avant de désactiver cette fonction."
allow_duplicate_topic_titles: "Autoriser la création de sujet avec le même titre."
unique_posts_mins: "Combien de temps avant qu'un utilisateur puisse poster le même contenu à nouveau"
@ -1112,6 +1159,7 @@ fr:
share_links: "Choix des éléments qui doivent apparaître dans la fenêtre de partage, et leur ordre."
site_contact_username: "Un pseudo de responsable valide pour envoyer tous les message automatiques. Si laissé vide, le compte système sera utilisé."
send_welcome_message: "Envoyer à tous les nouveaux utilisateurs un message de bienvenue avec un guide de démarrage rapide."
send_tl1_welcome_message: "Envoyer à tous les utilisateurs de niveau de confiance 1 un message de bienvenue."
suppress_reply_directly_below: "Ne pas afficher le panneau extensible des réponses d'un message quand la seule réponse est juste en dessous ce dernier."
suppress_reply_directly_above: "Ne pas afficher 'en réponse à' sur un message quand la seule réponse est juste en dessus de ce dernier."
suppress_reply_when_quoting: "Ne pas affiché le panneau \"En réponse à\" sur un message qui répond à une citation."
@ -1166,6 +1214,8 @@ fr:
enable_google_oauth2_logins: "Activer l'authentification Google Oauth2. C'est la méthode d'authentification que Google supporte désormais. Nécessite une clé et une phrase secrète."
google_oauth2_client_id: "Identifiante du client de votre application Google."
google_oauth2_client_secret: "Clé secrète du client de votre application Google."
google_oauth2_prompt: "Liste facultative de valeurs de chaînes de caractères délimitées par des espaces, qui spécifie si le serveur d'autorisation invite l'utilisateur à se réauthentifier et à donner son consentement. Voir https://developers.google.com/identity/protocols/OpenIDConnect#prompt pour des valeurs possibles."
google_oauth2_hd: "Un domaine optionnel Google Apps Hosted auquel la connexion sera limitée. Voir https://developers.google.com/identity/protocols/OpenIDConnect#hd-param pour plus de détails."
enable_twitter_logins: "Activer l'authentification Twitter, nécessite twitter_consumer_key et twitter_consumer_secret"
twitter_consumer_key: "Clé consommateur pour l'authentification Twitter, enregistrée sur https://apps.twitter.com/"
twitter_consumer_secret: "Secret consommateur pour l'authentification Twitter, enregistré sur http://dev.twitter.com"
@ -1186,6 +1236,8 @@ fr:
backup_frequency: "Nombre de jours entre sauvegardes"
enable_s3_backups: "Envoyer vos sauvegardes à S3 lorsqu'elles sont terminées. IMPORTANT : Vous devez avoir renseigné vos identifiants S3 dans les paramètres de fichiers."
s3_backup_bucket: "Bucket distant qui contiendra les sauvegardes. ATTENTION: Vérifiez que c'est un bucket privé"
s3_endpoint: "Le terminal peut être modifié pour être sauvegardé sur un service compatible S3 comme DigitalOcean Spaces ou Minio. ATTENTION: Utiliser défaut si vous utilisez AWS S3"
s3_force_path_style: "Appliquez l'adressage de type chemin pour votre terminal personnalisé. IMPORTANT : Nécessaire pour utiliser les téléchargements et sauvegardes Minio."
s3_disable_cleanup: "Désactiver la suppression des sauvegardes de S3 lors de leur suppression locale."
backup_time_of_day: "Heure (UTC) de planification de la sauvegarde."
backup_with_uploads: "Inclure les fichiers envoyés dans les sauvegardes. Si désactivé, seule la base de données sera sauvegardée."
@ -1211,6 +1263,8 @@ fr:
max_topic_invitations_per_day: "Nombre maximum d'invitations à un sujet qu'un utilisateur peut envoyer par jour."
max_logins_per_ip_per_hour: "Nombre maximum de connexions autorisées par adresse IP et par heure"
max_logins_per_ip_per_minute: "Nombre maximum de connexions autorisées par adresse IP, par minute"
max_post_deletions_per_minute: "Nombre maximum de sujet qu'un utilisateur peut supprimer par minute."
max_post_deletions_per_day: "Nombre maximum de sujet qu'un utilisateur peut supprimer par jour."
alert_admins_if_errors_per_minute: "Nombre d'erreurs par minute nécessaires pour déclencher une alerte administrateur. Une valeur de 0 désactive cette fonctionnalité. N. B. : nécessite un redémarrage."
alert_admins_if_errors_per_hour: "Nombre d'erreurs par heure nécessaires pour déclencher une alerte administrateur. Une valeur de 0 désactive cette fonctionnalité. N. B. : nécessite un redémarrage."
categories_topics: "Nombre de sujets à afficher dans la page /categories."
@ -1231,8 +1285,10 @@ fr:
avatar_sizes: "Liste des tailles des avatars automatiquement générés"
external_system_avatars_enabled: "Utilisez un service d'avatars externe."
external_system_avatars_url: "URL du service d'avatars externe. Les remplacements autorisés sont {username} {first_letter} {color} {size}"
default_opengraph_image_url: "URL de l'image par défaut pour les balises Open Graph."
twitter_summary_large_image_url: "URL de l'image par défaut de la carte de résumé Twitter (devrait au moins mesurer 280px en largeur et 150px en hauteur). "
selectable_avatars_enabled: "Obliger les utilisateurs à choisir un avatar de la liste."
selectable_avatars: "Liste d'avatars parmi lesquels les utilisateurs peuvent choisir."
default_opengraph_image_url: "Image opengraph par défaut, utilisée quand la page n'a pas d'autre image or logo de site approprié."
twitter_summary_large_image_url: "Image par défaut de la carte de résumé Twitter (devrait au moins mesurer 280px en largeur et 150px en hauteur). "
allow_all_attachments_for_group_messages: "Autorise toutes les pièces-jointes pour les messages de groupes."
png_to_jpg_quality: "Qualité du fichier JPEG converti (1 est la plus faible, 99 est la meilleure, 100 pour désactiver)."
allow_staff_to_upload_any_file_in_pm: "Autoriser les responsables à envoyer n'importe quel fichier dans les messages directs."
@ -1354,6 +1410,7 @@ fr:
unsubscribe_via_email: "Autorise les utilisateurs à se désinscrire des courriels en envoyant un courriel avec \"unsubscribe\" dans le sujet ou le corps du message."
unsubscribe_via_email_footer: "Inclure un lien dans le pied des courriels envoyés pour se désabonner"
delete_email_logs_after_days: "Efface les journaux de messagerie après (N) jours. 0 pour conserver indéfiniment."
disallow_reply_by_email_after_days: "Interdire la réponse par courriel après (N) jours. 0 pour permettre indéfiniment."
max_emails_per_day_per_user: "Nombre maximum de courriels à envoyer aux utilisateurs par jour. 0 pour désactiver la limite"
enable_staged_users: "Créer automatiquement les utilisateurs distants lors du traitement des courriels entrants."
maximum_staged_users_per_email: "Nombre maximum d'utilisateurs distants créés lors du traitement d'un courriel entrant."
@ -1380,9 +1437,11 @@ fr:
pop3_polling_host: "L'hôte pour le pooling des courriels via POP3."
pop3_polling_username: "Le nom d'utilisateur du compte POP3 pour le pooling des courriels."
pop3_polling_password: "Le mot de passe du compte POP3 pour le pooling des courriels."
pop3_polling_delete_from_server: "Courriels supprimés du serveur. NOTE: Si vous désactivez ceci vous devrez vider manuellement votre boîte de réception courriel."
log_mail_processing_failures: "Enregistrer tous les problèmes de mails sur\nhttp://yoursitename.com/logs"
email_in: "Autoriser les utilisateurs à poster de nouveaux sujets par courriel (requêtes manuelles ou via pop3 requises). Configurer les adresses dans l'onglet \"Paramètres\" de chaque catégorie."
email_in_min_trust: "Le niveau de confiance minimum qu'un utilisateur doit avoir pour être autorisé à poster de nouveaux sujets par courriel."
email_in_spam_header: "Entête courriel pour détecter du spam."
email_prefix: "Le [label] qui sera utilisé dans le sujet des courriels. Par défaut il prend la valeur de 'title'."
email_site_title: "Le titre du site utilisé comme expéditeur pour les courriels du site. Par défaut il prend la valeur de 'title'. Si votre 'title\" utilise des caractères interdits dans les courriels, utiliser ce paramètre."
find_related_post_with_key: "N'utilisez que le 'reply key' pour trouver le message auquel on a répondu. ATTENTION : la désactivation de cette option permet l'usurpation d'identité de l'utilisateur sur la base de l'adresse e-mail."
@ -1430,6 +1489,7 @@ fr:
dominating_topic_minimum_percent: "Quel est le pourcentage de messages un utilisateur doit poster dans un sujet avant d'être rappelé à l'ordre pour laissé la communauté répondre."
disable_avatar_education_message: "Désactiver le message incitant à changer l'avatar."
suppress_uncategorized_badge: "Ne pas afficher le badge pour les sujets non catégorisés dans les listes des sujets."
header_dropdown_category_count: "Nombre de catégories qui peuvent être affiché dans le menu déroulant d'en-tête."
permalink_normalizations: "Appliquer l'expression régulière suivante avant de détecter les permaliens, par exemple /(\\/topic.*)\\?.*/\\1 supprimera les chaînes de requête des chemins de sujet. Le format est regex+string, utilisez \\1 etc. pour capturer des séquences"
global_notice: "Affiche un bandeau global URGENT pour tout les utilisateurs du site, laissez vide pour le cacher (HTML autorisé)."
disable_edit_notifications: "Désactiver les notifications de modifications par l'utilisateur système lorsque l'option 'download_remote_images_to_local' est activée."
@ -1464,6 +1524,7 @@ fr:
enable_emoji_shortcuts: "Les textes de smiley courants :) :p :( seront convertis en emojis"
emoji_set: "Comment aimeriez-vous vos emoji ?"
enforce_square_emoji: "Forcer tous les Emojis à être carrés."
emoji_autocomplete_min_chars: "Nombre minimum de caractères nécessaires pour invoquer la fenêtre contextuelle d'emoji"
approve_post_count: "Le nombre de messages d'un utilisateur nouveau ou basique devant être approuvés"
approve_unless_trust_level: "Les messages des utilisateurs qui n'ont pas atteint ce niveau de confiance doivent être approuvés"
approve_new_topics_unless_trust_level: "Les nouveaux sujets des utilisateurs en dessous de ce niveau de confiance doivent être approuvés"
@ -1498,12 +1559,14 @@ fr:
default_categories_muted: "Liste de catégories silencées par défaut."
default_categories_watching_first_post: "Liste des catégories dont le premier message de chaque nouveau sujet sera surveillé par défaut."
retain_web_hook_events_period_days: "Nombre de jours de conservation des événement des Web hooks."
retry_web_hook_events: "Réessayez automatiquement 4 fois les événements de hook web qui ont échoué. Les intervalles entre les tentatives sont de 1, 5, 25 et 125 minutes."
allow_user_api_keys: "Autoriser la génération des clés de l'API utilisateur"
allow_user_api_key_scopes: "Liste des champs d'action autorisés pour les clés de l'API utilisateur"
max_api_keys_per_user: "Nombre maximum de clés de l'API utilisateur par utilisateur"
min_trust_level_for_user_api_key: "Niveau de confiance requis pour générer des clés pour l'API utilisateur"
allowed_user_api_auth_redirects: "URL autorisées pour la redirection d'authentification pour les clés de l'API utilisateur"
allowed_user_api_push_urls: "URL autorisées pour le service push du serveur vers l'API utilisateur"
expire_user_api_keys_days: "Nombre de jours avant l'expiration automatique d'une clé API utilisateur (0 pour jamais)"
tagging_enabled: "Activer les tags sur les sujets ?"
min_trust_to_create_tag: "Le niveau de confiance minimum requis pour créer un tag."
max_tags_per_topic: "Le nombre maximum de tags pouvant être ajoutés à un sujet."
@ -1589,6 +1652,8 @@ fr:
existing_topic_moderator_post:
one: "Un message a été intégré dans un sujet existant : %{topic_link}"
other: "%{count} messages ont été intégrés dans un sujet existant : %{topic_link}"
change_owner:
post_revision_text: "Propriété transférée"
topic_statuses:
archived_enabled: "Ce sujet est maintenant archivé. Il est gelé et ne peut plus être modifié d'aucune façon."
archived_disabled: "Ce sujet est maintenant désarchivé. Il n'est plus figé et peut être modifié."
@ -1673,6 +1738,17 @@ fr:
already_logged_in: "Oups, on dirait que vous essayez d'accepter une invitation d'un autre utilisateur. Si vous n'êtes pas %{current_user}, veuillez vous déconnecter et réessayer."
second_factor_title: "Authentification à deux facteurs"
second_factor_description: "Veuillez saisir le code d'authentification requise de votre app :"
second_factor_backup_description: "Veuillez entrer un de vos codes de secours :"
second_factor_backup_title: "Code de secours de l'authentification à deux étapes"
invalid_second_factor_code: "Code d'authentification invalide. Chaque code ne peut être utilisé qu'une fois."
second_factor_toggle:
totp: "Utilisez plutôt une application d'authentification"
backup_code: "Utilisez plutôt un code de secours"
admin:
email:
sent_test: "envoyé !"
sent_test_disabled: "impossible d'envoyer, les courriels sont désactivés"
sent_test_disabled_for_non_staff: "impossible d'envoyer, les courriels sont désactivés sauf pour les responsables"
user:
deactivated: "A été désactivé à cause de trop de courriels rejetés vers '%{email}'."
deactivated_by_staff: "Désactivé par un responsable"
@ -1946,6 +2022,27 @@ fr:
Pour plus d'informations, merci de vous référer à notre [charte communautaire](%{base_url}/guidelines).
flags_agreed_and_post_deleted:
title: "Message signalé, retiré par un responsable"
subject_template: "Message signalé, retiré par un responsable"
text_body_template: |+
Bonjour,
Ceci est un message automatique de %{site_name} pour vous informer que votre message a été caché.
<%{base_url}%{url} >
%{flag_reason}
Les message a été signalé par la communauté et un responsable a décidé de le retirer.
Pour plus d'informations, merci de vous référer à notre [charte communautaire](%{base_url}/guidelines).
usage_tips:
text_body_template: |
Pour connaître quelques astuces pour vous aider à démarrer en tant que nouveau membre, [rendez-vous sur cette page](https://meta.discourse.org/t/fr-trucs-et-astuces-pour-nouveaux-utilisateurs/84100/).
Au fur et à mesure que vous participerez ici, nous apprendrons à vous connaître et les limitations temporaires des nouveaux utilisateurs seront levées. Avec le temps, vous gagnerez [des niveaux de confiance](https://blog.discourse.org/2018/06/understanding-discourse-trust-levels/) (en anglais) qui incluent des fonctionnalités spéciales pour nous aider à gérer notre communauté ensemble.
welcome_user:
title: "Bienvenue Utilisateur"
subject_template: "Bienvenue sur %{site_name} !"
@ -1957,6 +2054,13 @@ fr:
Nous croyons au [comportement civilisé de la communauté](%{base_url}/guidelines) en tout temps.
Amusez-vous bien !
welcome_tl1_user:
title: "Bienvenue Utilisateur"
subject_template: "Merci de passer du temps avec nous"
text_body_template: |
Salut ! Nous avons vu que vous lisez beaucoup, ce qui est super, alors nous vous avons monté d'un [niveau de confiance !](https://blog.discourse.org/2018/06/understanding-discourse-trust-levels/)
Nous sommes ravis que vous passez du temps avec nous, et on adorerez en savoir plus sur vous. Prenez un moment pour [remplir votre profil](/my/preferences/profile), ou sentez-vous libre de [démarrer un nouveau sujet](/categories).
welcome_invite:
title: "Bienvenue invité"
subject_template: "Bienvenue sur %{site_name} !"
@ -2154,6 +2258,13 @@ fr:
%{post_error}
Si vous pouvez corriger les erreurs, veuillez réessayer.
email_reject_post_too_short:
title: "Courriel rejeté trop court"
subject_template: "[%{email_prefix}] Problème de courriel -- Message trop court"
text_body_template: |
Nous sommes désolés, mais l'envoi de votre courriel à %{destination} (intitulé %{former_title}) n'a pas fonctionné.
Afin de favoriser des discussions plus approfondies, les réponses très courtes ne sont pas permises. Est-ce que vous pouvez reformuler votre réponse avec au moins %{count} caractères ? Alternativement, vous pouvez aimer un message par courriel en répondant "+1".
email_reject_invalid_post_action:
title: "Courriel rejeté - Message action invalide"
subject_template: "[%{email_prefix}] Problème de courriel -- Action de message invalide"
@ -2178,6 +2289,10 @@ fr:
email_reject_old_destination:
title: "Courriel rejeté - Mauvaise ancienne destination"
subject_template: "[%{email_prefix}] Problème de courriel -- Vous essayez de répondre à une ancienne notification"
text_body_template: |
Nous sommes désolé, mais l'envoi de votre courriel à destination de %{destination} (intitulé %{former_title}) n'a pas fonctionné.
Pour des raisons de sécurité, nous n'acceptons des réponses aux notifications que pendant %{number_of_days} jours. Veuillez vous rendre sur [le sujet](%{short_url}) pour continuer la conversation.
email_reject_topic_not_found:
title: "Courriel rejeté - Sujet introuvable"
subject_template: "[%{email_prefix}] Problème de courriel -- Sujet introuvable"
@ -2215,9 +2330,24 @@ fr:
Assurez-vous que les paramètres de connexion POP sont corrects dans les [paramètres du site](%{base_url}/admin/site_settings/category/email).
S'il y a une interface web pour le compte POP, vous devrez peut-être vous y connecter pour vérifier les paramètres.
email_revoked:
title: "Courriel revoqué"
subject_template: "Est-ce que votre adresse courriel est correct ?"
text_body_template: |
Nous sommes désolés, mais nous avons de la difficulté à vous joindre par courriel. Nos derniers courriels vous ont tous été renvoyés comme non livrables.
Pouvez-vous vous assurer que [votre adresse email](%{base_url}/my/preferences/email) est valide et fonctionne ? Vous pouvez également ajouter notre adresse e-mail à votre carnet d'adresses / liste de contacts pour améliorer la délivrabilité.
too_many_spam_flags:
title: "Trop de signalements de spam"
subject_template: "Nouveau compte bloqué"
text_body_template: |
Bonjour,
Ceci est un message automatique de %{site_name} pour vous informer que vos messages ont été temporairement masqués suite à des signalements de la communauté.
Par mesure de précaution, votre nouveau compte a été mis sous silence et vous ne pourrez plus créer de nouvelles réponses et sujets tant qu'un responsable n'a pas vérifié votre compte. Veuillez nous excuser pour la gêne occasionnée.
Pour plus d'informations, merci de vous référer à la [charte de la communauté](%{base_url}/guidelines).
too_many_tl3_flags:
title: "Trop de flags TL3"
subject_template: "Nouveau compte bloqué"
@ -2685,6 +2815,7 @@ fr:
recent_topics: "Récents"
see_more: "Plus"
search_title: "Rechercher dans ce site"
search_button: "Rechercher"
offline:
title: "Impossible de charger l'application"
offline_page_message: "Il semble que vous n'êtes pas connectés à Internet. Veuillez vérifier votre connexion réseau puis réessayez."
@ -2718,11 +2849,45 @@ fr:
flag_reason:
sockpuppet: "Un nouvel utilisateur a créé un sujet et un autre nouvel utilisateur avec la même adresse IP (%{ip_address}) a répondu. Voir le paramètre <a href='/admin/site_settings/category/spam'>`flag_sockpuppets`</a>."
spam_hosts: "Ce nouvel utilisateur a tenté de créer plusieurs messages avec des liens vers le même domaine (%{domain}). Voir le paramètre <a href='/admin/site_settings/category/spam'>`newuser_spam_host_threshold`</a>."
skipped_email_log:
exceeded_emails_limit: "max_emails_per_day_per_user dépassé"
exceeded_bounces_limit: "bounce_score_threshold dépassé"
mailing_list_no_echo_mode: "Notifications de la liste de diffusion désactivées pour les messages de l'utilisateur."
user_email_no_user: "Impossible de trouver l'utilisateur avec l'id %{user_id}"
user_email_post_not_found: "Impossible de trouver le message avec l'id %{post_id}"
user_email_anonymous_user: "L'utilisateur est anonyme"
user_email_user_suspended_not_pm: "L'utilisateur est suspendu, pas un message"
user_email_seen_recently: "L'utilisateur a été vu récemment"
user_email_notification_already_read: "La notification de ce courriel a déjà été lue"
user_email_notification_topic_nil: "post.topic vaut nil"
user_email_post_user_deleted: "L'auteur du message a été supprimé."
user_email_post_deleted: "le message a été supprimé son auteur"
user_email_user_suspended: "l'utilisateur a été suspendu"
user_email_already_read: "l'utilisateur a déjà lu ce message"
sender_message_blank: "le message est vide"
sender_message_to_blank: "message.to est vide"
sender_text_part_body_blank: "text_part.body est vide"
sender_body_blank: "sans contenu"
sender_post_deleted: "le message a été supprimé"
color_schemes:
base_theme_name: "Base"
light: "Palette claire"
dark: "Palette sombre"
neutral: "Palette neutre"
grey_amber: "Palette gris ambre"
shades_of_blue: "Palette nuances de bleu"
latte: "Palette latte"
summer: "Palette été"
dark_rose: "Palette rose foncé"
default_theme_name: "Clair"
light_theme_name: "Clair"
dark_theme_name: "Sombre"
neutral_theme_name: "Neutre"
grey_amber_theme_name: "Gris ambre"
shades_of_blue_theme_name: "Nuances de bleu"
latte_theme_name: "Latte"
summer_theme_name: "Eté"
dark_rose_theme_name: "Rose foncé"
about: "À propos"
guidelines: "Charte"
privacy: "Confidentialité"
@ -3057,41 +3222,73 @@ fr:
editor:
name: Éditeur
description: Première modification d'un message
long_description: |
Ce badge est accordé après la première modification d'un de vos messages. Bien que la possibilité d'éditer un message soit limitée dans le temps, l'édition est toujours encouragée — vous pouvez améliorer les messages, réparer des petites erreurs, ou ajouter des éléments que vous avez oubliés lors de la rédaction. Éditer vos messages pour les rendre encore meilleurs !
basic_user:
name: Actif
description: "<a href=\"https://blog.discourse.org/2018/06/understanding-discourse-trust-levels/\">Accès accordé</a> à toutes les fonctions communautaires essentielles"
long_description: |
Ce badge est accordé lorsque vous atteignez le niveau de confiance 1. Merci d'être resté dans le coin et d'avoir lu quelques sujets pour en apprendre plus sur notre communauté. Les restrictions "nouvel utilisateur" ont été levées, et vous avez accès aux fonctionnalités essentielles telles que la messagerie personnelle, le signalement, l'édition des wikis, et la possibilité de poster des images et de multiples liens.
member:
name: Membre
description: "<a href=\"https://blog.discourse.org/2018/06/understanding-discourse-trust-levels/\">Accès accordé</a> aux invitations, aux messages de groupe et à plus de J'aime"
long_description: |
Ce badge est accordé lorsque vous atteignez le niveau de confiance 2. Merci d'avoir participé durant plusieurs semaines à notre communauté. Vous pouvez désormais envoyer des invitations personnelles depuis votre page utilisateur ou un sujet, envoyer des messages groupés, et avez des J'aime supplémentaires chaque jour.
regular:
name: Habitué
description: "<a href=\"https://blog.discourse.org/2018/06/understanding-discourse-trust-levels/\">Accès accordé</a> à la re-catégorisation, au renommage, au suivi de liens, au Wiki et à plus de J'aime"
long_description: |
Ce badge est accordé lorsque vous atteignez le niveau de confiance 3. Merci d'avoir été un participant régulier à notre communauté pendant ces quelques mois, l'un de nos lecteurs les plus actifs et un contributeur sérieux à ce qui rend notre communauté si belle. Vous pouvez désormais re-catégoriser et renommer des sujets, accéder à la section privée, signaler des spams, et vous avez plein de J'aime en plus chaque jour.
leader:
name: Meneur
description: "<a href=\"https://blog.discourse.org/2018/06/understanding-discourse-trust-levels/\">Accès accordé</a> à l'édition globale, l'épinglage, la fermeture, l'archivage, la séparation et la fusion de sujets, et toujours plus de J'aime"
long_description: |
Ce badge est accordé lorsque vous atteignez le niveau de confiance 4. Vous êtes un meneur choisi par l'équipe dans cette communauté, et vous montrez l'exemple dans vos actions et vos mots. Vous avez la capacité de modifier tous les messages, utiliser les actions de modérations telles qu'épingler, fermer, cacher, archiver, scinder et fusionner.
welcome:
name: Bienvenue
description: A reçu un J'aime
long_description: |
Ce badge est accordé lorsque vous recevez votre premier J'aime sur un de vos messages. Félicitations, vous avez écrit quelque chose que les membres de votre communauté ont trouvé intéressant, cool, ou utile !
autobiographer:
name: Autobiographe
description: "A complété les informations de son <a href=\"%{base_uri}/my/preferences/profile\">profile</a>"
long_description: |
Ce badge est accordé pour avoir complété <a href="%{base_uri}/my/preferences/profile">votre profil utilisateur</a> et au choix d'une photo de profil. En dire plus à la communauté sur vous et vos intérêts permet de la rendre plus agréable et plus connectée. Rejoignez-nous !
anniversary:
name: Jubilaire
description: "Membre actif depuis un an, avec au moins un message"
long_description: |
Ce badge est accordé après avoir été membre du site pendant une année, avec au moins un message crée dans cette année. Merci d'être resté avec nous et de contribuer ainsi à notre communauté ! Nous n'aurions pas pu le faire sans vous.
nice_post:
name: Jolie réponse
description: A reçu 10 J'aime sur une réponse
long_description: |
Ce badge est accordé quand votre réponse obtient 10 J'aime. Votre réponse a fait bonne impression sur la communauté et a aidé la conversation à progresser.
good_post:
name: Bonne réponse
description: A reçu 25 J'aime sur une réponse
long_description: |
Ce badge est accordé quand votre réponse obtient 25 J'aime. Votre réponse est exceptionnel et a rendu la conversation beaucoup plus intéressante !
great_post:
name: Super réponse
description: A reçu 50 J'aime sur une réponse
long_description: |
Ce badge est accordé quand votre réponse obtient 50 J'aime. Votre réponse était inspirante, fascinante, hilarante, ou pertinente et la communauté l'a adorée !
nice_topic:
name: Sujet intéressant
description: A reçu 10 J'aime sur un sujet
long_description: |
Ce badge est accordé quand votre sujet obtient 10 J'aime. Vous avez commencé une conversation intéressante que la communauté a apprécié !
good_topic:
name: Bon sujet
description: A reçu 25 J'aime sur un sujet
long_description: |
Ce badge est accordé quand votre sujet obtient 25 J'aime. Vous avez lancé une conversation vibrante autour de laquelle la communauté s'est ralliée !
great_topic:
name: Super sujet
description: A reçu 50 J'aime sur un sujet
long_description: |
Ce badge est accordé quand votre sujet obtient 50 J'aime. Vous avez initié une conversation fascinante et la communauté a apprécié la discussion dynamique résultante !
nice_share:
name: Partage sympa
description: Message partagé avec 25 visiteurs uniques
@ -3100,9 +3297,13 @@ fr:
good_share:
name: Bon partage
description: Message partagé avec 300 visiteurs uniques
long_description: |
Ce badge est accordé après le partage d'un lien vers un message consulté par 300 visiteurs extérieurs. Bon travail ! Vous avez diffusé une discussion intéressante à <i>beaucoup</i> de nouvelles personnes et nous avez aidés à grandir.
great_share:
name: Super partage
description: Message partagé avec 1000 visiteurs uniques
long_description: |
Ce badge est accordé après le partage d'un lien qui a été cliqué par 1000 visiteurs extérieurs. Whaou ! Vous avez fait la promotion d'une discussion intéressante auprès d'une nouvelle audience d'envergure et avez aidé notre communauté à grandir de manière significative !
first_like:
name: Premier J'aime
description: A aimé un message
@ -3111,6 +3312,8 @@ fr:
first_flag:
name: Premier signalement
description: A signalé un message
long_description: |
Ce badge est accordé la première fois que vous signalez un message. Les signalements sont essentiels à la santé de votre communauté. Si vous remarquez des messages nécessitant l'intervention d'un modérateur n'hésitez pas à les signaler. Si vous voyez un problème, :flag_black: signalez-le !
promoter:
name: Ambassadeur
description: A invité un utilisateur
@ -3119,9 +3322,13 @@ fr:
campaigner:
name: Militant
description: A invité 3 utilisateurs basiques
long_description: |
Ce badge est accordé lorsque vous avez invité 3 personnes qui ont ensuite passé assez de temps sur le site pour devenir des utilisateurs de base. Une communauté dynamique a besoin d'un apport régulier de nouveaux arrivants qui participent régulièrement et ajouter de nouvelles voix aux conversations.
champion:
name: Champion
description: A invité 5 membres
long_description: |
Ce badge est accordé lorsque vous avez invité 5 personnes qui ont ensuite passé assez de temps sur le site pour devenir des membres à part entière. Whaou ! Merci d'élargir la diversité de notre communauté avec de nouveaux membres !
first_share:
name: Premier partage
description: A partagé un message
@ -3135,9 +3342,13 @@ fr:
first_quote:
name: Première citation
description: A cité un message
long_description: |
Ce badge est accordé la première fois que vous citez un message dans votre réponse. Citer les sections pertinentes des messages précédents dans votre réponse permet de garder les discussions reliées entre elles et sur le sujet. La meilleure façon de citer est de mettre en évidence une section d'un message, puis appuyer sur un bouton de réponse. Citer généreusement !
read_guidelines:
name: Règlement lu
description: "A lu le <a href=\"%{base_uri}/guidelines\">règlement de la communauté</a>"
long_description: |
Ce badge est accordé après <a href="%{base_uri}/guidelines">lecture de la charte de la communauté</a>. Respecter et partager ces règles simples permet de construire une communauté sûre, agréable et durable. Souvenez-vous toujours qu'il y a un autre être humain, tout comme vous, de l'autre côté de cet écran. Soyez courtois !
reader:
name: Lecteur
description: A lu tous les messages d'un sujet contenant plus de 100 messages
@ -3186,6 +3397,8 @@ fr:
crazy_in_love:
name: Fou amoureux
description: A utilisé 50 likes en un jour 20 fois
long_description: |
Ce badge est accordé lorsque vous utilisez la totalité vos 50 J'aime par jour pendant 20 jours. Whaou ! Vous êtes un modèle d'encouragement pour les membres de la communauté !
thank_you:
name: "Merci "
description: A 20 messages ayant reçu un J'aime et a donné 10 j'aime
@ -3199,6 +3412,7 @@ fr:
empathetic:
name: Empathique
description: A 500 messages ayant reçu un J'aime et a donné 1000 J'aime
long_description: "Ce badge est accordé quand vous avez reçu 500 J'aime et en avez donné 1000 ou plus en retour. Whaou ! Vous êtes un modèle de générosité et d'amour mutuel :two_hearts:. \n"
first_emoji:
name: Premier Emoji
description: A utilisé un emoji dans un message
@ -3207,9 +3421,13 @@ fr:
first_mention:
name: Première mention
description: A mentionné un utilisateur dans un message
long_description: |
Ce badge est accordé la première fois que vous mentionnez le @pseudo de quelqu'un dans votre message. Chaque mention génère une notification à cette personne pour qu'elle soit informée de votre message. Il suffit de commencer à taper @ (arobase) pour mentionner un utilisateur ou, si autorisé, un groupe c'est un moyen pratique de porter quelque chose à leur attention.
first_onebox:
name: Premier onebox
description: A inséré un lien qui a été transformé en onebox
long_description: |
Ce badge est accordé la première fois que vous publiez un lien seul sur une ligne, qui a été développé automatiquement dans un onebox avec un bref résumé, un titre, et (le cas échéant) une image.
first_reply_by_email:
name: Première réponse par courriel
description: A répondu à un message par courriel
@ -3223,12 +3441,18 @@ fr:
enthusiast:
name: Passionné
description: A visité 10 jours
long_description: |
Ce badge est décerné pour avoir visité 10 jours consécutifs. Merci d'être resté avec nous pendant plus d'une semaine !
aficionado:
name: Aficionado
description: A visité 100 jours
long_description: |
Ce badge est décerné pour avoir visité 100 jours consécutifs. C'est plus de trois mois !
devotee:
name: Adepte
description: A visité 365 jours
long_description: |
Ce badge est décerné pour avoir visité 365 jours consécutifs. Waouh, une année entière !
badge_title_metadata: "%{display_name} badge sur %{site_title}"
admin_login:
success: "Courriel envoyé"
@ -3252,6 +3476,7 @@ fr:
button: "Créer"
title: "Créer un compte administrateur"
help: "créez un nouveau compte pour commencer"
no_emails: "Malheureusement aucune adresse courriel d'administrateur n'a été définie lors de la configuration. Merci d'ajouter un courriel de développeur dans le fichier de configuration ou de <a href='https://meta.discourse.org/t/create-admin-account-from-console/17274'>créer un compte administrateur depuis le console</a>."
confirm_email:
title: "Confirmer votre adresse courriel"
message: "<p>Nous avons envoyé un courriel dactivation à <b>%{email}</b>. Veuillez suivre les instructions du courriel pour activer votre compte.</p><p>S'il n'est pas réceptionné, assurez-vous que l'adresse courriel est correctement configurée pour votre Discourse et vérifiez vos courriers indésirables.</p>"
@ -3354,6 +3579,7 @@ fr:
fields:
favicon_url:
label: "Petite icône"
description: "Icône utilisée pour représenter le site dans les navigateurs Web et qui rend bien à de petites tailles comme 32px par 32px. PNG ou JPG recommandés."
apple_touch_icon_url:
label: "Grande icône"
description: "Icône utilisée pour représenter le site sur les appareils modernes et que rend bien à des tailles plus grandes. La taille minimale conseillée est de 512px par 512px."

View File

@ -1474,6 +1474,13 @@ ro:
Anumite probleme îți sunt raportate pe spațul de lucru al adminului.
[Te rugăm să le verifici și să le rezolvi](%{base_url}/admin).
new_user_of_the_month:
text_body_template: |
Felicitări, ai primit ecusonul **Premiul pentru utilizatorul nou al lunii %{month_year}**. :trophy:
În fiecare lună, doi utilizatori noi primesc acest premiu ce va fi vizibil permanent pe [pagina de ecusoane](%{url})
Ai devenit un membru valoros al comunității noastre. Mulțumim pentru participare!
unsubscribe_link: |
Pentru dezabonare de la aceste emailuri, [click aici](%{unsubscribe_url}).
unsubscribe_link_and_mail: |

View File

@ -1799,7 +1799,6 @@ zh_TW:
recent_topics: "最近"
see_more: "更多"
search_title: "搜尋該網頁"
search_google: "Google"
login_required:
welcome_message: |
## [歡迎來到 %{title}](#welcome)

View File

@ -413,6 +413,7 @@ Discourse::Application.routes.draw do
put "#{root_path}/:username/preferences/avatar/pick" => "users#pick_avatar", constraints: { username: RouteFormat.username }
put "#{root_path}/:username/preferences/avatar/select" => "users#select_avatar", constraints: { username: RouteFormat.username }
post "#{root_path}/:username/preferences/revoke-account" => "users#revoke_account", constraints: { username: RouteFormat.username }
post "#{root_path}/:username/preferences/revoke-auth-token" => "users#revoke_auth_token", constraints: { username: RouteFormat.username }
get "#{root_path}/:username/staff-info" => "users#staff_info", constraints: { username: RouteFormat.username }
get "#{root_path}/:username/summary" => "users#summary", constraints: { username: RouteFormat.username }
get "#{root_path}/:username/invited" => "users#invited", constraints: { username: RouteFormat.username }

View File

@ -1247,10 +1247,10 @@ rate_limits:
default: 6
max_post_deletions_per_minute:
min: 1
default: 3
default: 2
max_post_deletions_per_day:
min: 1
default: 20
default: 10
developer:
force_hostname:

View File

@ -25,7 +25,7 @@ pid (ENV["UNICORN_PID_PATH"] || "#{discourse_path}/tmp/pids/unicorn.pid")
if ENV["RAILS_ENV"] == "development" || !ENV["RAILS_ENV"]
logger Logger.new($stdout)
# we want a longer timeout in dev cause first request can be really slow
timeout 60
timeout (ENV["UNICORN_TIMEOUT"] && ENV["UNICORN_TIMEOUT"].to_i || 60)
else
# By default, the Unicorn logger will write to stderr.
# Additionally, some applications/frameworks log to stderr or stdout,

View File

@ -2,6 +2,7 @@
require 'migration/table_dropper'
require 'migration/column_dropper'
require 'badge_posts_view_manager'
Migration::ColumnDropper.drop(
table: 'user_profiles',
@ -74,20 +75,6 @@ Migration::ColumnDropper.drop(
}
)
VIEW_NAME = "badge_posts".freeze
def badge_posts_view_exists?
sql = <<~SQL
SELECT 1
FROM pg_catalog.pg_views
WHERE schemaname
IN ('public')
AND viewname = '#{VIEW_NAME}';
SQL
DB.exec(sql) == 1
end
Migration::ColumnDropper.drop(
table: 'posts',
after_migration: 'DropVoteCountFromTopicsAndPosts',
@ -96,27 +83,10 @@ Migration::ColumnDropper.drop(
},
on_drop: ->() {
STDERR.puts "Removing superflous post columns!"
DB.exec("DROP VIEW #{VIEW_NAME}")
raise "Failed to drop '#{VIEW_NAME}' view" if badge_posts_view_exists?
BadgePostsViewManager.drop!
},
after_drop: -> () {
sql = <<~SQL
CREATE VIEW #{VIEW_NAME} AS
SELECT p.*
FROM posts p
JOIN topics t ON t.id = p.topic_id
JOIN categories c ON c.id = t.category_id
WHERE c.allow_badges AND
p.deleted_at IS NULL AND
t.deleted_at IS NULL AND
NOT c.read_restricted AND
t.visible AND
p.post_type IN (1,2,3)
SQL
DB.exec(sql)
raise "Failed to create '#{VIEW_NAME}' view" unless badge_posts_view_exists?
BadgePostsViewManager.create!
}
)

View File

@ -99,7 +99,7 @@ class PostgreSQLFallbackHandler
end
def clear_connections
ActiveRecord::Base.connection_pool.disconnect
ActiveRecord::Base.connection_pool.disconnect!
end
private

View File

@ -81,12 +81,7 @@ class Auth::DefaultCurrentUserProvider
raise Discourse::InvalidAccess.new(I18n.t('invalid_api_credentials'), nil, custom_message: "invalid_api_credentials") unless current_user
raise Discourse::InvalidAccess if current_user.suspended? || !current_user.active
@env[API_KEY_ENV] = true
# we do not run this rate limiter while profiling
if Rails.env != "profile"
limiter_min = RateLimiter.new(nil, "admin_api_min_#{api_key}", GlobalSetting.max_admin_api_reqs_per_key_per_minute, 60)
limiter_min.performed!
end
rate_limit_admin_api_requests(api_key)
end
# user api key handling
@ -296,4 +291,17 @@ class Auth::DefaultCurrentUserProvider
end
end
private
def rate_limit_admin_api_requests(api_key)
return if Rails.env == "profile"
RateLimiter.new(
nil,
"admin_api_min_#{api_key}",
GlobalSetting.max_admin_api_reqs_per_key_per_minute,
60
).performed!
end
end

View File

@ -69,6 +69,8 @@ class Auth::Result
email_valid: !!email_valid,
omit_username: !!omit_username }
result[:destination_url] = destination_url if destination_url.present?
if SiteSetting.enable_names?
result[:name] = User.suggest_name(name || username || email)
end

View File

@ -0,0 +1,39 @@
class BadgePostsViewManager
VIEW_NAME = "badge_posts".freeze
def self.create!
sql = <<~SQL
CREATE VIEW #{VIEW_NAME} AS
SELECT p.*
FROM posts p
JOIN topics t ON t.id = p.topic_id
JOIN categories c ON c.id = t.category_id
WHERE c.allow_badges AND
p.deleted_at IS NULL AND
t.deleted_at IS NULL AND
NOT c.read_restricted AND
t.visible AND
p.post_type IN (1,2,3)
SQL
DB.exec(sql)
raise "Failed to create '#{VIEW_NAME}' view" unless badge_posts_view_exists?
end
def self.drop!
DB.exec("DROP VIEW #{VIEW_NAME}")
raise "Failed to drop '#{VIEW_NAME}' view" if badge_posts_view_exists?
end
def self.badge_posts_view_exists?
sql = <<~SQL
SELECT 1
FROM pg_catalog.pg_views
WHERE schemaname
IN ('public')
AND viewname = '#{VIEW_NAME}';
SQL
DB.exec(sql) == 1
end
end

View File

@ -35,12 +35,12 @@ class CookedPostProcessor
post_process_oneboxes
post_process_images
post_process_quotes
keep_reverse_index_up_to_date
optimize_urls
update_post_image
enforce_nofollow
pull_hotlinked_images(bypass_bump)
grant_badges
@post.link_post_uploads(fragments: @doc)
DiscourseEvent.trigger(:post_process_cooked, @doc, @post)
nil
end
@ -58,26 +58,6 @@ class CookedPostProcessor
BadgeGranter.grant(Badge.find(Badge::FirstReplyByEmail), @post.user, post_id: @post.id) if @post.is_reply_by_email?
end
def keep_reverse_index_up_to_date
upload_ids = []
@doc.css("a/@href", "img/@src").each do |media|
if upload = Upload.get_from_url(media.value)
upload_ids << upload.id
end
end
upload_ids |= downloaded_images.values.select { |id| Upload.exists?(id) }
values = upload_ids.map { |u| "(#{@post.id},#{u})" }.join(",")
PostUpload.transaction do
PostUpload.where(post_id: @post.id).delete_all
if upload_ids.size > 0
DB.exec("INSERT INTO post_uploads (post_id, upload_id) VALUES #{values}")
end
end
end
def post_process_images
extract_images.each do |img|
src = img["src"].sub(/^https?:/i, "")
@ -159,15 +139,25 @@ class CookedPostProcessor
end
def large_images
@large_images ||= JSON.parse(@post.custom_fields[Post::LARGE_IMAGES].presence || "[]") rescue []
@large_images ||=
begin
JSON.parse(@post.custom_fields[Post::LARGE_IMAGES].presence || "[]")
rescue JSON::ParserError
[]
end
end
def broken_images
@broken_images ||= JSON.parse(@post.custom_fields[Post::BROKEN_IMAGES].presence || "[]") rescue []
@broken_images ||=
begin
JSON.parse(@post.custom_fields[Post::BROKEN_IMAGES].presence || "[]")
rescue JSON::ParserError
[]
end
end
def downloaded_images
@downloaded_images ||= JSON.parse(@post.custom_fields[Post::DOWNLOADED_IMAGES].presence || "{}") rescue {}
@downloaded_images ||= @post.downloaded_images
end
def extract_images

View File

@ -39,23 +39,33 @@ module DiscourseHub
end
def self.singular_action(action, rel_url, params = {})
connect_opts = connect_opts(params)
JSON.parse(Excon.send(action,
"#{hub_base_url}#{rel_url}",
headers: { 'Referer' => referer, 'Accept' => accepts.join(', ') },
query: params,
omit_default_port: true
{
headers: { 'Referer' => referer, 'Accept' => accepts.join(', ') },
query: params,
omit_default_port: true
}.merge(connect_opts)
).body)
end
def self.collection_action(action, rel_url, params = {})
connect_opts = connect_opts(params)
JSON.parse(Excon.send(action,
"#{hub_base_url}#{rel_url}",
body: JSON[params],
headers: { 'Referer' => referer, 'Accept' => accepts.join(', '), "Content-Type" => "application/json" },
omit_default_port: true
{
body: JSON[params],
headers: { 'Referer' => referer, 'Accept' => accepts.join(', '), "Content-Type" => "application/json" },
omit_default_port: true
}.merge(connect_opts)
).body)
end
def self.connect_opts(params = {})
params.delete(:connect_opts)&.except(:body, :headers, :query) || {}
end
def self.hub_base_url
if Rails.env.production?
ENV['HUB_BASE_URL'] || 'https://api.discourse.org/api'

View File

@ -596,12 +596,14 @@ module Email
raise ReplyUserNotMatchingError, "post_reply_key.user_id => #{post_reply_key.user_id.inspect}, user.id => #{user.id.inspect}"
end
post = Post.with_deleted.find(post_reply_key.post_id)
create_reply(user: user,
raw: body,
elided: elided,
hidden_reason_id: hidden_reason_id,
post: post_reply_key.post,
topic: post_reply_key.post.topic,
post: post,
topic: post&.topic,
skip_validations: user.staged?)
end
end
@ -834,13 +836,14 @@ module Email
def create_reply(options = {})
raise TopicNotFoundError if options[:topic].nil? || options[:topic].trashed?
options[:post] = nil if options[:post]&.trashed?
if post_action_type = post_action_for(options[:raw])
create_post_action(options[:user], options[:post], post_action_type)
else
raise TopicClosedError if options[:topic].closed?
options[:topic_id] = options[:post].try(:topic_id)
options[:reply_to_post_number] = options[:post].try(:post_number)
options[:topic_id] = options[:topic].id
options[:reply_to_post_number] = options[:post]&.post_number
options[:is_group_message] = options[:topic].private_message? && options[:topic].allowed_groups.exists?
create_post_with_attachments(options)
end

View File

@ -106,14 +106,14 @@ module Email
.where(id: PostReply.where(reply_id: post_id).select(:post_id))
.order(id: :desc)
referenced_post_message_ids = referenced_posts.map do |post|
if post.incoming_email&.message_id.present?
"<#{post.incoming_email.message_id}>"
referenced_post_message_ids = referenced_posts.map do |referenced_post|
if referenced_post.incoming_email&.message_id.present?
"<#{referenced_post.incoming_email.message_id}>"
else
if post.post_number == 1
if referenced_post.post_number == 1
"<topic/#{topic_id}@#{host}>"
else
"<topic/#{topic_id}/#{post.id}@#{host}>"
"<topic/#{topic_id}/#{referenced_post.id}@#{host}>"
end
end
end

View File

@ -9,6 +9,8 @@ module FileStore
class S3Store < BaseStore
TOMBSTONE_PREFIX ||= "tombstone/"
attr_reader :s3_helper
def initialize(s3_helper = nil)
@s3_helper = s3_helper || S3Helper.new(s3_bucket, TOMBSTONE_PREFIX)
end

View File

@ -26,7 +26,7 @@ class FinalDestination
"HTTPS_DOMAIN_#{domain}"
end
attr_reader :status, :cookie, :status_code
attr_reader :status, :cookie, :status_code, :ignored
def initialize(url, opts = nil)
@url = url
@ -36,7 +36,22 @@ class FinalDestination
@force_get_hosts = @opts[:force_get_hosts] || []
@opts[:max_redirects] ||= 5
@opts[:lookup_ip] ||= lambda { |host| FinalDestination.lookup_ip(host) }
@ignored = [Discourse.base_url_no_prefix] + (@opts[:ignore_redirects] || [])
@ignored = @opts[:ignore_hostnames] || []
ignore_redirects = [Discourse.base_url_no_prefix]
if @opts[:ignore_redirects]
ignore_redirects.concat(@opts[:ignore_redirects])
end
ignore_redirects.each do |ignore_redirect|
ignore_redirect = uri(ignore_redirect)
if ignore_redirect.present? && ignore_redirect.hostname
@ignored << ignore_redirect.hostname
end
end
@limit = @opts[:max_redirects]
@status = :ready
@http_verb = @force_get_hosts.any? { |host| hostname_matches?(host) } ? :get : :head
@ -131,18 +146,18 @@ class FinalDestination
return nil
end
@ignored.each do |host|
if hostname_matches?(host)
@status = :resolved
return @uri
end
end
unless validate_uri
log(:warn, "FinalDestination could not resolve URL (invalid URI): #{@uri}") if @verbose
return nil
end
@ignored.each do |host|
if @uri&.hostname&.match?(host)
@status = :resolved
return @uri
end
end
headers = request_headers
response = Excon.public_send(@http_verb,
@uri.to_s,

View File

@ -20,12 +20,12 @@ module ActiveSupport
uncached = "#{method_name}_without_cache"
alias_method uncached, method_name
define_method(method_name) do |*args|
define_method(method_name) do |*arguments|
# this avoids recursive locks
found = true
data = cache.fetch(args) { found = false }
data = cache.fetch(arguments) { found = false }
unless found
cache[args] = data = send(uncached, *args)
cache[arguments] = data = send(uncached, *arguments)
end
# so cache is never corrupted
data.dup
@ -45,9 +45,10 @@ module ActiveSupport
args.each do |method_name|
orig = "#{method_name}_without_clear_memoize"
alias_method orig, method_name
define_method(method_name) do |*args|
define_method(method_name) do |*arguments|
ActiveSupport::Inflector.clear_memoize!
send(orig, *args)
send(orig, *arguments)
end
end
end

View File

@ -41,9 +41,11 @@ module I18n
I18n.backend.load_translations(I18n.load_path.grep(/\.rb$/))
# load plural rules from plugins
DiscoursePluginRegistry.locales.each do |locale, options|
DiscoursePluginRegistry.locales.each do |plugin_locale, options|
if options[:plural]
I18n.backend.store_translations(locale, i18n: { plural: options[:plural] })
I18n.backend.store_translations(plugin_locale,
i18n: { plural: options[:plural] }
)
end
end
end
@ -68,7 +70,7 @@ module I18n
target = opts[:backend] || backend
results = opts[:overridden] ? {} : target.search(config.locale, query)
regexp = /#{query}/i
regexp = /#{Regexp.escape(query)}/i
(overrides_by_locale(locale) || {}).each do |k, v|
results.delete(k)
results[k] = v if (k =~ regexp || v =~ regexp)

View File

@ -364,10 +364,10 @@ class Guardian
UserExport.where(user_id: @user.id, created_at: (Time.zone.now.beginning_of_day..Time.zone.now.end_of_day)).count == 0
end
def allow_themes?(theme_ids)
def allow_themes?(theme_ids, include_preview: false)
return true if theme_ids.blank?
if is_staff? && (theme_ids - Theme.theme_ids).blank?
if include_preview && is_staff? && (theme_ids - Theme.theme_ids).blank?
return true
end

View File

@ -44,7 +44,7 @@ module I18n
results = {}
fallbacks(locale).each do |fallback|
find_results(/#{query}/i, results, translations[fallback])
find_results(/#{Regexp.escape(query)}/i, results, translations[fallback])
end
results

View File

@ -1,6 +1,6 @@
class I18nInterpolationKeysFinder
def self.find(text)
keys = text.scan(I18n::INTERPOLATION_PATTERN)
keys = text.scan(Regexp.union(I18n::INTERPOLATION_PATTERN, /\{\{(\w+)\}\}/))
keys.flatten!
keys.compact!
keys.uniq!

View File

@ -0,0 +1,173 @@
require 'i18n/i18n_interpolation_keys_finder'
require 'yaml'
class LocaleFileChecker
TYPE_MISSING_INTERPOLATION_KEYS = 1
TYPE_UNSUPPORTED_INTERPOLATION_KEYS = 2
TYPE_MISSING_PLURAL_KEYS = 3
TYPE_INVALID_MESSAGE_FORMAT = 4
def check(locale)
@errors = {}
@locale = locale.to_s
locale_files.each do |locale_path|
next unless reference_path = reference_file(locale_path)
@relative_locale_path = Pathname.new(locale_path).relative_path_from(Pathname.new(Rails.root)).to_s
@locale_yaml = YAML.load_file(locale_path)
@reference_yaml = YAML.load_file(reference_path)
check_interpolation_keys
check_plural_keys
check_message_format
end
@errors
end
private
YML_DIRS = ["config/locales", "plugins/**/locales"]
PLURALS_FILE = "config/locales/plurals.rb"
REFERENCE_LOCALE = "en"
REFERENCE_PLURAL_KEYS = ["one", "other"]
# Some languages should always use %{count} in pluralized strings.
# https://meta.discourse.org/t/always-use-count-variable-when-translating-pluralized-strings/83969
FORCE_PLURAL_COUNT_LOCALES = ["bs", "lt", "lv", "ru", "sl", "sr", "uk"]
def locale_files
YML_DIRS.map { |dir| Dir["#{Rails.root}/#{dir}/{client,server}.#{@locale}.yml"] }.flatten
end
def reference_file(path)
path = path.gsub(/\.\w{2,}\.yml$/, ".#{REFERENCE_LOCALE}.yml")
path if File.exists?(path)
end
def traverse_hash(hash, parent_keys, &block)
hash.each do |key, value|
keys = parent_keys.dup << key
if value.is_a?(Hash)
traverse_hash(value, keys, &block)
else
yield(keys, value, hash)
end
end
end
def check_interpolation_keys
traverse_hash(@locale_yaml, []) do |keys, value|
reference_value = reference_value(keys)
next if reference_value.nil?
if pluralized = reference_value_pluralized?(reference_value)
if keys.last == "one" && !FORCE_PLURAL_COUNT_LOCALES.include?(@locale)
reference_value = reference_value["one"]
else
reference_value = reference_value["other"]
end
end
reference_interpolation_keys = I18nInterpolationKeysFinder.find(reference_value.to_s)
locale_interpolation_keys = I18nInterpolationKeysFinder.find(value.to_s)
missing_keys = reference_interpolation_keys - locale_interpolation_keys
unsupported_keys = locale_interpolation_keys - reference_interpolation_keys
# English strings often don't use the %{count} variable within the "one" key,
# but it's perfectly fine for other locales to use it.
unsupported_keys.delete("count") if pluralized && keys.last == "one"
# Not all locales need the %{count} variable within the "one" key.
if pluralized && keys.last == "one" && !FORCE_PLURAL_COUNT_LOCALES.include?(@locale)
missing_keys.delete("count")
end
add_error(keys, TYPE_MISSING_INTERPOLATION_KEYS, missing_keys, pluralized: pluralized) unless missing_keys.empty?
add_error(keys, TYPE_UNSUPPORTED_INTERPOLATION_KEYS, unsupported_keys, pluralized: pluralized) unless unsupported_keys.empty?
end
end
def check_plural_keys
known_parent_keys = Set.new
traverse_hash(@locale_yaml, []) do |keys, _, parent|
keys = keys[0..-2]
parent_key = keys.join(".")
next if known_parent_keys.include?(parent_key)
known_parent_keys << parent_key
reference_value = reference_value(keys)
next if reference_value.nil? || !reference_value_pluralized?(reference_value)
expected_plural_keys = plural_keys[@locale]
actual_plural_keys = parent.is_a?(Hash) ? parent.keys : []
missing_plural_keys = expected_plural_keys - actual_plural_keys
add_error(keys, TYPE_MISSING_PLURAL_KEYS, missing_plural_keys, pluralized: true) unless missing_plural_keys.empty?
end
end
def check_message_format
mf_locale, mf_filename = JsLocaleHelper.find_message_format_locale([@locale], true)
traverse_hash(@locale_yaml, []) do |keys, value|
next unless keys.last.ends_with?("_MF")
begin
JsLocaleHelper.with_context do |ctx|
ctx.load(mf_filename) if File.exist?(mf_filename)
ctx.eval("mf = new MessageFormat('#{mf_locale}');")
ctx.eval("mf.precompile(mf.parse(#{value.to_s.inspect}))")
end
rescue MiniRacer::EvalError => error
error_message = error.message.sub(/at undefined[:\d]+/, "").strip
add_error(keys, TYPE_INVALID_MESSAGE_FORMAT, error_message, pluralized: false)
end
end
end
def reference_value(keys)
value = @reference_yaml[REFERENCE_LOCALE]
keys[1..-2].each do |key|
value = value[key]
return nil if value.nil?
end
reference_value_pluralized?(value) ? value : value[keys.last]
end
def reference_value_pluralized?(value)
value.is_a?(Hash) &&
value.keys.sort == REFERENCE_PLURAL_KEYS &&
value.keys.all? { |k| value[k].is_a?(String) }
end
def plural_keys
@plural_keys ||= begin
eval(File.read("#{Rails.root}/#{PLURALS_FILE}")).map do |locale, value|
[locale.to_s, value[:i18n][:plural][:keys].map(&:to_s)]
end.to_h
end
end
def add_error(keys, type, details, pluralized:)
@errors[@relative_locale_path] ||= []
if pluralized
joined_key = keys[1..-2].join(".") << " [#{keys.last}]"
else
joined_key = keys[1..-1].join(".")
end
@errors[@relative_locale_path] << {
key: joined_key,
type: type,
details: details.to_s
}
end
end

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