diff --git a/.gitignore b/.gitignore index 6d540c4138..b671bcb967 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,7 @@ neo4j/ # Ignore Capistrano customizations config/deploy/* + + +# Ignore IDE files +.vscode/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bfc771ab98..9ca01a56f5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,7 +7,7 @@ There are three ways in which you can contribute to this repository: 2. By working on the back-end application 3. By working on the front-end application -Choosing what to work on in a large open source project is not easy. The list of GitHub issues may provide some ideas, but not every feature request has been greenlit. Likewise, not every change or feature that resolves a personal itch will be merged into the main repository. Some communication ahead of time may be wise. +Choosing what to work on in a large open source project is not easy. The list of GitHub issues may provide some ideas, but not every feature request has been greenlit. Likewise, not every change or feature that resolves a personal itch will be merged into the main repository. Some communication ahead of time may be wise. If your addition creates a new feature or setting, or otherwise changes how things work in some substantial way, please remember to submit a correlating pull request to document your changes in the [documentation](http://github.com/tootsuite/documentation). Below are the guidelines for working on pull requests: @@ -41,3 +41,4 @@ It is expected that you have a working development environment set up (see back- * If you are introducing new strings, they must be using localization methods If the JavaScript or CSS assets won't compile due to a syntax error, it's a good sign that the pull request isn't ready for submission yet. + diff --git a/README.md b/README.md index 37065f9d76..6a253b2855 100644 --- a/README.md +++ b/README.md @@ -67,23 +67,52 @@ Consult the example configuration file, `.env.production.sample` for the full li [](https://microbadger.com/images/gargron/mastodon "Get your own version badge on microbadger.com") [](https://microbadger.com/images/gargron/mastodon "Get your own image badge on microbadger.com") -The project now includes a `Dockerfile` and a `docker-compose.yml` file (which requires at least docker-compose version `1.10.0`). You need to turn `.env.production.sample` into `.env.production` with all the variables set before you can: +The project now includes a `Dockerfile` and a `docker-compose.yml` file (which requires at least docker-compose version `1.10.0`). + +Review the settings in `docker-compose.yml`. Note that it is not default to store the postgresql database and redis databases in a persistent storage location, +so you may need or want to adjust the settings there. + +Before running the first time, you need to build the images: docker-compose build -And finally +Then, you need to fill in the `.env.production` file: - docker-compose up -d + cp .env.production.sample .env.production + nano .env.production -As usual, the first thing you would need to do would be to run migrations: +Do NOT change the `REDIS_*` or `DB_*` settings when running with the default docker configurations. + +You will need to fill in, at least: `LOCAL_DOMAIN`, `LOCAL_HTTPS`, `PAPERCLIP_SECRET`, `SECRET_KEY_BASE`, `OTP_SECRET`, and the `SMTP_*` settings. To generate the `PAPERCLIP_SECRET`, `SECRET_KEY_BASE`, and `OTP_SECRET`, you may use: + + docker-compose run --rm web rake secret + +Do this once for each of those keys, and copy the result into the `.env.production` file in the appropriate field. + +Then you should run the `db:migrate` command to create the database, or migrate it from an older release: docker-compose run --rm web rails db:migrate -And since the instance running in the container will be running in production mode, you need to pre-compile assets: +Then, you will also need to precompile the assets: docker-compose run --rm web rails assets:precompile -The container has two volumes, for the assets and for user uploads. The default docker-compose.yml maps them to the repository's `public/assets` and `public/system` directories, you may wish to put them somewhere else. Likewise, the PostgreSQL and Redis images have data containers that you may wish to map somewhere where you know how to find them and back them up. +before you can launch the docker image with: + + docker-compose up + +If you wish to run this as a daemon process instead of monitoring it on console, use instead: + + docker-compose up -d + +Then you may login to your new Mastodon instance by browsing to http://localhost:3000/ + +Following that, make sure that you read the [production guide](docs/Running-Mastodon/Production-guide.md). You are probably going to want to understand how +to configure Nginx to make your Mastodon instance available to the rest of the world. + +The container has two volumes, for the assets and for user uploads, and optionally two more, for the postgresql and redis databases. + +The default docker-compose.yml maps them to the repository's `public/assets` and `public/system` directories, you may wish to put them somewhere else. Likewise, the PostgreSQL and Redis images have data containers that you may wish to map somewhere where you know how to find them and back them up. **Note**: The `--rm` option for docker-compose will remove the container that is created to run a one-off command after it completes. As data is stored in volumes it is not affected by that container clean-up. @@ -103,17 +132,11 @@ Running any of these tasks via docker-compose would look like this: This approach makes updating to the latest version a real breeze. - git pull - -To pull down the updates, re-run - - docker-compose build - -And finally, - - docker-compose up -d - -Which will re-create the updated containers, leaving databases and data as is. Depending on what files have been updated, you might need to re-run migrations and asset compilation. +1. `git pull` to download updates from the repository +2. `docker-compose build` to compile the Docker image out of the changed source files +3. (optional) `docker-compose run --rm web rails db:migrate` to perform database migrations. Does nothing if your database is up to date +4. (optional) `docker-compose run --rm web rails assets:precompile` to compile new JS and CSS assets +5. `docker-compose up -d` to re-create (restart) containers and pick up the changes ## Deployment without Docker @@ -129,7 +152,7 @@ Docker is great for quickly trying out software, but it has its drawbacks too. I [](https://heroku.com/deploy) -Mastodon can theoretically run indefinitely on a free [Heroku](https://heroku.com) app. [You can view a guide for deployment on Heroku here.](https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Heroku-guide.md) +Mastodon can run on [Heroku](https://heroku.com), but it gets expensive and impractical due to how Heroku prices resource usage. [You can view a guide for deployment on Heroku here](https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Heroku-guide.md), but you have been warned. ## Development with Vagrant diff --git a/app/assets/images/logo.svg b/app/assets/images/logo.svg index 5379a02404..52bf86b0ee 100644 --- a/app/assets/images/logo.svg +++ b/app/assets/images/logo.svg @@ -1,4 +1 @@ - + \ No newline at end of file diff --git a/app/assets/javascripts/components/containers/mastodon.jsx b/app/assets/javascripts/components/containers/mastodon.jsx index d48bb2ba98..d8810dc644 100644 --- a/app/assets/javascripts/components/containers/mastodon.jsx +++ b/app/assets/javascripts/components/containers/mastodon.jsx @@ -41,15 +41,18 @@ import Report from '../features/report'; import { IntlProvider, addLocaleData } from 'react-intl'; import en from 'react-intl/locale-data/en'; import de from 'react-intl/locale-data/de'; -import es from 'react-intl/locale-data/es'; -import fr from 'react-intl/locale-data/fr'; -import pt from 'react-intl/locale-data/pt'; -import hu from 'react-intl/locale-data/hu'; -import uk from 'react-intl/locale-data/uk'; -import fi from 'react-intl/locale-data/fi'; import eo from 'react-intl/locale-data/eo'; -import ru from 'react-intl/locale-data/ru'; +import es from 'react-intl/locale-data/es'; +import fi from 'react-intl/locale-data/fi'; +import fr from 'react-intl/locale-data/fr'; +import hu from 'react-intl/locale-data/hu'; import ja from 'react-intl/locale-data/ja'; +import pt from 'react-intl/locale-data/pt'; +import no from 'react-intl/locale-data/no'; +import ru from 'react-intl/locale-data/ru'; +import uk from 'react-intl/locale-data/uk'; +import zh from 'react-intl/locale-data/zh'; +import { localeData as zh_hk } from '../locales/zh-hk'; import getMessagesForLocale from '../locales'; import { hydrateStore } from '../actions/store'; @@ -64,7 +67,22 @@ const browserHistory = useRouterHistory(createBrowserHistory)({ }); -addLocaleData([...en, ...de, ...es, ...fr, ...pt, ...hu, ...uk, ...fi, ...eo, ...ru, ...ja]); +addLocaleData([ + ...en, + ...de, + ...eo, + ...es, + ...fi, + ...fr, + ...hu, + ...ja, + ...pt, + ...no, + ...ru, + ...uk, + ...zh, + ...zh_hk, +]); const Mastodon = React.createClass({ diff --git a/app/assets/javascripts/components/features/getting_started/index.jsx b/app/assets/javascripts/components/features/getting_started/index.jsx index d7a78d9cc2..0656bf69af 100644 --- a/app/assets/javascripts/components/features/getting_started/index.jsx +++ b/app/assets/javascripts/components/features/getting_started/index.jsx @@ -43,7 +43,7 @@ const GettingStarted = ({ intl, me }) => {
diff --git a/app/assets/javascripts/components/locales/es.jsx b/app/assets/javascripts/components/locales/es.jsx index b75fb57d92..7e9c0dc2ce 100644 --- a/app/assets/javascripts/components/locales/es.jsx +++ b/app/assets/javascripts/components/locales/es.jsx @@ -5,28 +5,35 @@ const es = { "status.mention": "Mencionar", "status.delete": "Borrar", "status.reply": "Responder", - "status.reblog": "Republicar", + "status.reblog": "Retoot", "status.favourite": "Favorito", - "status.reblogged_by": "{name} republicado", + "status.reblogged_by": "Retooteado por {name}", + "status.sensitive_warning": "Contenido sensible", + "status.sensitive_toggle": "Click para ver", + "status.show_more": "Mostrar más", + "status.show_less": "Mostrar menos", + "status.open": "Expandir estado", + "status.report": "Reportar", "video_player.toggle_sound": "Act/Desac. sonido", - "account.mention": "Mención", + "account.mention": "Mencionar", "account.edit_profile": "Editar perfil", "account.unblock": "Desbloquear", "account.unfollow": "Dejar de seguir", + "account.mute": "Silenciar", "account.block": "Bloquear", "account.follow": "Seguir", - "account.block": "Bloquear", "account.posts": "Publicaciones", "account.follows": "Seguir", "account.followers": "Seguidores", "account.follows_you": "Te sigue", + "account.requested": "Esperando aprobación", "getting_started.heading": "Primeros pasos", "getting_started.about_addressing": "Puedes seguir a gente si conoces su nombre de usuario y el dominio en el que están registrados, introduciendo algo similar a una dirección de correo electrónico en el formulario en la parte superior de la barra lateral.", "getting_started.about_shortcuts": "Si el usuario que buscas está en el mismo dominio que tú, simplemente funcionará introduciendo el nombre de usuario. La misma regla se aplica para mencionar a usuarios.", - "getting_started.about_developer": "Puedes seguir al desarrollador de este proyecto en Gargron@mastodon.social", + "getting_started.open_source_notice": "Mastodon es software libre. Puedes contribuir o reportar errores en {github}. {apps}.", "column.home": "Inicio", - "column.mentions": "Menciones", - "column.public": "Historia pública", + "column.community": "Historia local", + "column.public": "Historia federada", "column.notifications": "Notificaciones", "tabs_bar.compose": "Redactar", "tabs_bar.home": "Inicio", @@ -34,23 +41,47 @@ const es = { "tabs_bar.public": "Público", "tabs_bar.notifications": "Notificaciones", "compose_form.placeholder": "¿En qué estás pensando?", - "compose_form.publish": "Publicar", - "compose_form.sensitive": "Marcar el contenido como sensible", - "compose_form.unlisted": "Privado", + "compose_form.publish": "Tootear", + "compose_form.sensitive": "Marcar contenido como sensible", + "compose_form.spoiler": "Ocultar texto tras advertencia", + "compose_form.spoiler_placeholder": "Advertencia de contenido", + "composer_form.private": "Marcar como privado", + "composer_form.privacy_disclaimer": "Tu estado se mostrará a los usuarios mencionados en {domains}. Tu estado podrá ser visto en otras instancias, quizás no quieras que tu estado sea visto por otros usuarios.", + "compose_form.unlisted": "No mostrar en la historia federada", "navigation_bar.edit_profile": "Editar perfil", "navigation_bar.preferences": "Preferencias", - "navigation_bar.public_timeline": "Público", + "navigation_bar.community_timeline": "Historia local", + "navigation_bar.public_timeline": "Historia federada", + "navigation_bar.favourites": "Favoritos", + "navigation_bar.blocks": "Usuarios bloqueados", + "navigation_bar.info": "Información adicional", "navigation_bar.logout": "Cerrar sesión", "reply_indicator.cancel": "Cancelar", "search.placeholder": "Buscar", "search.account": "Cuenta", "search.hashtag": "Etiqueta", - "upload_button.label": "Añadir medio", + "upload_button.label": "Subir multimedia", "upload_form.undo": "Deshacer", - "notification.follow": "{name} le esta ahora siguiendo", - "notification.favourite": "{name} marcó como favorito su estado", - "notification.reblog": "{name} volvió a publicar su estado", - "notification.mention": "Fue mencionado por {name}" + "notification.follow": "{name} te empezó a seguir", + "notification.favourite": "{name} marcó tu estado como favorito", + "notification.reblog": "{name} ha retooteado tu estado", + "notification.mention": "{name} te ha mencionado", + "notifications.column_settings.alert": "Notificaciones de escritorio", + "notifications.column_settings.show": "Mostrar en columna", + "notifications.column_settings.follow": "Nuevos seguidores:", + "notifications.column_settings.favourite": "Favoritos:", + "notifications.column_settings.mention": "Menciones:", + "notifications.column_settings.reblog": "Retoots:", + "emoji_button.label": "Insertar emoji", + "privacy.public.short": "Público", + "privacy.public.long": "Mostrar en la historia federada", + "privacy.unlisted.short": "Sin federar", + "privacy.unlisted.long": "No mostrar en la historia federada", + "privacy.private.short": "Privado", + "privacy.private.long": "Sólo mostrar a seguidores", + "privacy.direct.short": "Directo", + "privacy.direct.long": "Sólo mostrar a los usuarios mencionados", + "privacy.change": "Ajustar privacidad" }; export default es; diff --git a/app/assets/javascripts/components/locales/fr.jsx b/app/assets/javascripts/components/locales/fr.jsx index 9dff8f2ba3..8838c264f0 100644 --- a/app/assets/javascripts/components/locales/fr.jsx +++ b/app/assets/javascripts/components/locales/fr.jsx @@ -34,7 +34,7 @@ const fr = { "account.report": "Signaler", "account.disclaimer": "Ce compte est situé sur une autre instance. Les nombres peuvent être plus grands.", "getting_started.heading": "Pour commencer", - "getting_started.about_addressing": "Vous pouvez suivre les statuts de quelqu’un en entrant dans le champs de recherche leur identifiant et le domaine de leur instance, séparés par un @ à la manière d’une adresse courriel.", + "getting_started.about_addressing": "Vous pouvez suivre les statuts de quelqu’un en entrant dans le champ de recherche leur identifiant et le domaine de leur instance, séparés par un @ à la manière d’une adresse courriel.", "getting_started.about_shortcuts": "Si cette personne utilise la même instance que vous, l’identifiant suffit. C’est le même principe pour mentionner quelqu’un dans vos statuts.", "getting_started.about_developer": "Pour suivre le développeur de ce projet, c’est Gargron@mastodon.social", "getting_started.open_source_notice": "Mastodon est un logiciel libre. Vous pouvez contribuer et envoyer vos commentaires et rapports de bogues via {github} sur GitHub.", @@ -107,7 +107,7 @@ const fr = { "privacy.private.short": "Privé", "privacy.private.long": "N’afficher que pour vos abonné⋅e⋅s", "privacy.direct.short": "Direct", - "privacy.direct.long": "N’afficher que pour les personnes mentionné⋅e⋅s", + "privacy.direct.long": "N’afficher que pour les personnes mentionnées", "privacy.change": "Ajuster la confidentialité du message", "media_gallery.toggle_visible": "Modifier la visibilité", "missing_indicator.label": "Non trouvé", diff --git a/app/assets/javascripts/components/locales/index.jsx b/app/assets/javascripts/components/locales/index.jsx index da85240b7e..e772c10744 100644 --- a/app/assets/javascripts/components/locales/index.jsx +++ b/app/assets/javascripts/components/locales/index.jsx @@ -3,12 +3,14 @@ import de from './de'; import es from './es'; import hu from './hu'; import fr from './fr'; +import no from './no'; import pt from './pt'; import uk from './uk'; import fi from './fi'; import eo from './eo'; import ru from './ru'; import ja from './ja'; +import zh_hk from './zh-hk'; const locales = { @@ -17,13 +19,14 @@ const locales = { es, hu, fr, + no, pt, uk, fi, eo, ru, - ja - + ja, + 'zh-HK': zh_hk, }; export default function getMessagesForLocale (locale) { diff --git a/app/assets/javascripts/components/locales/ja.jsx b/app/assets/javascripts/components/locales/ja.jsx index ac45120399..25a6f7f675 100644 --- a/app/assets/javascripts/components/locales/ja.jsx +++ b/app/assets/javascripts/components/locales/ja.jsx @@ -2,42 +2,45 @@ const ja = { "column_back_button.label": "戻る", "lightbox.close": "閉じる", "loading_indicator.label": "読み込み中...", - "status.mention": "@{name}さんへの返信", + "status.mention": "@{name} さんへの返信", "status.delete": "削除", "status.reply": "返信", "status.reblog": "ブースト", "status.favourite": "お気に入り", - "status.reblogged_by": "{name}さんにブーストされました", + "status.reblogged_by": "{name} さんにブーストされました", "status.sensitive_warning": "不適切なコンテンツ", - "status.sensitive_toggle": "見るにはクリック", + "status.sensitive_toggle": "クリックして表示", "status.show_more": "もっと見る", "status.show_less": "隠す", "status.open": "Expand this status", - "status.report": "@{name}さんを報告", - "video_player.toggle_sound": "音切り替え", - "account.mention": "@{name}さんに返信", - "account.edit_profile": "プロフィール返信", - "account.unblock": "@{name}さんのブロックを解除", + "status.report": "@{name} さんを報告", + "video_player.toggle_sound": "音の切り替え", + "account.mention": "@{name} さんに返信", + "account.edit_profile": "プロフィールを編集", + "account.unblock": "@{name} さんのブロックを解除", "account.unfollow": "フォロー解除", - "account.block": "@{name}さんをブロック", + "account.block": "@{name} さんをブロック", + "account.mute": "ミュート", + "account.unmute": "ミュート解除", "account.follow": "フォロー", "account.posts": "投稿", "account.follows": "フォロー", "account.followers": "フォロワー", - "account.follows_you": "フォロー中", + "account.follows_you": "フォローされています", "account.requested": "承認待ち", "getting_started.heading": "スタート", "getting_started.about_addressing": "ドメインとユーザー名を知っているなら検索フォームに入力すればフォローできます。", "getting_started.about_shortcuts": "対象のアカウントがあなたと同じドメインのユーザーならばユーザー名のみで検索できます。これは返信のときも一緒です。", - "getting_started.open_source_notice": "Mastodon はオープンソースのソフトウェアです。誰でもGitHub({github})から開発に参加したり、問題を報告したりできます。 {apps}", + "getting_started.open_source_notice": "Mastodon はオープンソースソフトウェアです。誰でも GitHub({github})から開発に参加したり、問題を報告したりできます。 {apps}", "column.home": "ホーム", "column.community": "ローカルタイムライン", - "column.public": "連邦タイムライン", + "column.public": "連合タイムライン", "column.notifications": "通知", - "tabs_bar.compose": "Compose", + "tabs_bar.compose": "投稿", "tabs_bar.home": "ホーム", "tabs_bar.mentions": "返信", - "tabs_bar.public": "連邦タイムライン", + "tabs_bar.local_timeline": "ローカルTL", + "tabs_bar.federated_timeline": "連合TL", "tabs_bar.notifications": "通知", "compose_form.placeholder": "今なにしてる?", "compose_form.publish": "トゥート", @@ -46,27 +49,35 @@ const ja = { "compose_form.private": "非公開にする", "compose_form.privacy_disclaimer": "あなたの非公開トゥートは返信先のユーザー(at {domains})に公開されます。{domainsCount, plural, one {that server} other {those servers}}を信頼しますか?投稿のプライバシー保護はMastodonサーバー内でのみ有効です。 もし{domains} {domainsCount, plural, one {is not a Mastodon instance} other {are not Mastodon instances}}ならばあなたの投稿のプライバシーは保護されず、ブーストされたり予期しないユーザーに見られる可能性があります。", "compose_form.unlisted": "公開タイムラインに表示しない", - "navigation_bar.edit_profile": "プロフィール編集", + "navigation_bar.edit_profile": "プロフィールを編集", "navigation_bar.preferences": "ユーザー設定", "navigation_bar.community_timeline": "ローカルタイムライン", - "navigation_bar.public_timeline": "連邦タイムライン", + "navigation_bar.public_timeline": "連合タイムライン", "navigation_bar.logout": "ログアウト", + "navigation_bar.favourites": "お気に入り", + "navigation_bar.blocks": "ブロックしたユーザー", + "navigation_bar.info": "サーバー情報", "reply_indicator.cancel": "キャンセル", "search.placeholder": "検索", "search.account": "アカウント", "search.hashtag": "ハッシュタグ", "upload_button.label": "メディアを追加", "upload_form.undo": "やり直す", - "notification.follow": "{name}さんにフォローされました", - "notification.favourite": "{name}さんがあなたのトゥートをいいねしました", - "notification.reblog": "{name}さんがあなたのトゥートをブーストしました", - "notification.mention": "{name}さんがあなたに返信しました", + "notification.follow": "{name} さんにフォローされました", + "notification.favourite": "{name} さんがあなたのトゥートをお気に入りに登録しました", + "notification.reblog": "{name} さんがあなたのトゥートをブーストしました", + "notification.mention": "{name} さんがあなたに返信しました", "notifications.column_settings.alert": "デスクトップ通知", - "notifications.column_settings.show": "表示項目", - "notifications.column_settings.follow": "新しいフォロワー:", - "notifications.column_settings.favourite": "いいね:", - "notifications.column_settings.mention": "返信:", - "notifications.column_settings.reblog": "ブースト:", + "notifications.column_settings.show": "カラムに表示", + "notifications.column_settings.follow": "新しいフォロワー", + "notifications.column_settings.favourite": "お気に入り", + "notifications.column_settings.mention": "返信", + "notifications.column_settings.reblog": "ブースト", + "notifications.column_settings.sound": "通知音を再生", + "empty_column.home": "まだ誰もフォローしていません。{public}を見に行くか、検索を使って他のユーザーを見つけましょう。", + "empty_column.home.public_timeline": "連合タイムライン", + "empty_column.notifications": "まだ通知がありません。他の人とふれ合って会話を始めましょう。", + "empty_column.public": "ここにはまだ何もありません!公開で何かを投稿したり、他のインスタンスのユーザーをフォローしたりしていっぱいにしましょう!", }; export default ja; diff --git a/app/assets/javascripts/components/locales/no.jsx b/app/assets/javascripts/components/locales/no.jsx new file mode 100644 index 0000000000..3b937246bb --- /dev/null +++ b/app/assets/javascripts/components/locales/no.jsx @@ -0,0 +1,72 @@ +const no = { + "column_back_button.label": "Tilbake", + "lightbox.close": "Lukk", + "loading_indicator.label": "Laster...", + "status.mention": "Nevn @{name}", + "status.delete": "Slett", + "status.reply": "Svar", + "status.reblog": "Reblogg", + "status.favourite": "Favoritt", + "status.reblogged_by": "{name} reblogget", + "status.sensitive_warning": "Sensitivt innhold", + "status.sensitive_toggle": "Klikk for å vise", + "status.show_more": "Vis mer", + "status.show_less": "Vis mindre", + "status.open": "Utvid denne statusen", + "status.report": "Rapporter @{name}", + "video_player.toggle_sound": "Veksle lyd", + "account.mention": "Nevn @{name}", + "account.edit_profile": "Rediger profil", + "account.unblock": "Avblokker @{name}", + "account.unfollow": "Avfølg", + "account.block": "Blokker @{name}", + "account.follow": "Følg", + "account.posts": "Poster", + "account.follows": "Følginger", + "account.followers": "Følgere", + "account.follows_you": "Folger deg", + "account.requested": "Venter på godkjennelse", + "getting_started.heading": "Kom i gang", + "getting_started.about_addressing": "Du kan følge noen hvis du vet brukernavnet deres og domenet de er på ved å skrive en e-postadresse inn i søkeskjemaet.", + "getting_started.about_shortcuts": "Hvis målbrukeren er på samme domene som deg, vil kun brukernavnet også fungere. Den samme regelen gjelder når man nevner noen i statuser.", + "getting_started.open_source_notice": "Mastodon er programvare med fri kildekode. Du kan bidra eller rapportere problemer på GitHub på {github}. {apps}.", + "column.home": "Hjem", + "column.community": "Lokal tidslinje", + "column.public": "Føderert tidslinje", + "column.notifications": "Varslinger", + "tabs_bar.compose": "Komponer", + "tabs_bar.home": "Hjem", + "tabs_bar.mentions": "Nevninger", + "tabs_bar.public": "Føderert tidslinje", + "tabs_bar.notifications": "Varslinger", + "compose_form.placeholder": "Hva har du på hjertet?", + "compose_form.publish": "Tut", + "compose_form.sensitive": "Merk media som følsomt", + "compose_form.spoiler": "Skjul tekst bak advarsel", + "compose_form.private": "Merk som privat", + "compose_form.privacy_disclaimer": "Din private status vil leveres til nevnte brukere på {domains}. Stoler du på {domainsCount, plural, one {den serveren} other {de serverne}}? Synlighet fungerer kun på Mastodon-instanser. Hvis {domains} {domainsCount, plural, one {ike er en Mastodon-instans} other {ikke er Mastodon-instanser}}, vil det ikke indikeres at posten din er privat, og den kan kanskje bli reblogget eller på annen måte bli synlig for uventede mottakere.", + "compose_form.unlisted": "Ikke vis på offentlige tidslinjer", + "navigation_bar.edit_profile": "Rediger profil", + "navigation_bar.preferences": "Preferanser", + "navigation_bar.community_timeline": "Lokal tidslinje", + "navigation_bar.public_timeline": "Føderert tidslinje", + "navigation_bar.logout": "Logg ut", + "reply_indicator.cancel": "Avbryt", + "search.placeholder": "Søk", + "search.account": "Konto", + "search.hashtag": "Hashtag", + "upload_button.label": "Legg til media", + "upload_form.undo": "Angre", + "notification.follow": "{name} fulgte deg", + "notification.favourite": "{name} likte din status", + "notification.reblog": "{name} reblogget din status", + "notification.mention": "{name} nevnte deg", + "notifications.column_settings.alert": "Skrivebordsvarslinger", + "notifications.column_settings.show": "Vis i kolonne", + "notifications.column_settings.follow": "Nye følgere:", + "notifications.column_settings.favourite": "Favouritter:", + "notifications.column_settings.mention": "Nevninger:", + "notifications.column_settings.reblog": "Reblogginger:", +}; + +export default no; diff --git a/app/assets/javascripts/components/locales/zh-hk.jsx b/app/assets/javascripts/components/locales/zh-hk.jsx new file mode 100644 index 0000000000..b26a8c3dd6 --- /dev/null +++ b/app/assets/javascripts/components/locales/zh-hk.jsx @@ -0,0 +1,113 @@ +import zh from 'react-intl/locale-data/zh'; + +const localeData = zh.reduce(function (acc, localeData) { + if (localeData.locale === "zh-Hant-HK") { + // rename the locale "zh-Hant-HK" as "zh-HK" + // (match the code usually used in Accepted-Language header) + acc.push(Object.assign({}, + localeData, + { + "locale": "zh-HK", + "parentLocale": "zh-Hant-HK", + } + )); + } + return acc; +}, []); + +export { localeData as localeData }; + +const zh_hk = { + "account.block": "封鎖 @{name}", + "account.edit_profile": "修改個人資料", + "account.follow": "關注", + "account.followers": "關注的人", + "account.follows_you": "關注你", + "account.follows": "正在關注", + "account.mention": "提及 @{name}", + "account.posts": "文章", + "account.requested": "等候審批", + "account.unblock": "解除對 @{name} 的封鎖", + "account.unfollow": "取消關注", + "column_back_button.label": "先前顯示", + "column.community": "本站時間軸", + "column.home": "家", + "column.notifications": "通知", + "column.public": "跨站公共時間軸", + "compose_form.placeholder": "你在想甚麼?", + "compose_form.privacy_disclaimer": "你的私人文章,將被遞送至你所提及的 {domains} 用戶。你是否信任 {domainsCount, plural, one {這個網站} other {這些網站}}?請留意,文章私隱設定只適用於各 Mastodon 服務站,如果 {domains} {domainsCount, plural, one {不是 Mastodon 服務站} other {之中有些不是 Mastodon 服務站}},對方將無法收到這篇文章的私隱設定,然後可能被轉推給不能預知的用戶閱讀。", + "compose_form.private": "標示為「只有關注你的人能看」", + "compose_form.publish": "發文", + "compose_form.sensitive": "將媒體檔案標示為「敏感內容」", + "compose_form.spoiler": "將部份文字藏於警告訊息之後", + "compose_form.unlisted": "請勿在公共時間軸顯示", + "empty_column.community": "本站時間軸暫時未有內容,快貼文來搶頭香啊!", + "empty_column.hashtag": "這個標籤暫時未有內容。", + "empty_column.home": "你還沒有關注任何用戶。快看看{public},向其他用戶搭訕吧。", + "empty_column.home.public_timeline": "公共時間軸", + "empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.", + "empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up.", + "getting_started.about_addressing": "只要你知道一位用戶的用戶名稱和域名,你可以用「@用戶名稱@域名」的格式在搜尋欄尋找該用戶。", + "getting_started.about_shortcuts": "只要該用戶是在你現在的服務站開立,你可以直接輸入用戶𠱷搜尋。同樣的規則適用於在文章提及別的用戶。", + "getting_started.apps": "手機或桌面應用程式", + "getting_started.heading": "開始使用", + "getting_started.open_source_notice": "Mastodon 是一個開放源碼的軟件。你可以在官方 GitHub ({github}) 貢獻或者回報問題。你亦可透過{apps}閱讀 Mastodon 上的消息。", + "home.column_settings.basic": "基本", + "home.column_settings.show_reblogs": "顯示被轉推的文章", + "home.column_settings.show_replies": "顯示回應文章", + "home.column_settings.advanced": "進階", + "lightbox.close": "關閉", + "loading_indicator.label": "載入中...", + "missing_indicator.label": "找不到內容", + "navigation_bar.community_timeline": "本站時間軸", + "navigation_bar.edit_profile": "修改個人資料", + "navigation_bar.logout": "登出", + "navigation_bar.preferences": "個人設定", + "navigation_bar.public_timeline": "跨站公共時間軸", + "notification.favourite": "{name} 喜歡你的文章", + "notification.follow": "{name} 開始開始你", + "notification.mention": "{name} 提及你", + "notification.reblog": "{name} 轉推你的文章", + "notifications.column_settings.alert": "顯示桌面通知", + "notifications.column_settings.favourite": "喜歡你的文章:", + "notifications.column_settings.follow": "關注你:", + "notifications.column_settings.mention": "提及你:", + "notifications.column_settings.reblog": "轉推你的文章:", + "notifications.column_settings.show": "在通知欄顯示", + "notifications.column_settings.sound": "播放音效", + "reply_indicator.cancel": "取消", + "report.target": "Reporting", + "search.account": "用戶", + "search.hashtag": "標籤", + "search.placeholder": "搜尋", + "search_results.total": "{count} 項結果", + "search.status_by": "按用戶名稱搜尋文章", + "status.delete": "刪除", + "status.favourite": "喜歡", + "status.load_more": "載入更多", + "status.media_hidden": "隱藏媒體內容", + "status.mention": "提及 @{name}", + "status.open": "展開文章", + "status.reblog": "轉推", + "status.reblogged_by": "{name} 轉推", + "status.reply": "回應", + "status.report": "舉報 @{name}", + "status.sensitive_toggle": "點擊顯示", + "status.sensitive_warning": "敏感內容", + "status.show_less": "減少顯示", + "status.show_more": "顯示更多", + "tabs_bar.compose": "撰寫", + "tabs_bar.home": "家", + "tabs_bar.local_timeline": "本站", + "tabs_bar.mentions": "提及", + "tabs_bar.notifications": "通知", + "tabs_bar.public": "跨站公共時間軸", + "tabs_bar.federated_timeline": "跨站", + "upload_area.title": "將檔案拖放至此上載", + "upload_button.label": "上載媒體檔案", + "upload_progress.label": "上載中……", + "upload_form.undo": "還原", + "video_player.toggle_sound": "開關音效", +}; + +export default zh_hk; diff --git a/app/assets/javascripts/components/reducers/compose.jsx b/app/assets/javascripts/components/reducers/compose.jsx index 4470ad6433..00cc758a56 100644 --- a/app/assets/javascripts/components/reducers/compose.jsx +++ b/app/assets/javascripts/components/reducers/compose.jsx @@ -76,7 +76,8 @@ function appendMedia(state, media) { map.update('media_attachments', list => list.push(media)); map.set('is_uploading', false); map.set('resetFileKey', Math.floor((Math.random() * 0x10000))); - map.update('text', oldText => `${oldText} ${media.get('text_url')}`.trim()); + map.set('focusDate', new Date()); + map.update('text', oldText => `${oldText.trim()} ${media.get('text_url')}`.trim() + ' '); }); }; diff --git a/app/assets/stylesheets/accounts.scss b/app/assets/stylesheets/accounts.scss index 407e917b28..f9a698824b 100644 --- a/app/assets/stylesheets/accounts.scss +++ b/app/assets/stylesheets/accounts.scss @@ -14,7 +14,7 @@ } &:after { - background: rgba($color8, 0.5); + background: linear-gradient(rgba($color8, 0.5), rgba($color8, 0.8)); display: block; content: ""; position: absolute; @@ -72,7 +72,6 @@ position: relative; z-index: 2; flex-direction: row; - background: rgba(0,0,0,0.5); } .details-counters { @@ -388,6 +387,5 @@ .account__header__content { font-size: 14px; color: $color1; - text-shadow: 0 0 2px $color8; } } diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index ba16d4a211..35bdd3b6a3 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -4,305 +4,13 @@ @import 'fonts/montserrat'; @import 'font-awesome'; -/* http://meyerweb.com/eric/tools/css/reset/ - v2.0 | 20110126 - License: none (public domain) -*/ - -html, body, div, span, applet, object, iframe, -h1, h2, h3, h4, h5, h6, p, blockquote, pre, -a, abbr, acronym, address, big, cite, code, -del, dfn, em, img, ins, kbd, q, s, samp, -small, strike, strong, sub, sup, tt, var, -b, u, i, center, -dl, dt, dd, ol, ul, li, -fieldset, form, label, legend, -table, caption, tbody, tfoot, thead, tr, th, td, -article, aside, canvas, details, embed, -figure, figcaption, footer, header, hgroup, -menu, nav, output, ruby, section, summary, -time, mark, audio, video { - margin: 0; - padding: 0; - border: 0; - font-size: 100%; - font: inherit; - vertical-align: baseline; -} - -/* HTML5 display-role reset for older browsers */ -article, aside, details, figcaption, figure, -footer, header, hgroup, menu, nav, section { - display: block; -} - -body { - line-height: 1; -} - -ol, ul { - list-style: none; -} - -blockquote, q { - quotes: none; -} - -blockquote:before, blockquote:after, -q:before, q:after { - content: ''; - content: none; -} - -table { - border-collapse: collapse; - border-spacing: 0; -} - -::-webkit-scrollbar { - width: 8px; - height: 8px; -} - -::-webkit-scrollbar-thumb { - background: lighten($color1, 4%); - border: 0px none $color5; - border-radius: 50px; -} - -::-webkit-scrollbar-thumb:hover { - background: lighten($color1, 6%); -} - -::-webkit-scrollbar-thumb:active { - background: lighten($color1, 4%); -} - -::-webkit-scrollbar-track { - border: 0px none $color5; - border-radius: 0; - background: rgba($color8, 0.1); -} - -::-webkit-scrollbar-track:hover { - background: $color1; -} - -::-webkit-scrollbar-track:active { - background: $color1; -} - -::-webkit-scrollbar-corner { - background: transparent; -} - -body { - font-family: 'Roboto', sans-serif; - background: $color1 image-url('background-photo.jpeg'); - background-size: cover; - background-attachment: fixed; - font-size: 13px; - line-height: 18px; - font-weight: 400; - color: $color5; - padding-bottom: 140px; - text-rendering: optimizelegibility; - font-feature-settings: "kern"; - text-size-adjust: none; - - &.app-body { - position: fixed; - width: 100%; - height: 100%; - padding: 0; - background: $color1; - } - - &.embed { - background: transparent; - margin: 0; - - .container { - position: absolute; - width: 100%; - height: 100%; - overflow: hidden; - } - } - - &.admin { - background: darken($color1, 4%); - position: fixed; - width: 100%; - height: 100%; - padding: 0; - } - - @media screen and (max-width: 360px) { - padding-bottom: 0; - } -} - -button:focus { - outline: none; -} - -.app-holder { - display: flex; - width: 100%; - height: 100%; - align-items: center; - justify-content: center; -} - -.container { - width: 700px; - margin: 0 auto; - margin-top: 40px; - - @media screen and (max-width: 700px) { - width: 100%; - margin: 0; - } -} - -.logo-container { - max-width: 400px; - margin: 100px auto; - margin-bottom: 0; - cursor: default; - - @media screen and (max-width: 360px) { - margin: 30px auto; - } - - h1 { - display: block; - text-align: center; - color: $color5; - font-size: 48px; - font-weight: 500; - - img { - display: block; - margin: 20px auto; - width: 180px; - height: 180px; - } - - a { - color: inherit; - text-decoration: none; - outline: 0; - - img { - opacity: 0.8; - transition: all 0.8s ease; - } - - &:hover { - img { - opacity: 1; - transition-duration: 0.2s; - } - } - } - - small { - display: block; - font-size: 12px; - font-weight: 400; - font-family: 'Roboto Mono', monospace; - } - } -} - -.no-list { - list-style: none; - - li { - display: inline-block; - margin: 0 5px; - } -} - -.footer { - text-align: center; - margin-top: 30px; - font-size: 12px; - color: darken($color2, 25%); - - .domain { - font-weight: 500; - - a { - color: inherit; - text-decoration: none; - } - } - - .powered-by { - font-weight: 400; - - a { - color: inherit; - text-decoration: underline; - font-weight: 500; - - &:hover { - text-decoration: none; - } - } - } -} - -.compact-header { - h1 { - font-size: 24px; - line-height: 28px; - color: $color3; - overflow: hidden; - font-weight: 500; - margin-bottom: 20px; - - a { - color: inherit; - text-decoration: none; - } - - small { - font-weight: 400; - color: $color2; - } - - img { - display: inline-block; - margin-bottom: -5px; - margin-right: 15px; - width: 36px; - height: 36px; - } - } -} - -.landing-strip { - background: rgba(darken($color1, 7%), 0.8); - color: $color3; - font-weight: 400; - padding: 14px; - border-radius: 4px; - margin-bottom: 20px; - - strong, a { - font-weight: 500; - } - - a { - color: inherit; - text-decoration: underline; - } -} - +@import 'reset'; +@import 'basics'; +@import 'containers'; +@import 'lists'; +@import 'footer'; +@import 'compact_header'; +@import 'landing_strip'; @import 'forms'; @import 'accounts'; @import 'stream_entries'; diff --git a/app/assets/stylesheets/basics.scss b/app/assets/stylesheets/basics.scss new file mode 100644 index 0000000000..c8ff7f5d52 --- /dev/null +++ b/app/assets/stylesheets/basics.scss @@ -0,0 +1,58 @@ +body { + font-family: 'Roboto', sans-serif; + background: $color1 image-url('background-photo.jpeg'); + background-size: cover; + background-attachment: fixed; + font-size: 13px; + line-height: 18px; + font-weight: 400; + color: $color5; + padding-bottom: 140px; + text-rendering: optimizelegibility; + font-feature-settings: "kern"; + text-size-adjust: none; + + &.app-body { + position: fixed; + width: 100%; + height: 100%; + padding: 0; + background: $color1; + } + + &.embed { + background: transparent; + margin: 0; + + .container { + position: absolute; + width: 100%; + height: 100%; + overflow: hidden; + } + } + + &.admin { + background: darken($color1, 4%); + position: fixed; + width: 100%; + height: 100%; + padding: 0; + } + + @media screen and (max-width: 360px) { + padding-bottom: 0; + } +} + +button:focus { + outline: none; +} + +.app-holder { + display: flex; + width: 100%; + height: 100%; + align-items: center; + justify-content: center; +} diff --git a/app/assets/stylesheets/compact_header.scss b/app/assets/stylesheets/compact_header.scss new file mode 100644 index 0000000000..8fef05f0fe --- /dev/null +++ b/app/assets/stylesheets/compact_header.scss @@ -0,0 +1,28 @@ +.compact-header { + h1 { + font-size: 24px; + line-height: 28px; + color: $color3; + overflow: hidden; + font-weight: 500; + margin-bottom: 20px; + + a { + color: inherit; + text-decoration: none; + } + + small { + font-weight: 400; + color: $color2; + } + + img { + display: inline-block; + margin-bottom: -5px; + margin-right: 15px; + width: 36px; + height: 36px; + } + } +} diff --git a/app/assets/stylesheets/components.scss b/app/assets/stylesheets/components.scss index 8e4a667e35..1c7f375b74 100644 --- a/app/assets/stylesheets/components.scss +++ b/app/assets/stylesheets/components.scss @@ -59,7 +59,7 @@ z-index: 2; } -@media screen and (min-width: 1024px) { +@media screen and (min-width: 1025px) { .column-icon-clear { top: 10px; } @@ -857,7 +857,7 @@ a.status__content__spoiler-link { } } -@media screen and (min-width: 1024px) { +@media screen and (min-width: 1025px) { .columns-area { padding: 0; } diff --git a/app/assets/stylesheets/containers.scss b/app/assets/stylesheets/containers.scss new file mode 100644 index 0000000000..43705b19c9 --- /dev/null +++ b/app/assets/stylesheets/containers.scss @@ -0,0 +1,61 @@ +.container { + width: 700px; + margin: 0 auto; + margin-top: 40px; + + @media screen and (max-width: 700px) { + width: 100%; + margin: 0; + } +} + +.logo-container { + max-width: 400px; + margin: 100px auto; + margin-bottom: 0; + cursor: default; + + @media screen and (max-width: 360px) { + margin: 30px auto; + } + + h1 { + display: block; + text-align: center; + color: $color5; + font-size: 48px; + font-weight: 500; + + img { + display: block; + margin: 20px auto; + width: 180px; + height: 180px; + } + + a { + color: inherit; + text-decoration: none; + outline: 0; + + img { + opacity: 0.8; + transition: all 0.8s ease; + } + + &:hover { + img { + opacity: 1; + transition-duration: 0.2s; + } + } + } + + small { + display: block; + font-size: 12px; + font-weight: 400; + font-family: 'Roboto Mono', monospace; + } + } +} diff --git a/app/assets/stylesheets/footer.scss b/app/assets/stylesheets/footer.scss new file mode 100644 index 0000000000..719bfcf435 --- /dev/null +++ b/app/assets/stylesheets/footer.scss @@ -0,0 +1,29 @@ +.footer { + text-align: center; + margin-top: 30px; + font-size: 12px; + color: darken($color2, 25%); + + .domain { + font-weight: 500; + + a { + color: inherit; + text-decoration: none; + } + } + + .powered-by { + font-weight: 400; + + a { + color: inherit; + text-decoration: underline; + font-weight: 500; + + &:hover { + text-decoration: none; + } + } + } +} diff --git a/app/assets/stylesheets/landing_strip.scss b/app/assets/stylesheets/landing_strip.scss new file mode 100644 index 0000000000..4c24031600 --- /dev/null +++ b/app/assets/stylesheets/landing_strip.scss @@ -0,0 +1,17 @@ +.landing-strip { + background: rgba(darken($color1, 7%), 0.8); + color: $color3; + font-weight: 400; + padding: 14px; + border-radius: 4px; + margin-bottom: 20px; + + strong, a { + font-weight: 500; + } + + a { + color: inherit; + text-decoration: underline; + } +} diff --git a/app/assets/stylesheets/lists.scss b/app/assets/stylesheets/lists.scss new file mode 100644 index 0000000000..eac9f5a2ca --- /dev/null +++ b/app/assets/stylesheets/lists.scss @@ -0,0 +1,8 @@ +.no-list { + list-style: none; + + li { + display: inline-block; + margin: 0 5px; + } +} diff --git a/app/assets/stylesheets/reset.scss b/app/assets/stylesheets/reset.scss new file mode 100644 index 0000000000..71064cc312 --- /dev/null +++ b/app/assets/stylesheets/reset.scss @@ -0,0 +1,91 @@ +/* http://meyerweb.com/eric/tools/css/reset/ + v2.0 | 20110126 + License: none (public domain) +*/ + +html, body, div, span, applet, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +a, abbr, acronym, address, big, cite, code, +del, dfn, em, img, ins, kbd, q, s, samp, +small, strike, strong, sub, sup, tt, var, +b, u, i, center, +dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td, +article, aside, canvas, details, embed, +figure, figcaption, footer, header, hgroup, +menu, nav, output, ruby, section, summary, +time, mark, audio, video { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; +} + +/* HTML5 display-role reset for older browsers */ +article, aside, details, figcaption, figure, +footer, header, hgroup, menu, nav, section { + display: block; +} + +body { + line-height: 1; +} + +ol, ul { + list-style: none; +} + +blockquote, q { + quotes: none; +} + +blockquote:before, blockquote:after, +q:before, q:after { + content: ''; + content: none; +} + +table { + border-collapse: collapse; + border-spacing: 0; +} + +::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +::-webkit-scrollbar-thumb { + background: lighten($color1, 4%); + border: 0px none $color5; + border-radius: 50px; +} + +::-webkit-scrollbar-thumb:hover { + background: lighten($color1, 6%); +} + +::-webkit-scrollbar-thumb:active { + background: lighten($color1, 4%); +} + +::-webkit-scrollbar-track { + border: 0px none $color5; + border-radius: 0; + background: rgba($color8, 0.1); +} + +::-webkit-scrollbar-track:hover { + background: $color1; +} + +::-webkit-scrollbar-track:active { + background: $color1; +} + +::-webkit-scrollbar-corner { + background: transparent; +} diff --git a/app/controllers/concerns/localized.rb b/app/controllers/concerns/localized.rb index bcf3fd0a0b..22bb5b21af 100644 --- a/app/controllers/concerns/localized.rb +++ b/app/controllers/concerns/localized.rb @@ -26,7 +26,7 @@ module Localized end def default_locale - ENV.fetch('DEFAULT_LOCALE') { + ENV.fetch('DEFAULT_LOCALE') { http_accept_language.compatible_language_from(I18n.available_locales) || I18n.default_locale } end diff --git a/app/controllers/settings/exports/base_controller.rb b/app/controllers/settings/exports/base_controller.rb new file mode 100644 index 0000000000..0b790959fe --- /dev/null +++ b/app/controllers/settings/exports/base_controller.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module Settings + module Exports + class BaseController < ApplicationController + before_action :authenticate_user! + + def index + export_data = Export.new(export_accounts).to_csv + + respond_to do |format| + format.csv { send_data export_data, filename: export_filename } + end + end + + private + + def export_filename + "#{controller_name}.csv" + end + end + end +end diff --git a/app/controllers/settings/exports/blocked_accounts_controller.rb b/app/controllers/settings/exports/blocked_accounts_controller.rb index 0bf8848b49..9c4bcaa532 100644 --- a/app/controllers/settings/exports/blocked_accounts_controller.rb +++ b/app/controllers/settings/exports/blocked_accounts_controller.rb @@ -2,15 +2,11 @@ module Settings module Exports - class BlockedAccountsController < ApplicationController - before_action :authenticate_user! + class BlockedAccountsController < BaseController + private - def index - export_data = Export.new(current_account.blocking).to_csv - - respond_to do |format| - format.csv { send_data export_data, filename: 'blocking.csv' } - end + def export_accounts + current_account.blocking end end end diff --git a/app/controllers/settings/exports/following_accounts_controller.rb b/app/controllers/settings/exports/following_accounts_controller.rb index a7f4344ca7..8d06bcc954 100644 --- a/app/controllers/settings/exports/following_accounts_controller.rb +++ b/app/controllers/settings/exports/following_accounts_controller.rb @@ -2,15 +2,11 @@ module Settings module Exports - class FollowingAccountsController < ApplicationController - before_action :authenticate_user! + class FollowingAccountsController < BaseController + private - def index - export_data = Export.new(current_account.following).to_csv - - respond_to do |format| - format.csv { send_data export_data, filename: 'following.csv' } - end + def export_accounts + current_account.following end end end diff --git a/app/controllers/settings/exports/muted_accounts_controller.rb b/app/controllers/settings/exports/muted_accounts_controller.rb new file mode 100644 index 0000000000..a77a9af6d6 --- /dev/null +++ b/app/controllers/settings/exports/muted_accounts_controller.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module Settings + module Exports + class MutedAccountsController < BaseController + private + + def export_accounts + current_account.muting + end + end + end +end diff --git a/app/controllers/settings/exports_controller.rb b/app/controllers/settings/exports_controller.rb index e060f03d3c..77dea32316 100644 --- a/app/controllers/settings/exports_controller.rb +++ b/app/controllers/settings/exports_controller.rb @@ -9,5 +9,6 @@ class Settings::ExportsController < ApplicationController @total_storage = current_account.media_attachments.sum(:file_file_size) @total_follows = current_account.following.count @total_blocks = current_account.blocking.count + @total_mutes = current_account.muting.count end end diff --git a/app/controllers/stream_entries_controller.rb b/app/controllers/stream_entries_controller.rb index 469a8c33e2..a9ee73500a 100644 --- a/app/controllers/stream_entries_controller.rb +++ b/app/controllers/stream_entries_controller.rb @@ -27,8 +27,6 @@ class StreamEntriesController < ApplicationController def embed response.headers['X-Frame-Options'] = 'ALLOWALL' - @external_links = true - return gone if @stream_entry.activity.nil? render layout: 'embedded' diff --git a/app/controllers/xrd_controller.rb b/app/controllers/xrd_controller.rb index 5964172e9a..2886315ac0 100644 --- a/app/controllers/xrd_controller.rb +++ b/app/controllers/xrd_controller.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class XrdController < ApplicationController - before_action :set_default_format_json, only: :webfinger before_action :set_default_format_xml, only: :host_meta def host_meta @@ -31,20 +30,8 @@ class XrdController < ApplicationController request.format = 'xml' if request.headers['HTTP_ACCEPT'].nil? && params[:format].nil? end - def set_default_format_json - request.format = 'json' if request.headers['HTTP_ACCEPT'].nil? && params[:format].nil? - end - def username_from_resource - if resource_param =~ /\Ahttps?:\/\// - path_params = Rails.application.routes.recognize_path(resource_param) - raise ActiveRecord::RecordNotFound unless path_params[:controller] == 'users' && path_params[:action] == 'show' - path_params[:username] - else - username, domain = resource_param.gsub(/\Aacct:/, '').split('@') - raise ActiveRecord::RecordNotFound unless TagManager.instance.local_domain?(domain) - username - end + WebfingerResource.new(resource_param).username end def pem_to_magic_key(public_key) diff --git a/app/helpers/admin/accounts_helper.rb b/app/helpers/admin/accounts_helper.rb index c539229b3d..6cda77819d 100644 --- a/app/helpers/admin/accounts_helper.rb +++ b/app/helpers/admin/accounts_helper.rb @@ -6,10 +6,21 @@ module Admin::AccountsHelper end def filter_link_to(text, more_params) - link_to text, filter_params(more_params), class: params.merge(more_params).compact == params.compact ? 'selected' : '' + new_url = filtered_url_for(more_params) + link_to text, new_url, class: filter_link_class(new_url) end def table_link_to(icon, text, path, options = {}) link_to safe_join([fa_icon(icon), text]), path, options.merge(class: 'table-action-link') end + + private + + def filter_link_class(new_url) + filtered_url_for(params) == new_url ? 'selected' : '' + end + + def filtered_url_for(params) + url_for filter_params(params) + end end diff --git a/app/helpers/settings_helper.rb b/app/helpers/settings_helper.rb index 8a94df5f4d..211b570429 100644 --- a/app/helpers/settings_helper.rb +++ b/app/helpers/settings_helper.rb @@ -6,15 +6,16 @@ module SettingsHelper de: 'Deutsch', es: 'Español', eo: 'Esperanto', - pt: 'Português', fr: 'Français', hu: 'Magyar', - uk: 'Українська', - 'zh-CN': '简体中文', + no: 'Norsk', + pt: 'Português', fi: 'Suomi', ru: 'Русский', + uk: 'Українська', ja: '日本語', - + 'zh-CN': '简体中文', + 'zh-HK': '繁體中文(香港)', }.freeze def human_locale(locale) diff --git a/app/helpers/stream_entries_helper.rb b/app/helpers/stream_entries_helper.rb index d5cc004b0b..00a01df44d 100644 --- a/app/helpers/stream_entries_helper.rb +++ b/app/helpers/stream_entries_helper.rb @@ -2,22 +2,40 @@ module StreamEntriesHelper def display_name(account) - account.display_name.blank? ? account.username : account.display_name + account.display_name.presence || account.username + end + + def stream_link_target + embedded_view? ? '_blank' : nil end def acct(account) - "@#{account.acct}#{@external_links && account.local? ? "@#{Rails.configuration.x.local_domain}" : ''}" + "@#{account.acct}#{embedded_view? && account.local? ? "@#{Rails.configuration.x.local_domain}" : ''}" end - def entry_classes(status, is_predecessor, is_successor, include_threads) + def style_classes(status, is_predecessor, is_successor, include_threads) classes = ['entry'] - classes << 'entry-reblog u-repost-of h-cite' if status.reblog? - classes << 'entry-predecessor u-in-reply-to h-cite' if is_predecessor - classes << 'entry-successor u-comment h-cite' if is_successor - classes << 'entry-center h-entry' if include_threads + classes << 'entry-predecessor' if is_predecessor + classes << 'entry-reblog' if status.reblog? + classes << 'entry-successor' if is_successor + classes << 'entry-center' if include_threads classes.join(' ') end + def microformats_classes(status, is_direct_parent, is_direct_child) + classes = [] + classes << 'p-in-reply-to' if is_direct_parent + classes << 'p-repost-of' if status.reblog? && is_direct_parent + classes << 'p-comment' if is_direct_child + classes.join(' ') + end + + def microformats_h_class(status, is_predecessor, is_successor, include_threads) + return 'h-cite' if is_predecessor || status.reblog || is_successor + return 'h-entry' unless include_threads + '' + end + def rtl?(text) return false if text.empty? @@ -30,4 +48,10 @@ module StreamEntriesHelper rtl_size / ltr_size > 0.3 end + + private + + def embedded_view? + params[:controller] == 'stream_entries' && params[:action] == 'embed' + end end diff --git a/app/lib/atom_serializer.rb b/app/lib/atom_serializer.rb index 68d2fce688..69e1f153c3 100644 --- a/app/lib/atom_serializer.rb +++ b/app/lib/atom_serializer.rb @@ -26,8 +26,8 @@ class AtomSerializer append_element(author, 'link', nil, rel: :avatar, type: account.avatar_content_type, 'media:width': 120, 'media:height': 120, href: full_asset_url(account.avatar.url(:original))) append_element(author, 'link', nil, rel: :header, type: account.header_content_type, 'media:width': 700, 'media:height': 335, href: full_asset_url(account.header.url(:original))) append_element(author, 'poco:preferredUsername', account.username) - append_element(author, 'poco:displayName', account.display_name) unless account.display_name.blank? - append_element(author, 'poco:note', Formatter.instance.simplified_format(account).to_str) unless account.note.blank? + append_element(author, 'poco:displayName', account.display_name) if account.display_name? + append_element(author, 'poco:note', Formatter.instance.simplified_format(account).to_str) if account.note? append_element(author, 'mastodon:scope', account.locked? ? :private : :public) author @@ -327,7 +327,7 @@ class AtomSerializer end def serialize_status_attributes(entry, status) - append_element(entry, 'summary', status.spoiler_text) unless status.spoiler_text.blank? + append_element(entry, 'summary', status.spoiler_text) if status.spoiler_text? append_element(entry, 'content', Formatter.instance.format(status.proper).to_str, type: 'html') status.mentions.each do |mentioned| diff --git a/app/lib/formatter.rb b/app/lib/formatter.rb index c3f331ff7d..b6d371ed2d 100644 --- a/app/lib/formatter.rb +++ b/app/lib/formatter.rb @@ -95,6 +95,6 @@ class Formatter end def mention_html(match, account) - "#{match.split('@').first}@#{account.username}" + "#{match.split('@').first}@#{account.username}" end end diff --git a/app/lib/webfinger_resource.rb b/app/lib/webfinger_resource.rb new file mode 100644 index 0000000000..8c5db795d2 --- /dev/null +++ b/app/lib/webfinger_resource.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +class WebfingerResource + attr_reader :resource + + def initialize(resource) + @resource = resource + end + + def username + case resource + when /\Ahttps?/i + username_from_url + when /\@/ + username_from_acct + else + raise(ActiveRecord::RecordNotFound) + end + end + + private + + def username_from_url + if account_show_page? + path_params[:username] + else + raise ActiveRecord::RecordNotFound + end + end + + def account_show_page? + path_params[:controller] == 'accounts' && path_params[:action] == 'show' + end + + def path_params + Rails.application.routes.recognize_path(resource) + end + + def username_from_acct + if domain_matches_local? + local_username + else + raise ActiveRecord::RecordNotFound + end + end + + def split_acct + resource_without_acct_string.split('@') + end + + def resource_without_acct_string + resource.gsub(/\Aacct:/, '') + end + + def local_username + split_acct.first + end + + def local_domain + split_acct.last + end + + def domain_matches_local? + TagManager.instance.local_domain?(local_domain) + end +end diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb index 3b4d4d0764..64ca92a3ac 100644 --- a/app/mailers/user_mailer.rb +++ b/app/mailers/user_mailer.rb @@ -7,9 +7,10 @@ class UserMailer < Devise::Mailer def confirmation_instructions(user, token, _opts = {}) @resource = user @token = token + @instance = Rails.configuration.x.local_domain I18n.with_locale(@resource.locale || I18n.default_locale) do - mail to: @resource.unconfirmed_email.blank? ? @resource.email : @resource.unconfirmed_email + mail to: @resource.unconfirmed_email.blank? ? @resource.email : @resource.unconfirmed_email, subject: I18n.t('devise.mailer.confirmation_instructions.subject', instance: @instance) end end @@ -18,7 +19,7 @@ class UserMailer < Devise::Mailer @token = token I18n.with_locale(@resource.locale || I18n.default_locale) do - mail to: @resource.email + mail to: @resource.email, subject: I18n.t('devise.mailer.reset_password_instructions.subject') end end @@ -26,7 +27,7 @@ class UserMailer < Devise::Mailer @resource = user I18n.with_locale(@resource.locale || I18n.default_locale) do - mail to: @resource.email + mail to: @resource.email, subject: I18n.t('devise.mailer.password_change.subject') end end end diff --git a/app/models/import.rb b/app/models/import.rb index 5384986d81..3013bc50eb 100644 --- a/app/models/import.rb +++ b/app/models/import.rb @@ -3,7 +3,7 @@ class Import < ApplicationRecord self.inheritance_column = false - enum type: [:following, :blocking] + enum type: [:following, :blocking, :muting] belongs_to :account diff --git a/app/services/after_block_service.rb b/app/services/after_block_service.rb index 0f478bcb7d..d1a846f21c 100644 --- a/app/services/after_block_service.rb +++ b/app/services/after_block_service.rb @@ -12,7 +12,7 @@ class AfterBlockService < BaseService home_key = FeedManager.instance.key(:home, account.id) redis.pipelined do - target_account.statuses.select('id').find_each do |status| + target_account.statuses.select('id').reorder(nil).find_each do |status| redis.zrem(home_key, status.id) end end diff --git a/app/services/fan_out_on_write_service.rb b/app/services/fan_out_on_write_service.rb index 19eedc0a75..055fda8a9b 100644 --- a/app/services/fan_out_on_write_service.rb +++ b/app/services/fan_out_on_write_service.rb @@ -34,7 +34,7 @@ class FanOutOnWriteService < BaseService def deliver_to_followers(status) Rails.logger.debug "Delivering status #{status.id} to followers" - status.account.followers.where(domain: nil).joins(:user).where('users.current_sign_in_at > ?', 14.days.ago).select(:id).find_each do |follower| + status.account.followers.where(domain: nil).joins(:user).where('users.current_sign_in_at > ?', 14.days.ago).select(:id).reorder(nil).find_each do |follower| FeedInsertWorker.perform_async(status.id, follower.id) end end diff --git a/app/services/mute_service.rb b/app/services/mute_service.rb index 0050cfc8d0..1a650ed2a0 100644 --- a/app/services/mute_service.rb +++ b/app/services/mute_service.rb @@ -12,7 +12,7 @@ class MuteService < BaseService def clear_home_timeline(account, target_account) home_key = FeedManager.instance.key(:home, account.id) - target_account.statuses.select('id').find_each do |status| + target_account.statuses.select('id').reorder(nil).find_each do |status| redis.zrem(home_key, status.id) end end diff --git a/app/services/suspend_account_service.rb b/app/services/suspend_account_service.rb index 8528ef62af..42ff4dcb78 100644 --- a/app/services/suspend_account_service.rb +++ b/app/services/suspend_account_service.rb @@ -12,7 +12,7 @@ class SuspendAccountService < BaseService private def purge_content - @account.statuses.find_each do |status| + @account.statuses.reorder(nil).find_each do |status| RemoveStatusService.new.call(status) end diff --git a/app/views/about/terms.no.html.haml b/app/views/about/terms.no.html.haml new file mode 100644 index 0000000000..5506cd863d --- /dev/null +++ b/app/views/about/terms.no.html.haml @@ -0,0 +1,76 @@ +- content_for :page_title do + #{Rails.configuration.x.local_domain} Personvern og villkår for bruk av nettstedet + +.wrapper + %h2 Personvernserklæring + + %h3#collect Hvilke opplysninger samler vi? + + %p Vi samler opplysninger fra deg når du registrerer deg på nettstedet vårt, og vi samler data når du deltar på forumet ved å lese, skrive og evaluere innholdet som deles her. + + %p Når du registrerer deg på nettstedet vårt, kan du bli bedt om å oppgi navnet og e-postadressen din. Imidlertid kan du besøke nettstedet vårt uten å registrere deg. E-postadressen din vil bli bekreftet med en e-post som inneholder en unik lenke. Hvis siden den lenker til, blir besøkt, vet vi at du har kontroll over e-postadressen. + + %p Når du registrerer deg og skriver innlegg, registrerer vi IP-adressen som innlegget stammer fra. Vi kan også oppbevare logger som inkluderer IP-adressen til alle forespørslene sendt til tjeneren vår. + + %h3#use Hva bruker vi opplysningene dine til? + + %p Alle opplysningene vi samler fra deg, kan bli brukt på en av følgende måter: + + %ul + %li For å gjøre opplevelsen din mer personlig. Opplysningene dine hjelper oss å svare bedre på dine individuelle behov. + %li For å forbedre nettstedet vårt. Vi jobber konstant for å forbedre nettstedets tilbud basert på opplysningene og tilbakemeldingene vi mottar fra deg. + %li For å forbedre vår kundeservice. Dine opplysninger hjelper oss å svare mer effektivt på dine forespørsler sendt til kundeservice eller behov om støtte. + %li For å sende periodiske e-poster. E-postadressen du oppgir, kan bli brukt til å sende deg informasjon, påminnelser som du ber om ved endringer av emner eller ved svar til brukernavnet ditt, til henvendelser, og/eller andre forspørsler eller andre spørsmål. + + %h3#protect Hvordan sikrer vi opplysningene? + + %p Vi gjennomfører flere sikkerhetstiltak for å holde personopplysningene dine sikre når du skriver inn, lagrer eller henter dem. + + %h3#data-retention Hva er retningslinjene deres for lagring av data? + + %p Vi vil forsøke i god tro å: + + %ul + %li Ikke oppbevare tjener-logger som inneholder IP-adressen til alle forespørslene til denne tjeneren i lenger enn i 90 dager. + %li Ikke oppbevare IP-adressene forbundet med registrerte brukere og deres innlegg lenger enn i 5 år. + + %h3#cookies Bruker vi informasjonskapsler? + + %p Ja. Informasjonskapsler er små filer som et nettsted eller dets tjenesteleverandør overfører til harddisken på datamaskinen din gjennom nettleseren din (dersom du tillater det). Disse informasjonskapslene gjør det mulig for nettstedet å gjenkjenne nettleseren din og, dersom du har en konto, knytte nettleseren til den. + + %p Vi bruker informasjonskapsler for å forstå og lagre preferansene dine for fremtidige besøk og for å samle aggregatdata om trafikk på og samhandling med nettstedet slik at vi kan tilby bedre opplevelser og verktøy på nettstedet i fremtiden. Vi kan inngå avtaler med tredjeparts tjenesteleverandører for å bistå oss i å forstå besøkerne våres bedre. Disse tjenesteleverandørene har ikke lov til å bruke opplysningene samlet på våres vegne unntatt til å hjelpe oss å gjennomføre og forbedre anliggendet vårt. + + %h3#disclose Gir vi noen opplysninger videre til andre parter? + + %p Vi verken selger, handler med eller overfører på noen annen måte til andre parter dine identifiserbare personopplysninger. Dette inkluderer ikke tredjeparter som har vår tillit og bistår oss i å drive nettstedet, utføre våre anliggender eller yter tjenester til deg, så lenge disse partene samtykker til å behandle disse opplysningene fortrolig. Vi kan også frigi opplysningene dine dersom vi tror at å frigi dem er hensiktsmessig for å overholde loven, håndheve nettstedet retningslinjer eller beskytte våre og andres rettigheter. Imidlertid kan opplysninger som ikke er personlig identifiserbare, bli delt med andre parter for markedsføring, reklame eller annet bruk. + + %h3#third-party Tredjeparts lenker + + %p Av og til, etter skjønn, kan vil inkludere eller tilby tredjeparts produkter eller tjenester på nettstedet vårt. Disse tredjeparts nettstedene har separate og selvstendige personvernerklæringer. Vi bærer derfor intet ansvar eller forpliktelser for innholdet eller aktivitetene til disse nettstedene det lenkes til. Ikke mindre prøver vi å bevare vår eget nettsteds integritet og ønsker enhver tilbakemelding om disse nettstedene velkomne. + + %h3#coppa Overensstemmelse med Children's Online Privacy Protection Act + + %p + Nettstedet er rettet mot folk som er minst 13 år gamle. Dersom denne tjeneren er i USA, og du er under 13 år i henhold til kravene i COPPA + = surround '(', '),' do + = link_to 'Children\'s Online Privacy Protection Act', 'https://en.wikipedia.org/wiki/Children%27s_Online_Privacy_Protection_Act' + ikke bruk dette nettstedet. + + %h3#online Personvernerklæring bare for nettet + + %p Denne nett-personvernerklæringen gjelder bare for informasjon samlet gjennom nettstedet vårt og ikke for opplysninger samlet når en er frakoblet. + + %h3#consent Ditt samtykke + + %p Ved å bruke dette nettstedet samtykker du til nettstedets personvernerklæring. + + %h3#changes Endringer i vår personvernerklæring + + %p Dersom vi beslutter å endre personvernerklæringen vår, vil vi publisere disse endringene på denne siden. + + %p Dette dokumentet er lisensiert under CC-BY-SA. De ble sist oppdatert 12. april 2017. + + %p + Dokumentet er en adoptert og endret versjon fra + = succeed '.' do + = link_to 'Discourse privacy policy', 'https://github.com/discourse/discourse' diff --git a/app/views/accounts/_header.html.haml b/app/views/accounts/_header.html.haml index 0d43fba304..beee96cd8d 100644 --- a/app/views/accounts/_header.html.haml +++ b/app/views/accounts/_header.html.haml @@ -13,7 +13,7 @@ %h1.name %span.p-name.emojify= display_name(@account) %small - %span.p-nickname= "@#{@account.username}" + %span= "@#{@account.username}" = fa_icon('lock') if @account.locked? .details .bio diff --git a/app/views/admin/pubsubhubbub/index.html.haml b/app/views/admin/pubsubhubbub/index.html.haml index 2b8e36e6a3..dcbb11c114 100644 --- a/app/views/admin/pubsubhubbub/index.html.haml +++ b/app/views/admin/pubsubhubbub/index.html.haml @@ -21,9 +21,9 @@ %i.fa.fa-check %td= distance_of_time_in_words(Time.now, subscription.expires_at) %td - - if subscription.last_successful_delivery_at.nil? - %i.fa.fa-times - - else + - if subscription.last_successful_delivery_at? = l subscription.last_successful_delivery_at + - else + %i.fa.fa-times = paginate @subscriptions diff --git a/app/views/admin/reports/index.html.haml b/app/views/admin/reports/index.html.haml index 9c5c789356..68dc070169 100644 --- a/app/views/admin/reports/index.html.haml +++ b/app/views/admin/reports/index.html.haml @@ -1,12 +1,12 @@ - content_for :page_title do - Reports + = t('reports.reports') .filters .filter-subset - %strong Status + %strong= t('reports.status') %ul - %li= filter_link_to 'Unresolved', action_taken: nil - %li= filter_link_to 'Resolved', action_taken: '1' + %li= filter_link_to t('reports.unresolved'), action_taken: nil + %li= filter_link_to t('reports.resolved'), action_taken: '1' = form_tag do @@ -14,10 +14,10 @@ %thead %tr %th - %th ID - %th Target - %th Reported by - %th Comment + %th= t('reports.id') + %th= t('reports.target') + %th= t('reports.reported_by') + %th= t('reports.comment.label') %th %tbody - @reports.each do |report| @@ -27,6 +27,6 @@ %td= link_to report.target_account.acct, admin_account_path(report.target_account.id) %td= link_to report.account.acct, admin_account_path(report.account.id) %td= truncate(report.comment, length: 30, separator: ' ') - %td= table_link_to 'circle', 'View', admin_report_path(report) + %td= table_link_to 'circle', t('reports.view'), admin_report_path(report) = paginate @reports diff --git a/app/views/admin/reports/show.html.haml b/app/views/admin/reports/show.html.haml index caa8415dff..ecbb98482b 100644 --- a/app/views/admin/reports/show.html.haml +++ b/app/views/admin/reports/show.html.haml @@ -1,20 +1,18 @@ - content_for :page_title do - = "Report ##{@report.id}" + = t('reports.report', id: @report.id) .report-accounts .report-accounts__item - %strong Reported account: + %strong= t('reports.reported_account') = render partial: 'authorize_follow/card', locals: { account: @report.target_account } .report-accounts__item - %strong Reported by: + %strong= t('reports.reported_by') = render partial: 'authorize_follow/card', locals: { account: @report.account } %p - %strong Comment: - - if @report.comment.blank? - None - - else - = @report.comment + %strong= t('reports.comment.label') + \: + = @report.comment.presence || t('reports.comment.none') - unless @statuses.empty? %hr/ @@ -24,7 +22,7 @@ .activity-stream.activity-stream-headless .entry= render partial: 'stream_entries/simple_status', locals: { status: status } .report-status__actions - = link_to remove_admin_report_path(@report, status_id: status.id), method: :post, class: 'icon-button', style: 'font-size: 24px; width: 24px; height: 24px', title: 'Delete' do + = link_to remove_admin_report_path(@report, status_id: status.id), method: :post, class: 'icon-button', style: 'font-size: 24px; width: 24px; height: 24px', title: t('reports.delete') do = fa_icon 'trash' - if !@report.action_taken? @@ -32,10 +30,10 @@ %div{ style: 'overflow: hidden' } %div{ style: 'float: right' } - = link_to 'Silence account', silence_admin_report_path(@report), method: :post, class: 'button' - = link_to 'Suspend account', suspend_admin_report_path(@report), method: :post, class: 'button' + = link_to t('reports.silence_account'), silence_admin_report_path(@report), method: :post, class: 'button' + = link_to t('reports.suspend_account'), suspend_admin_report_path(@report), method: :post, class: 'button' %div{ style: 'float: left' } - = link_to 'Mark as resolved', resolve_admin_report_path(@report), method: :post, class: 'button' + = link_to t('reports.mark_as_resolved'), resolve_admin_report_path(@report), method: :post, class: 'button' - elsif !@report.action_taken_by_account.nil? %hr/ diff --git a/app/views/admin/settings/index.html.haml b/app/views/admin/settings/index.html.haml index 02faac8c26..b00e75a166 100644 --- a/app/views/admin/settings/index.html.haml +++ b/app/views/admin/settings/index.html.haml @@ -1,52 +1,40 @@ - content_for :page_title do - Site Settings + = t('admin.settings.title') %table.table %colgroup %col{ width: '35%' }/ %thead %tr - %th Setting - %th Click to edit + %th= t('admin.settings.setting') + %th= t('admin.settings.click_to_edit') %tbody %tr %td{ rowspan: 2 } - %strong Contact information - %td= best_in_place @settings['site_contact_username'], :value, url: admin_setting_path(@settings['site_contact_username']), place_holder: 'Enter a username' + %strong= t('admin.settings.contact_information.label') + %td= best_in_place @settings['site_contact_username'], :value, url: admin_setting_path(@settings['site_contact_username']), place_holder: t('admin.settings.contact_information.username') %tr - %td= best_in_place @settings['site_contact_email'], :value, url: admin_setting_path(@settings['site_contact_email']), place_holder: 'Enter a public e-mail address' + %td= best_in_place @settings['site_contact_email'], :value, url: admin_setting_path(@settings['site_contact_email']), place_holder: t('admin.settings.contact_information.email') %tr %td - %strong Site title + %strong= t('admin.settings.site_title') %td= best_in_place @settings['site_title'], :value, url: admin_setting_path(@settings['site_title']) %tr %td - %strong Site description - %br/ - Displayed as a paragraph on the frontpage and used as a meta tag. - %br/ - You can use HTML tags, in particular - %code= '' - and - %code= '' + %strong= t('admin.settings.site_description.title') + %p= t('admin.settings.site_description.desc_html') %td= best_in_place @settings['site_description'], :value, as: :textarea, url: admin_setting_path(@settings['site_description']) %tr %td - %strong Extended site description - %br/ - Displayed on extended information page - %br/ - You can use HTML tags + %strong= t('admin.settings.site_description_extended.title') + %p= t('admin.settings.site_description_extended.desc_html') %td= best_in_place @settings['site_extended_description'], :value, as: :textarea, url: admin_setting_path(@settings['site_extended_description']) %tr %td - %strong Open registration - %td= best_in_place @settings['open_registrations'], :value, as: :checkbox, collection: { false: 'Disabled', true: 'Enabled'}, url: admin_setting_path(@settings['open_registrations']) + %strong= t('admin.settings.registrations.open.title') + %td= best_in_place @settings['open_registrations'], :value, as: :checkbox, collection: { false: t('admin.settings.registrations.open.disabled'), true: t('admin.settings.registrations.open.enabled')}, url: admin_setting_path(@settings['open_registrations']) %tr %td - %strong Closed registration message - %br/ - Displayed on frontpage when registrations are closed - %br/ - You can use HTML tags + %strong= t('admin.settings.registrations.closed_message.title') + %p= t('admin.settings.registrations.closed_message.desc_html') %td= best_in_place @settings['closed_registrations_message'], :value, as: :textarea, url: admin_setting_path(@settings['closed_registrations_message']) diff --git a/app/views/authorize_follow/_card.html.haml b/app/views/authorize_follow/_card.html.haml index eef0bec07c..16af9220e5 100644 --- a/app/views/authorize_follow/_card.html.haml +++ b/app/views/authorize_follow/_card.html.haml @@ -7,5 +7,5 @@ %strong.emojify= display_name(account) %span= "@#{account.acct}" - - unless account.note.blank? + - if account.note? .account__header__content.emojify= Formatter.instance.simplified_format(account) diff --git a/app/views/errors/404.html.haml b/app/views/errors/404.html.haml index ba1d5f72dc..5ffba3ca90 100644 --- a/app/views/errors/404.html.haml +++ b/app/views/errors/404.html.haml @@ -1,5 +1,5 @@ - content_for :page_title do - The page you were looking for doesn't exist + = t('errors.404') - content_for :content do - The page you were looking for doesn't exist + = t('errors.404') \ No newline at end of file diff --git a/app/views/errors/410.html.haml b/app/views/errors/410.html.haml index 07cf3742fd..627c65583a 100644 --- a/app/views/errors/410.html.haml +++ b/app/views/errors/410.html.haml @@ -1,5 +1,5 @@ - content_for :page_title do - The page you were looking for doesn't exist anymore + = t('errors.410') - content_for :content do - The page you were looking for doesn't exist anymore + = t('errors.410') diff --git a/app/views/errors/422.html.haml b/app/views/errors/422.html.haml index e369cded64..e710fecb31 100644 --- a/app/views/errors/422.html.haml +++ b/app/views/errors/422.html.haml @@ -1,5 +1,5 @@ - content_for :page_title do - Security verification failed + = t('errors.422.title') - content_for :content do - Security verification failed. Are you blocking cookies? + = t('errors.422.content') diff --git a/app/views/settings/exports/show.html.haml b/app/views/settings/exports/show.html.haml index 432a61b4a4..51be40fb62 100644 --- a/app/views/settings/exports/show.html.haml +++ b/app/views/settings/exports/show.html.haml @@ -15,3 +15,7 @@ %th= t('exports.blocks') %td= @total_blocks %td= table_link_to 'download', t('exports.csv'), settings_exports_blocks_path(format: :csv) + %tr + %th= t('exports.mutes') + %td= @total_mutes + %td= table_link_to 'download', t('exports.csv'), settings_exports_mutes_path(format: :csv) diff --git a/app/views/stream_entries/_detailed_status.html.haml b/app/views/stream_entries/_detailed_status.html.haml index 8495f28b9a..e3cc522be2 100644 --- a/app/views/stream_entries/_detailed_status.html.haml +++ b/app/views/stream_entries/_detailed_status.html.haml @@ -1,18 +1,19 @@ .detailed-status.light - = link_to TagManager.instance.url_for(status.account), class: 'detailed-status__display-name p-author h-card', target: @external_links ? '_blank' : nil, rel: 'noopener' do + = link_to TagManager.instance.url_for(status.account), class: 'detailed-status__display-name p-author h-card', target: stream_link_target, rel: 'noopener' do %div %div.avatar = image_tag status.account.avatar.url(:original), width: 48, height: 48, alt: '', class: 'u-photo' %span.display-name %strong.p-name.emojify= display_name(status.account) - %span.p-nickname= acct(status.account) + %span= acct(status.account) - .status__content.e-content.p-name.emojify< - - unless status.spoiler_text.blank? + .status__content.p-name.emojify< + - if status.spoiler_text? %p{ style: 'margin-bottom: 0' }< - %span>= "#{status.spoiler_text} " + %span.p-summary>= "#{status.spoiler_text} " %a.status__content__spoiler-link{ href: '#' }= t('statuses.show_more') - %div{ style: "display: #{status.spoiler_text.blank? ? 'block' : 'none'}; direction: #{rtl?(status.content) ? 'rtl' : 'ltr'}" }= Formatter.instance.format(status) + %div.e-content{ style: "display: #{status.spoiler_text? ? 'none' : 'block'}; direction: #{rtl?(status.content) ? 'rtl' : 'ltr'}" }= Formatter.instance.format(status) + - unless status.media_attachments.empty? - if status.media_attachments.first.video? @@ -30,7 +31,7 @@ %div.detailed-status__meta %data.dt-published{ value: status.created_at.to_time.iso8601 } - = link_to TagManager.instance.url_for(status), class: 'detailed-status__datetime u-url u-uid', target: @external_links ? '_blank' : nil, rel: 'noopener' do + = link_to TagManager.instance.url_for(status), class: 'detailed-status__datetime u-url u-uid', target: stream_link_target, rel: 'noopener' do %span= l(status.created_at) · - if status.application @@ -49,4 +50,4 @@ - if user_signed_in? · - = link_to t('statuses.open_in_web'), web_url("statuses/#{status.id}"), class: 'open-in-web-link' + = link_to t('statuses.open_in_web'), web_url("statuses/#{status.id}"), class: 'open-in-web-link', target: '_blank' diff --git a/app/views/stream_entries/_simple_status.html.haml b/app/views/stream_entries/_simple_status.html.haml index 2eb9bf1667..52905ff5e0 100644 --- a/app/views/stream_entries/_simple_status.html.haml +++ b/app/views/stream_entries/_simple_status.html.haml @@ -1,23 +1,23 @@ .status.light .status__header .status__meta - = link_to time_ago_in_words(status.created_at), TagManager.instance.url_for(status), class: 'status__relative-time u-url u-uid', title: l(status.created_at), target: @external_links ? '_blank' : nil, rel: 'noopener' + = link_to time_ago_in_words(status.created_at), TagManager.instance.url_for(status), class: 'status__relative-time u-url u-uid', title: l(status.created_at), target: stream_link_target, rel: 'noopener' %data.dt-published{ value: status.created_at.to_time.iso8601 } - = link_to TagManager.instance.url_for(status.account), class: 'status__display-name p-author h-card', target: @external_links ? '_blank' : nil, rel: 'noopener' do + = link_to TagManager.instance.url_for(status.account), class: 'status__display-name p-author h-card', target: stream_link_target, rel: 'noopener' do .status__avatar %div = image_tag status.account.avatar(:original), width: 48, height: 48, alt: '', class: 'u-photo' %span.display-name %strong.p-name.emojify= display_name(status.account) - %span.p-nickname= acct(status.account) + %span= acct(status.account) - .status__content.e-content.p-name.emojify< - - unless status.spoiler_text.blank? + .status__content.p-name.emojify< + - if status.spoiler_text? %p{ style: 'margin-bottom: 0' }< - %span>= "#{status.spoiler_text} " + %span.p-summary>= "#{status.spoiler_text} " %a.status__content__spoiler-link{ href: '#' }= t('statuses.show_more') - %div{ style: "display: #{status.spoiler_text.blank? ? 'block' : 'none'}; direction: #{rtl?(status.content) ? 'rtl' : 'ltr'}" }= Formatter.instance.format(status) + %div.e-content{ style: "display: #{status.spoiler_text? ? 'none' : 'block'}; direction: #{rtl?(status.content) ? 'rtl' : 'ltr'}" }= Formatter.instance.format(status) - unless status.media_attachments.empty? .status__attachments diff --git a/app/views/stream_entries/_status.html.haml b/app/views/stream_entries/_status.html.haml index 1333d4d82a..f389a8dfe7 100644 --- a/app/views/stream_entries/_status.html.haml +++ b/app/views/stream_entries/_status.html.haml @@ -1,12 +1,22 @@ - include_threads ||= false - is_predecessor ||= false - is_successor ||= false +- direct_reply_id ||= false +- parent_id ||= false +- is_direct_parent = direct_reply_id == status.id +- is_direct_child = parent_id == status.in_reply_to_id +- parent_id ||= false - centered ||= include_threads && !is_predecessor && !is_successor +- h_class = microformats_h_class(status, is_predecessor, is_successor, include_threads) +- style_classes = style_classes(status, is_predecessor, is_successor, include_threads) +- mf_classes = microformats_classes(status, is_direct_parent, is_direct_child) +- entry_classes = h_class + ' ' + mf_classes + ' ' + style_classes - if status.reply? && include_threads - = render partial: 'stream_entries/status', collection: @ancestors, as: :status, locals: { is_predecessor: true } + = render partial: 'stream_entries/status', collection: @ancestors, as: :status, locals: { is_predecessor: true, direct_reply_id: status.in_reply_to_id} + +.entry{ class: entry_classes } -.entry{ class: entry_classes(status, is_predecessor, is_successor, include_threads) } - if status.reblog? .pre-header %div.pre-header__icon @@ -19,4 +29,4 @@ = render partial: centered ? 'stream_entries/detailed_status' : 'stream_entries/simple_status', locals: { status: status.proper } - if include_threads - = render partial: 'stream_entries/status', collection: @descendants, as: :status, locals: { is_successor: true } + = render partial: 'stream_entries/status', collection: @descendants, as: :status, locals: { is_successor: true, parent_id: status.id} diff --git a/app/views/stream_entries/show.html.haml b/app/views/stream_entries/show.html.haml index 088881b11f..f37fb79196 100644 --- a/app/views/stream_entries/show.html.haml +++ b/app/views/stream_entries/show.html.haml @@ -23,5 +23,5 @@ - if !user_signed_in? && !Rails.configuration.x.single_user_mode = render partial: 'shared/landing_strip', locals: { account: @stream_entry.account } -.activity-stream.activity-stream-headless +.activity-stream.activity-stream-headless.h-entry = render partial: "stream_entries/#{@type}", locals: { @type.to_sym => @stream_entry.activity, include_threads: true } diff --git a/app/views/user_mailer/confirmation_instructions.en.html.erb b/app/views/user_mailer/confirmation_instructions.en.html.erb index 69e9ff80ff..f28a38be28 100644 --- a/app/views/user_mailer/confirmation_instructions.en.html.erb +++ b/app/views/user_mailer/confirmation_instructions.en.html.erb @@ -1,5 +1,12 @@ -Welcome <%= @resource.email %>!
+Welcome <%= @resource.email %> !
-You can confirm your Mastodon account email through the link below:
+You just created an account on <%= @instance %>.
-<%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %>
+To confirm your inscription, please click on the following link :
+<%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %>
+
+
Please also check out our <%= link_to 'terms and conditions', terms_url %>.
+ +Sincerely,
+ +
The <%= @instance %> team
\ No newline at end of file diff --git a/app/views/user_mailer/confirmation_instructions.en.text.erb b/app/views/user_mailer/confirmation_instructions.en.text.erb index bb21cf8e25..0419adef0f 100644 --- a/app/views/user_mailer/confirmation_instructions.en.text.erb +++ b/app/views/user_mailer/confirmation_instructions.en.text.erb @@ -1,5 +1,12 @@ -Welcome <%= @resource.email %>! +Welcome <%= @resource.email %> ! -You can confirm your Mastodon account email through the link below: +You just created an account on <%= @instance %>. +To confirm your inscription, please click on the following link : <%= confirmation_url(@resource, confirmation_token: @token) %> + +Please also check out our terms and conditions <%= terms_url %> + +Sincerely, + +The <%= @instance %> team \ No newline at end of file diff --git a/app/views/user_mailer/confirmation_instructions.fr.html.erb b/app/views/user_mailer/confirmation_instructions.fr.html.erb index 6c45f1a214..b0b3d0f511 100644 --- a/app/views/user_mailer/confirmation_instructions.fr.html.erb +++ b/app/views/user_mailer/confirmation_instructions.fr.html.erb @@ -1,5 +1,14 @@ -Bienvenue <%= @resource.email %> !
+Bonjour <%= @resource.email %> !
-
Vous pouvez confirmer le courriel de votre compte Mastodon en cliquant sur le lien ci-dessous :
+Vous venez de vous créer un compte sur <%= @instance %> et nous vous en remercions :)
-<%= link_to 'Confirmer mon compte', confirmation_url(@resource, confirmation_token: @token) %>
+Pour confirmer votre inscription, merci de cliquer sur le lien suivant :
+<%= link_to 'Confirmer mon compte', confirmation_url(@resource, confirmation_token: @token) %>
Après votre première connexion, vous pourrez accéder à la documentation de l'outil.
+ +Pensez également à jeter un œil à nos <%= link_to 'conditions d\'utilisation', terms_url %>.
+ +Amicalement,
+ +L'équipe <%= @instance %>
\ No newline at end of file diff --git a/app/views/user_mailer/confirmation_instructions.fr.text.erb b/app/views/user_mailer/confirmation_instructions.fr.text.erb index dfa3f9f7c3..cf8e39689c 100644 --- a/app/views/user_mailer/confirmation_instructions.fr.text.erb +++ b/app/views/user_mailer/confirmation_instructions.fr.text.erb @@ -1,5 +1,14 @@ -Bienvenue <%= @resource.email %> ! +Bonjour <%= @resource.email %> ! -Vous pouvez confirmer le courriel de votre compte Mastodon en cliquant sur le lien ci-dessous : +Vous venez de vous créer un compte sur <%= @instance %> et nous vous en remercions. +Pour confirmer votre inscription, merci de cliquer sur le lien suivant : <%= confirmation_url(@resource, confirmation_token: @token) %> + +Après votre première connexion, vous pourrez accéder à la documentation de l'outil. + +Pour rappel, nos conditions d'utilisation sont indiquées ici <%= terms_url %> + +Amicalement, + +L'équipe <%= @instance %> \ No newline at end of file diff --git a/app/workers/import_worker.rb b/app/workers/import_worker.rb index 60529c0e1c..bb21468e7b 100644 --- a/app/workers/import_worker.rb +++ b/app/workers/import_worker.rb @@ -16,6 +16,8 @@ class ImportWorker process_blocks when 'following' process_follows + when 'muting' + process_mutes end @import.destroy @@ -35,6 +37,18 @@ class ImportWorker CSV.new(import_contents).reject(&:blank?) end + def process_mutes + import_rows.each do |row| + begin + target_account = FollowRemoteAccountService.new.call(row.first) + next if target_account.nil? + MuteService.new.call(from_account, target_account) + rescue Goldfinger::Error, HTTP::Error, OpenSSL::SSL::SSLError + next + end + end + end + def process_blocks import_rows.each do |row| begin diff --git a/app/workers/pubsubhubbub/delivery_worker.rb b/app/workers/pubsubhubbub/delivery_worker.rb index 8412be4b75..b440f7986e 100644 --- a/app/workers/pubsubhubbub/delivery_worker.rb +++ b/app/workers/pubsubhubbub/delivery_worker.rb @@ -19,7 +19,7 @@ class Pubsubhubbub::DeliveryWorker headers['User-Agent'] = 'Mastodon/PubSubHubbub' headers['Link'] = LinkHeader.new([[api_push_url, [%w(rel hub)]], [account_url(subscription.account, format: :atom), [%w(rel self)]]]).to_s - headers['X-Hub-Signature'] = signature(subscription.secret, payload) unless subscription.secret.blank? + headers['X-Hub-Signature'] = signature(subscription.secret, payload) if subscription.secret? response = HTTP.timeout(:per_operation, write: 50, connect: 20, read: 50) .headers(headers) diff --git a/config/application.rb b/config/application.rb index a3991639c5..2c720474a0 100644 --- a/config/application.rb +++ b/config/application.rb @@ -25,7 +25,22 @@ module Mastodon # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] - config.i18n.available_locales = [:en, :de, :es, :pt, :fr, :hu, :uk, 'zh-CN', :fi, :eo, :ru, :ja] + config.i18n.available_locales = [ + :en, + :de, + :eo, + :es, + :fi, + :fr, + :hu, + :ja, + :no, + :pt, + :ru, + :uk, + 'zh-CN', + :'zh-HK', + ] config.i18n.default_locale = :en diff --git a/config/initializers/blacklists.rb b/config/initializers/blacklists.rb index 6db7be7dc5..020d84f569 100644 --- a/config/initializers/blacklists.rb +++ b/config/initializers/blacklists.rb @@ -2,5 +2,5 @@ Rails.application.configure do config.x.email_domains_blacklist = ENV.fetch('EMAIL_DOMAIN_BLACKLIST') { 'mvrht.com' } - config.x.email_domains_whitelist = ENV.fetch('EMAIL_DOMAIN_WHITELIST') { '' } + config.x.email_domains_whitelist = ENV.fetch('EMAIL_DOMAIN_WHITELIST') { '' } end diff --git a/config/locales/devise.en.yml b/config/locales/devise.en.yml index 442e70d516..b9204b59c5 100644 --- a/config/locales/devise.en.yml +++ b/config/locales/devise.en.yml @@ -17,7 +17,7 @@ en: unconfirmed: You have to confirm your email address before continuing. mailer: confirmation_instructions: - subject: 'Mastodon: Confirmation instructions' + subject: 'Mastodon: Confirmation instructions for %{instance}' password_change: subject: 'Mastodon: Password changed' reset_password_instructions: diff --git a/config/locales/devise.fr.yml b/config/locales/devise.fr.yml index a986113e2c..d1dae7c32b 100644 --- a/config/locales/devise.fr.yml +++ b/config/locales/devise.fr.yml @@ -17,13 +17,13 @@ fr: unconfirmed: Vous devez valider votre compte pour continuer. mailer: confirmation_instructions: - subject: Instructions de confirmation + subject: "Merci de confirmer votre inscription sur %{instance}" password_change: subject: Votre mot de passe a été modifié avec succés. reset_password_instructions: - subject: Instructions pour modifier le mot de passe + subject: Instructions pour changer votre mot de passe unlock_instructions: - subject: Instructions pour déverrouiller le compte + subject: Instructions pour déverrouiller votre compte omniauth_callbacks: failure: 'Nous n''avons pas pu vous authentifier via %{kind} : ''%{reason}''.' success: Authentifié avec succès via %{kind}. diff --git a/config/locales/devise.zh-HK.yml b/config/locales/devise.zh-HK.yml new file mode 100644 index 0000000000..cecd40073a --- /dev/null +++ b/config/locales/devise.zh-HK.yml @@ -0,0 +1,61 @@ +--- +zh-HK: + devise: + confirmations: + confirmed: 你的電郵地址確認成功 + send_instructions: 你將會在幾分鐘內收到確認指示電郵,上面有確認你電郵地址的指示。 + send_paranoid_instructions: 如果你的電郵地址已經存在於我們的資料庫,你將會在幾分鐘內收到電郵,確認你電郵地址的指示。 + failure: + already_authenticated: 你之前已經登入了。 + inactive: 你的用戶並未啟用。 + invalid: 不正確的 %{authentication_keys} 或密碼。 + last_attempt: 若你再一次嘗試失敗,我們將鎖定你的用戶,以察安全。 + locked: 你的用戶已被鎖定 + not_found_in_database: 不正確的 %{authentication_keys} 或密碼。 + timeout: 你的登入階段已經過期,請重新登入以繼續使用。 + unauthenticated: 你必須先登入或登記,以繼續使用。 + unconfirmed: 你必須先確認電郵地址,繼續使用。 + mailer: + confirmation_instructions: + subject: 'Mastodon: 確認電郵地址' + password_change: + subject: 'Mastodon: 更改密碼' + reset_password_instructions: + subject: 'Mastodon: 重設密碼' + unlock_instructions: + subject: 'Mastodon: 解除用戶鎖定' + omniauth_callbacks: + failure: 無法以 %{kind} 登入你的用戶,原因是︰「%{reason}」。 + success: 成功以 %{kind} 登入你的用戶。 + passwords: + no_token: 你必須使用重設密碼電郵內的網址進入本頁。如果你確是使用電郵內的網址,請確認你用了完整的網址。 + send_instructions: 你將在幾分鐘內收到重設密碼的電郵指示。 + send_paranoid_instructions: 如果你的電郵地址已經存在於我們的資料庫,你將會在幾分鐘內收到重設密碼的電郵指示。 + updated: 你的密碼已經更新,你現在正登入本站。 + updated_not_active: 你的密碼已經更新。 + registrations: + destroyed: 再見了!你的用戶已被取消,希望我們相有相見的機會吧。 + signed_up: 歡迎你!你的登記已經成功。 + signed_up_but_inactive: 你的登記已經成功,可是由於你的用戶還被被啟用,暫時還不能讓你登入。 + signed_up_but_locked: 你的登記已經成功,可是由於你的用戶已被鎖定,我們無法讓你登入。 + signed_up_but_unconfirmed: 一條確認連結已經電郵到你的郵址。請使用讓連結啟用你的用戶。 + update_needs_confirmation: 你的用戶已經更新,但我們需要確認你的電郵地址。請打開你的郵箱,使用確認電郵的連結來確認的地郵址。 + updated: 你的用戶已經成功更新。 + sessions: + already_signed_out: 成功登出。 + signed_in: 成功登入。 + signed_out: 成功登出。 + unlocks: + send_instructions: 你將在幾分鐘內收到解除用戶鎖定的電郵指示。 + send_paranoid_instructions: 如果你的電郵地址已經存在於我們的資料庫,你將在幾分鐘內收到解除用戶鎖定的電郵指示。 + unlocked: 你的用戶已經解鎖,請登入以繼續。 + errors: + messages: + already_confirmed: 先前已經確認,請嘗試登入 + confirmation_period_expired: 需要在 %{period} 之內確認。請重新申請 + expired: 已經過期,請重新申請 + not_found: 找不到 + not_locked: 並未被鎖定 + not_saved: + one: '1 個錯誤令 %{resource} 被法被儲存︰' + other: "%{count} 個錯誤令 %{resource} 被法被儲存︰" diff --git a/config/locales/doorkeeper.ja.yml b/config/locales/doorkeeper.ja.yml index 7c6a140569..35592bc494 100644 --- a/config/locales/doorkeeper.ja.yml +++ b/config/locales/doorkeeper.ja.yml @@ -60,15 +60,15 @@ ja: title: 認証コード authorized_applications: buttons: - revoke: 取り消す + revoke: 取消 confirmations: revoke: 本当に取り消しますか? index: - application: アプリケーション - created_at: 認証済み + application: アプリ名 + created_at: 許可した日時 date_format: "%Y年%m月%d日 %H時%M分%S秒" scopes: アクセス権 - title: あなたの認証済みアプリケーション + title: 認証済みアプリケーション errors: messages: access_denied: リソースの所有者または認証サーバーが要求を拒否しました。 @@ -83,7 +83,7 @@ ja: expired: アクセストークンの有効期限が切れています revoked: アクセストークンは取り消されています。 unknown: アクセストークンが無効です。 - resource_owner_authenticator_not_configured: Doorkeeper.configure.resource_owner_authenticatorが設定されていないため、リソース所有者の検索に失敗しました。 + resource_owner_authenticator_not_configured: Doorkeeper.configure.resource_owner_authenticator が設定されていないため、リソース所有者の検索に失敗しました。 server_error: 認証サーバーに予期せぬ例外が発生したため、リクエストを実行できなくなりました。 temporarily_unavailable: 現在、認証サーバーに一時的な過負荷が掛かっているか、またはメンテナンス中のため、リクエストを処理できません。 unauthorized_client: クライアントはこのメゾットで要求を実行する権限がありません。 diff --git a/config/locales/doorkeeper.zh-HK.yml b/config/locales/doorkeeper.zh-HK.yml new file mode 100644 index 0000000000..90224c7386 --- /dev/null +++ b/config/locales/doorkeeper.zh-HK.yml @@ -0,0 +1,113 @@ +--- +zh-HK: + activerecord: + attributes: + doorkeeper/application: + name: 名稱 + redirect_uri: 轉接 URI + errors: + models: + doorkeeper/application: + attributes: + redirect_uri: + fragment_present: 'URI 不可包含 "#fragment" 部份' + invalid_uri: 必需有正確的 URI. + relative_uri: 必需為絕對 URI. + secured_uri: 必需使用有 HTTPS/SSL 加密的 URI. + doorkeeper: + applications: + buttons: + authorize: 認證 + cancel: 取消 + destroy: 移除 + edit: 編輯 + submit: 提交 + confirmations: + destroy: 是否確定? + edit: + title: 編輯應用程式 + form: + error: 噢!請檢查你表格的錯誤訊息 + help: + native_redirect_uri: 使用 %{native_redirect_uri} 作局部測試 + redirect_uri: 每行輸入一個 URI + scopes: 請用半形空格分開權限範圍 (scope)。留空表示使用預設的權限範圍 + index: + callback_url: 回傳網址 + name: 名稱 + new: 新增應用程式 + title: 你的應用程式 + new: + title: 新增應用程式 + show: + actions: 操作 + application_id: 應用程式 ID + callback_urls: 回傳網址 + scopes: 權限範圍 + secret: 密碼 + title: '應用程式︰ %{name}' + authorizations: + buttons: + authorize: 批准 + deny: 拒絕 + error: + title: 發生錯誤 + new: + able_to: 要求獲取權限 + prompt: 應用程式 %{client_name} 要求得到你用戶的部份權限 + title: 需要用戶授權 + show: + title: 授權代碼 + authorized_applications: + buttons: + revoke: 取消授權 + confirmations: + revoke: 是否確定要取消授權? + index: + application: 應用程式 + created_at: 授權於 + date_format: "%Y-%m-%d %H:%M:%S" + scopes: 權限範圍 + title: 已獲你授權的程用程式 + errors: + messages: + access_denied: 資源擁有者或授權伺服器不接受請求。 + credential_flow_not_configured: 資源擁有者密碼認證程序 (Resource Owner Password Credentials flow) 失敗,原因是 Doorkeeper.configure.resource_owner_from_credentials 沒有設定。 + invalid_client: 用戶程式認證 (Client authentication) 失敗,原因是用戶程式未有登記、沒有指定用戶程式 (client)、或者使用了不支援的認證方法 (method)。 + invalid_grant: 授權申請 (authorization grant) 不正確、過期、已被取消,或者無法對應授權請求 (authorization request) 內的轉接 URI,或者屬於別的用戶程式。 + invalid_redirect_uri: 不正確的轉接網址。 + invalid_request: 請求缺少了必要的參數、包含了不支援的參數、或者其他輸入錯誤。 + invalid_resource_owner: 資源擁有者的登入資訊錯誤、或者無法找到該資源擁有者。 + invalid_scope: 請求的權限範圍 (scope) 不正確、未有定義、或者輸入錯誤。 + invalid_token: + expired: access token 已經過期 + revoked: access token 已被取消 + unknown: access token 不正確 + resource_owner_authenticator_not_configured: 無法找到資源擁有者,原因是 Doorkeeper.configure.resource_owner_authenticator 沒有設定。 + server_error: 認證伺服器遇上未知狀況,令請求無法通過。 + temporarily_unavailable: 認證伺服器由於臨時負荷過重或者維護,目前未能處理請求。 + unauthorized_client: 用戶程式無權用此方法 (method) 請行這個請求。 + unsupported_grant_type: 授權伺服器不支援這個授權類型 (grant type)。 + unsupported_response_type: 授權伺服器不支援這個回應類型 (response type). + flash: + applications: + create: + notice: 已新增應用程式。 + destroy: + notice: 已刪除應用程式。 + update: + notice: 已更新應用程式。 + authorized_applications: + destroy: + notice: 已取消應用程式授權。 + layouts: + admin: + nav: + applications: 應用程式 + oauth2_provider: OAuth2 供應者 + application: + title: 需要 OAuth 授權 + scopes: + follow: 關注、封鎖、解除封鎖及取消關注用戶 + read: 閱讀你的用戶資料 + write: 以你的名義發佈文章 diff --git a/config/locales/en.yml b/config/locales/en.yml index 6c4738991e..6e32d2c385 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -76,6 +76,7 @@ en: x_seconds: "%{count}s" exports: blocks: You block + mutes: You mute csv: CSV follows: You follow storage: Media storage @@ -92,6 +93,7 @@ en: types: blocking: Blocking list following: Following list + muting: Muting list upload: Upload landing_strip_html: %{name} is a user on %{domain}. You can follow them or interact with them if you have an account anywhere in the fediverse. If you don't, you can sign up here. media_attachments: @@ -170,3 +172,54 @@ en: users: invalid_email: The e-mail address is invalid invalid_otp_token: Invalid two-factor code + will_paginate: + page_gap: "…" + errors: + 404: The page you were looking for doesn't exist. + 410: The page you were looking for doesn't exist anymore. + 422: + title: Security verification failed + content: Security verification failed. Are you blocking cookies? + reports: + reports: Reports + status: Status + unresolved: Unresolved + resolved: Resolved + id: ID + target: Target + reported_by: Reported by + comment: + label: Comment + none: None + view: View + report: "Report #%{id}" + delete: Delete + reported_account: Reported account + reported_by: Signalé par + silence_account: Silence account + suspend_account: Suspend account + mark_as_resolved: Mark as resolved + admin: + settings: + title: Site Settings + setting: Setting + click_to_edit: Click to edit + contact_information: + label: Contact information + username: Enter a username + email: Enter a public e-mail address + site_title: Site title + site_description: + title: Site description + desc_html: "Displayed as a paragraph on the frontpage and used as a meta tag.<a>
and <em>
."
+ site_description_extended:
+ title: Extended site description
+ desc_html: "Displayed on extended information page<a>
et <em>
."
+ site_description_extended:
+ title: Description étendue du site
+ desc_html: "Affichée sur la page d'informations complémentaires du site<a>
and <em>
が利用可能です。"
+ site_description_extended:
+ title: サイトの詳細な説明
+ desc_html: "インスタンスについてのページに表示されます。