Version bump
This commit is contained in:
commit
187505d0ba
@ -102,6 +102,9 @@ Layout/EndAlignment:
|
||||
Lint/RequireParentheses:
|
||||
Enabled: true
|
||||
|
||||
Lint/ShadowingOuterLocalVariable:
|
||||
Enabled: true
|
||||
|
||||
Layout/MultilineMethodCallIndentation:
|
||||
Enabled: true
|
||||
EnforcedStyle: indented
|
||||
|
||||
3
Gemfile
3
Gemfile
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"),
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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)))
|
||||
);
|
||||
},
|
||||
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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}}
|
||||
|
||||
@ -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"}}}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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");
|
||||
}
|
||||
);
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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];
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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}}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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([]);
|
||||
|
||||
@ -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
|
||||
|
||||
@ -0,0 +1,5 @@
|
||||
export default Ember.Component.extend({
|
||||
keyPress(e) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
});
|
||||
@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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%;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -219,6 +219,9 @@
|
||||
.avatar-flair {
|
||||
bottom: 8px;
|
||||
right: 2px;
|
||||
.fa {
|
||||
font-size: $font-0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -4,6 +4,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
thead {
|
||||
.d-icon {
|
||||
padding-left: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
$filter-line-height: 1.5;
|
||||
|
||||
.groups-header-filters {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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|
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
26
app/jobs/onceoff/correct_missing_dualstack_urls.rb
Normal file
26
app/jobs/onceoff/correct_missing_dualstack_urls.rb
Normal 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
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -17,6 +17,8 @@ module Jobs
|
||||
UserOption.ensure_consistency!
|
||||
Tag.ensure_consistency!
|
||||
CategoryTagStat.ensure_consistency!
|
||||
User.ensure_consistency!
|
||||
UserAvatar.ensure_consistency!
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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|
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
class UserAuthTokenLog < ActiveRecord::Base
|
||||
belongs_to :user
|
||||
end
|
||||
|
||||
# == Schema Information
|
||||
|
||||
@ -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
|
||||
|
||||
71
app/serializers/concerns/user_auth_tokens_mixin.rb
Normal file
71
app/serializers/concerns/user_auth_tokens_mixin.rb
Normal 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
|
||||
16
app/serializers/user_auth_token_log_serializer.rb
Normal file
16
app/serializers/user_auth_token_log_serializer.rb
Normal 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
|
||||
5
app/serializers/user_auth_token_serializer.rb
Normal file
5
app/serializers/user_auth_token_serializer.rb
Normal file
@ -0,0 +1,5 @@
|
||||
class UserAuthTokenSerializer < ApplicationSerializer
|
||||
include UserAuthTokensMixin
|
||||
|
||||
attributes :seen_at
|
||||
end
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 %>">
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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: "رد"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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."
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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: "動作"
|
||||
|
||||
@ -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 ■■■■ 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.
|
||||
|
||||
|
||||
@ -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."
|
||||
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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 …
|
||||
|
||||
* 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 d’activation à <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."
|
||||
|
||||
@ -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: |
|
||||
|
||||
@ -1799,7 +1799,6 @@ zh_TW:
|
||||
recent_topics: "最近"
|
||||
see_more: "更多"
|
||||
search_title: "搜尋該網頁"
|
||||
search_google: "Google"
|
||||
login_required:
|
||||
welcome_message: |
|
||||
## [歡迎來到 %{title}](#welcome)
|
||||
|
||||
@ -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 }
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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!
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@ -99,7 +99,7 @@ class PostgreSQLFallbackHandler
|
||||
end
|
||||
|
||||
def clear_connections
|
||||
ActiveRecord::Base.connection_pool.disconnect
|
||||
ActiveRecord::Base.connection_pool.disconnect!
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
39
lib/badge_posts_view_manager.rb
Normal file
39
lib/badge_posts_view_manager.rb
Normal 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
|
||||
@ -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
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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!
|
||||
|
||||
173
lib/i18n/locale_file_checker.rb
Normal file
173
lib/i18n/locale_file_checker.rb
Normal 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
Reference in New Issue
Block a user