Merge branch 'master' into skylight

This commit is contained in:
Eugen Rochko 2017-04-12 21:12:07 +02:00
commit f4ab6b9f2c
92 changed files with 2002 additions and 640 deletions

4
.gitignore vendored
View file

@ -28,3 +28,7 @@ neo4j/
# Ignore Capistrano customizations # Ignore Capistrano customizations
config/deploy/* config/deploy/*
# Ignore IDE files
.vscode/

View file

@ -7,7 +7,7 @@ There are three ways in which you can contribute to this repository:
2. By working on the back-end application 2. By working on the back-end application
3. By working on the front-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: 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 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. 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.

View file

@ -67,23 +67,52 @@ Consult the example configuration file, `.env.production.sample` for the full li
[![](https://images.microbadger.com/badges/version/gargron/mastodon.svg)](https://microbadger.com/images/gargron/mastodon "Get your own version badge on microbadger.com") [![](https://images.microbadger.com/badges/image/gargron/mastodon.svg)](https://microbadger.com/images/gargron/mastodon "Get your own image badge on microbadger.com") [![](https://images.microbadger.com/badges/version/gargron/mastodon.svg)](https://microbadger.com/images/gargron/mastodon "Get your own version badge on microbadger.com") [![](https://images.microbadger.com/badges/image/gargron/mastodon.svg)](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 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 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 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. **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. This approach makes updating to the latest version a real breeze.
git pull 1. `git pull` to download updates from the repository
2. `docker-compose build` to compile the Docker image out of the changed source files
To pull down the updates, re-run 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
docker-compose build 5. `docker-compose up -d` to re-create (restart) containers and pick up the changes
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.
## Deployment without Docker ## Deployment without Docker
@ -129,7 +152,7 @@ Docker is great for quickly trying out software, but it has its drawbacks too. I
[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy) [![Deploy](https://www.herokucdn.com/deploy/button.svg)](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 ## Development with Vagrant

View file

@ -1,4 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="1000" viewBox="0 0 1000 1000"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000" height="1000" width="1000"><g fill="#189efc"><path d="M500 0A500 500 0 0 0 0 500a500 500 0 0 0 500 500 500 500 0 0 0 500-500A500 500 0 0 0 500 0zm-2.5 271.1h107.24c-20.56 14.471-27.24 57.064-27.24 78.927v202.145c0 43.726-35.202 78.928-80 78.928s-80-35.202-80-78.928V350.027c0-43.725 35.202-78.927 80-78.927zm-276 48.9c44.798 0 80 35.202 80 78.928v202.144c0 21.863 6.68 64.456 27.24 78.928H221.5c-44.798 0-80-35.202-80-78.928V398.928c0-43.726 35.202-78.928 80-78.928zm550.24 0c44.799 0 80 35.202 80 78.928v202.144c0 43.726-35.201 78.928-80 78.928H664.5c20.56-14.472 27.24-57.065 27.24-78.928V398.928c0-43.726 35.202-78.928 80-78.928z"/><g transform="translate(-2)"><circle cx="223.5" cy="410.5" r="27.5"/><circle cx="223.5" cy="500.5" r="27.5"/><circle cx="223.5" cy="590.5" r="27.5"/></g><g transform="matrix(1 0 0 -1 274 951)"><circle cx="223.5" cy="410.5" r="27.5"/><circle cx="223.5" cy="500.5" r="27.5"/><circle cx="223.5" cy="590.5" r="27.5"/></g><g transform="matrix(-1 0 0 1 995 0)"><circle cx="223.5" cy="410.5" r="27.5"/><circle cx="223.5" cy="500.5" r="27.5"/><circle cx="223.5" cy="590.5" r="27.5"/></g></g></svg>
<path d="M527.194 543.7a28.362 28.362 0 0 0-56.723 0 25.73 25.73 0 0 0 2.67 11.674 26.42 26.42 0 0 0 5.672 8.34 28.2 28.2 0 0 0 40.04 0 31.87 31.87 0 0 0 6.006-8.34 28.8 28.8 0 0 0 2.336-11.674m-48.382-113.413a28.308 28.308 0 1 0 40.04 40.027 37.2 37.2 0 0 0 4.67-5.67 28.092 28.092 0 0 0 3.67-14.343 27.29 27.29 0 0 0-8.34-20.012 28.24 28.24 0 0 0-5.006-4 26.958 26.958 0 0 0-15.015-4.336 27.31 27.31 0 0 0-20.02 8.34m20.02-101.735a28.476 28.476 0 1 0 20.02 8.34 27.31 27.31 0 0 0-20.02-8.34M231.9 573.717a28.18 28.18 0 1 0 8.342 20.012 27.308 27.308 0 0 0-8.342-20.014m-40.04-93.4a28.352 28.352 0 0 0 20.02 48.366 26.958 26.958 0 0 0 15.015-4.336 28.255 28.255 0 0 0 5.005-4 27.29 27.29 0 0 0 8.342-20.013 28.09 28.09 0 0 0-3.67-14.343 37.21 37.21 0 0 0-4.67-5.67 28.2 28.2 0 0 0-40.04 0m40.04-93.4a28.2 28.2 0 0 0-40.04 0 26.425 26.425 0 0 0-5.673 8.34 25.73 25.73 0 0 0-2.67 11.673 28.315 28.315 0 0 0 48.38 20.018 27.29 27.29 0 0 0 8.342-20.012 28.8 28.8 0 0 0-2.336-11.674 31.87 31.87 0 0 0-6.006-8.34m550.55 178.453a28.476 28.476 0 1 0 20.02 8.34 27.31 27.31 0 0 0-20.02-8.34m20.02-85.057a28.2 28.2 0 0 0-40.04 0 37.2 37.2 0 0 0-4.672 5.67 28.092 28.092 0 0 0-3.67 14.343 27.29 27.29 0 0 0 8.342 20.013 28.248 28.248 0 0 0 5.005 4 26.96 26.96 0 0 0 15.015 4.336 28.3 28.3 0 0 0 20.02-48.366m-46.046-85.057a28.8 28.8 0 0 0-2.336 11.673 28.362 28.362 0 0 0 56.723 0 25.73 25.73 0 0 0-2.668-11.674 26.427 26.427 0 0 0-5.672-8.34 28.2 28.2 0 0 0-40.04 0 31.86 31.86 0 0 0-6.007 8.343z" fill="#2b90d9"/>
<path d="M853.52 146.764Q707.04 0 499.833 0 292.96 0 146.48 146.764 0 293.2 0 500q0 207.138 146.48 353.57T499.833 1000q207.207 0 353.687-146.43T1000 500q0-206.8-146.48-353.236zM213.547 708.806h-3.337q-43.043 0-73.407-30.02-30.03-30.354-30.03-73.382V395.93v-.666q1.335-41.027 30.03-69.713 30.364-30.35 73.407-30.35t73.073 30.354q29.363 29.02 30.364 70.38V615.41q2.336 55.037 46.713 93.4zM600.6 554.7q-1 41.36-30.364 70.38-30.03 30.353-73.073 30.354t-73.407-30.354q-28.7-28.686-30.03-69.713V345.23q0-43.03 30.03-73.382 30.364-30.02 73.407-30.02h150.15q-44.378 38.36-46.713 93.4zm286.954 50.7q0 43.03-30.03 73.382-30.364 30.02-73.407 30.02h-150.15q44.378-38.36 46.713-93.4v-219.47q1-41.362 30.364-70.38 30.03-30.355 73.073-30.355t73.407 30.354q28.7 28.687 30.03 69.714V605.4z" fill="#2b90d9"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -41,15 +41,18 @@ import Report from '../features/report';
import { IntlProvider, addLocaleData } from 'react-intl'; import { IntlProvider, addLocaleData } from 'react-intl';
import en from 'react-intl/locale-data/en'; import en from 'react-intl/locale-data/en';
import de from 'react-intl/locale-data/de'; 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 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 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 getMessagesForLocale from '../locales';
import { hydrateStore } from '../actions/store'; 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({ const Mastodon = React.createClass({

View file

@ -43,7 +43,7 @@ const GettingStarted = ({ intl, me }) => {
<div className='scrollable optionally-scrollable' style={{ display: 'flex', flexDirection: 'column' }}> <div className='scrollable optionally-scrollable' style={{ display: 'flex', flexDirection: 'column' }}>
<div className='static-content getting-started'> <div className='static-content getting-started'>
<p><FormattedMessage id='getting_started.open_source_notice' defaultMessage='Mastodon is open source software. You can contribute or report issues on GitHub at {github}. {apps}.' values={{ github: <a href="https://github.com/tootsuite/mastodon" target="_blank">tootsuite/mastodon</a>, apps: <a href="https://github.com/tootsuite/mastodon/blob/master/docs/Using-Mastodon/Apps.md" target="_blank"><FormattedMessage id='getting_started.apps' defaultMessage='Various apps are available' /></a> }} /></p> <p><FormattedMessage id='getting_started.open_source_notice' defaultMessage='Mastodon is open source software. You can contribute or report issues on GitHub at {github}. {apps}.' values={{ github: <a href="https://github.com/tootsuite/mastodon" target="_blank">tootsuite/mastodon</a>, apps: <a href="https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md" target="_blank"><FormattedMessage id='getting_started.apps' defaultMessage='Various apps are available' /></a> }} /></p>
</div> </div>
</div> </div>
</Column> </Column>

View file

@ -5,28 +5,35 @@ const es = {
"status.mention": "Mencionar", "status.mention": "Mencionar",
"status.delete": "Borrar", "status.delete": "Borrar",
"status.reply": "Responder", "status.reply": "Responder",
"status.reblog": "Republicar", "status.reblog": "Retoot",
"status.favourite": "Favorito", "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", "video_player.toggle_sound": "Act/Desac. sonido",
"account.mention": "Mención", "account.mention": "Mencionar",
"account.edit_profile": "Editar perfil", "account.edit_profile": "Editar perfil",
"account.unblock": "Desbloquear", "account.unblock": "Desbloquear",
"account.unfollow": "Dejar de seguir", "account.unfollow": "Dejar de seguir",
"account.mute": "Silenciar",
"account.block": "Bloquear", "account.block": "Bloquear",
"account.follow": "Seguir", "account.follow": "Seguir",
"account.block": "Bloquear",
"account.posts": "Publicaciones", "account.posts": "Publicaciones",
"account.follows": "Seguir", "account.follows": "Seguir",
"account.followers": "Seguidores", "account.followers": "Seguidores",
"account.follows_you": "Te sigue", "account.follows_you": "Te sigue",
"account.requested": "Esperando aprobación",
"getting_started.heading": "Primeros pasos", "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_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_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.home": "Inicio",
"column.mentions": "Menciones", "column.community": "Historia local",
"column.public": "Historia pública", "column.public": "Historia federada",
"column.notifications": "Notificaciones", "column.notifications": "Notificaciones",
"tabs_bar.compose": "Redactar", "tabs_bar.compose": "Redactar",
"tabs_bar.home": "Inicio", "tabs_bar.home": "Inicio",
@ -34,23 +41,47 @@ const es = {
"tabs_bar.public": "Público", "tabs_bar.public": "Público",
"tabs_bar.notifications": "Notificaciones", "tabs_bar.notifications": "Notificaciones",
"compose_form.placeholder": "¿En qué estás pensando?", "compose_form.placeholder": "¿En qué estás pensando?",
"compose_form.publish": "Publicar", "compose_form.publish": "Tootear",
"compose_form.sensitive": "Marcar el contenido como sensible", "compose_form.sensitive": "Marcar contenido como sensible",
"compose_form.unlisted": "Privado", "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.edit_profile": "Editar perfil",
"navigation_bar.preferences": "Preferencias", "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", "navigation_bar.logout": "Cerrar sesión",
"reply_indicator.cancel": "Cancelar", "reply_indicator.cancel": "Cancelar",
"search.placeholder": "Buscar", "search.placeholder": "Buscar",
"search.account": "Cuenta", "search.account": "Cuenta",
"search.hashtag": "Etiqueta", "search.hashtag": "Etiqueta",
"upload_button.label": "Añadir medio", "upload_button.label": "Subir multimedia",
"upload_form.undo": "Deshacer", "upload_form.undo": "Deshacer",
"notification.follow": "{name} le esta ahora siguiendo", "notification.follow": "{name} te empezó a seguir",
"notification.favourite": "{name} marcó como favorito su estado", "notification.favourite": "{name} marcó tu estado como favorito",
"notification.reblog": "{name} volvió a publicar su estado", "notification.reblog": "{name} ha retooteado tu estado",
"notification.mention": "Fue mencionado por {name}" "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; export default es;

View file

@ -34,7 +34,7 @@ const fr = {
"account.report": "Signaler", "account.report": "Signaler",
"account.disclaimer": "Ce compte est situé sur une autre instance. Les nombres peuvent être plus grands.", "account.disclaimer": "Ce compte est situé sur une autre instance. Les nombres peuvent être plus grands.",
"getting_started.heading": "Pour commencer", "getting_started.heading": "Pour commencer",
"getting_started.about_addressing": "Vous pouvez suivre les statuts de quelquun en entrant dans le champs de recherche leur identifiant et le domaine de leur instance, séparés par un @ à la manière dune adresse courriel.", "getting_started.about_addressing": "Vous pouvez suivre les statuts de quelquun en entrant dans le champ de recherche leur identifiant et le domaine de leur instance, séparés par un @ à la manière dune adresse courriel.",
"getting_started.about_shortcuts": "Si cette personne utilise la même instance que vous, lidentifiant suffit. Cest le même principe pour mentionner quelquun dans vos statuts.", "getting_started.about_shortcuts": "Si cette personne utilise la même instance que vous, lidentifiant suffit. Cest le même principe pour mentionner quelquun dans vos statuts.",
"getting_started.about_developer": "Pour suivre le développeur de ce projet, cest Gargron@mastodon.social", "getting_started.about_developer": "Pour suivre le développeur de ce projet, cest 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.", "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.short": "Privé",
"privacy.private.long": "Nafficher que pour vos abonné⋅e⋅s", "privacy.private.long": "Nafficher que pour vos abonné⋅e⋅s",
"privacy.direct.short": "Direct", "privacy.direct.short": "Direct",
"privacy.direct.long": "Nafficher que pour les personnes mentionnées", "privacy.direct.long": "Nafficher que pour les personnes mentionnées",
"privacy.change": "Ajuster la confidentialité du message", "privacy.change": "Ajuster la confidentialité du message",
"media_gallery.toggle_visible": "Modifier la visibilité", "media_gallery.toggle_visible": "Modifier la visibilité",
"missing_indicator.label": "Non trouvé", "missing_indicator.label": "Non trouvé",

View file

@ -3,12 +3,14 @@ import de from './de';
import es from './es'; import es from './es';
import hu from './hu'; import hu from './hu';
import fr from './fr'; import fr from './fr';
import no from './no';
import pt from './pt'; import pt from './pt';
import uk from './uk'; import uk from './uk';
import fi from './fi'; import fi from './fi';
import eo from './eo'; import eo from './eo';
import ru from './ru'; import ru from './ru';
import ja from './ja'; import ja from './ja';
import zh_hk from './zh-hk';
const locales = { const locales = {
@ -17,13 +19,14 @@ const locales = {
es, es,
hu, hu,
fr, fr,
no,
pt, pt,
uk, uk,
fi, fi,
eo, eo,
ru, ru,
ja ja,
'zh-HK': zh_hk,
}; };
export default function getMessagesForLocale (locale) { export default function getMessagesForLocale (locale) {

View file

@ -2,42 +2,45 @@ const ja = {
"column_back_button.label": "戻る", "column_back_button.label": "戻る",
"lightbox.close": "閉じる", "lightbox.close": "閉じる",
"loading_indicator.label": "読み込み中...", "loading_indicator.label": "読み込み中...",
"status.mention": "@{name}さんへの返信", "status.mention": "@{name} さんへの返信",
"status.delete": "削除", "status.delete": "削除",
"status.reply": "返信", "status.reply": "返信",
"status.reblog": "ブースト", "status.reblog": "ブースト",
"status.favourite": "お気に入り", "status.favourite": "お気に入り",
"status.reblogged_by": "{name}さんにブーストされました", "status.reblogged_by": "{name} さんにブーストされました",
"status.sensitive_warning": "不適切なコンテンツ", "status.sensitive_warning": "不適切なコンテンツ",
"status.sensitive_toggle": "見るにはクリック", "status.sensitive_toggle": "クリックして表示",
"status.show_more": "もっと見る", "status.show_more": "もっと見る",
"status.show_less": "隠す", "status.show_less": "隠す",
"status.open": "Expand this status", "status.open": "Expand this status",
"status.report": "@{name}さんを報告", "status.report": "@{name} さんを報告",
"video_player.toggle_sound": "音切り替え", "video_player.toggle_sound": "音切り替え",
"account.mention": "@{name}さんに返信", "account.mention": "@{name} さんに返信",
"account.edit_profile": "プロフィール返信", "account.edit_profile": "プロフィールを編集",
"account.unblock": "@{name}さんのブロックを解除", "account.unblock": "@{name} さんのブロックを解除",
"account.unfollow": "フォロー解除", "account.unfollow": "フォロー解除",
"account.block": "@{name}さんをブロック", "account.block": "@{name} さんをブロック",
"account.mute": "ミュート",
"account.unmute": "ミュート解除",
"account.follow": "フォロー", "account.follow": "フォロー",
"account.posts": "投稿", "account.posts": "投稿",
"account.follows": "フォロー", "account.follows": "フォロー",
"account.followers": "フォロワー", "account.followers": "フォロワー",
"account.follows_you": "フォロー", "account.follows_you": "フォローされています",
"account.requested": "承認待ち", "account.requested": "承認待ち",
"getting_started.heading": "スタート", "getting_started.heading": "スタート",
"getting_started.about_addressing": "ドメインとユーザー名を知っているなら検索フォームに入力すればフォローできます。", "getting_started.about_addressing": "ドメインとユーザー名を知っているなら検索フォームに入力すればフォローできます。",
"getting_started.about_shortcuts": "対象のアカウントがあなたと同じドメインのユーザーならばユーザー名のみで検索できます。これは返信のときも一緒です。", "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.home": "ホーム",
"column.community": "ローカルタイムライン", "column.community": "ローカルタイムライン",
"column.public": "連タイムライン", "column.public": "連タイムライン",
"column.notifications": "通知", "column.notifications": "通知",
"tabs_bar.compose": "Compose", "tabs_bar.compose": "投稿",
"tabs_bar.home": "ホーム", "tabs_bar.home": "ホーム",
"tabs_bar.mentions": "返信", "tabs_bar.mentions": "返信",
"tabs_bar.public": "連邦タイムライン", "tabs_bar.local_timeline": "ローカルTL",
"tabs_bar.federated_timeline": "連合TL",
"tabs_bar.notifications": "通知", "tabs_bar.notifications": "通知",
"compose_form.placeholder": "今なにしてる?", "compose_form.placeholder": "今なにしてる?",
"compose_form.publish": "トゥート", "compose_form.publish": "トゥート",
@ -46,27 +49,35 @@ const ja = {
"compose_form.private": "非公開にする", "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.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": "公開タイムラインに表示しない", "compose_form.unlisted": "公開タイムラインに表示しない",
"navigation_bar.edit_profile": "プロフィール編集", "navigation_bar.edit_profile": "プロフィール編集",
"navigation_bar.preferences": "ユーザー設定", "navigation_bar.preferences": "ユーザー設定",
"navigation_bar.community_timeline": "ローカルタイムライン", "navigation_bar.community_timeline": "ローカルタイムライン",
"navigation_bar.public_timeline": "連タイムライン", "navigation_bar.public_timeline": "連タイムライン",
"navigation_bar.logout": "ログアウト", "navigation_bar.logout": "ログアウト",
"navigation_bar.favourites": "お気に入り",
"navigation_bar.blocks": "ブロックしたユーザー",
"navigation_bar.info": "サーバー情報",
"reply_indicator.cancel": "キャンセル", "reply_indicator.cancel": "キャンセル",
"search.placeholder": "検索", "search.placeholder": "検索",
"search.account": "アカウント", "search.account": "アカウント",
"search.hashtag": "ハッシュタグ", "search.hashtag": "ハッシュタグ",
"upload_button.label": "メディアを追加", "upload_button.label": "メディアを追加",
"upload_form.undo": "やり直す", "upload_form.undo": "やり直す",
"notification.follow": "{name}さんにフォローされました", "notification.follow": "{name} さんにフォローされました",
"notification.favourite": "{name}さんがあなたのトゥートをいいねしました", "notification.favourite": "{name} さんがあなたのトゥートをお気に入りに登録しました",
"notification.reblog": "{name}さんがあなたのトゥートをブーストしました", "notification.reblog": "{name} さんがあなたのトゥートをブーストしました",
"notification.mention": "{name}さんがあなたに返信しました", "notification.mention": "{name} さんがあなたに返信しました",
"notifications.column_settings.alert": "デスクトップ通知", "notifications.column_settings.alert": "デスクトップ通知",
"notifications.column_settings.show": "表示項目", "notifications.column_settings.show": "カラムに表示",
"notifications.column_settings.follow": "新しいフォロワー:", "notifications.column_settings.follow": "新しいフォロワー",
"notifications.column_settings.favourite": "いいね:", "notifications.column_settings.favourite": "お気に入り",
"notifications.column_settings.mention": "返信:", "notifications.column_settings.mention": "返信",
"notifications.column_settings.reblog": "ブースト:", "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; export default ja;

View file

@ -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;

View file

@ -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;

View file

@ -76,7 +76,8 @@ function appendMedia(state, media) {
map.update('media_attachments', list => list.push(media)); map.update('media_attachments', list => list.push(media));
map.set('is_uploading', false); map.set('is_uploading', false);
map.set('resetFileKey', Math.floor((Math.random() * 0x10000))); 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() + ' ');
}); });
}; };

View file

@ -14,7 +14,7 @@
} }
&:after { &:after {
background: rgba($color8, 0.5); background: linear-gradient(rgba($color8, 0.5), rgba($color8, 0.8));
display: block; display: block;
content: ""; content: "";
position: absolute; position: absolute;
@ -72,7 +72,6 @@
position: relative; position: relative;
z-index: 2; z-index: 2;
flex-direction: row; flex-direction: row;
background: rgba(0,0,0,0.5);
} }
.details-counters { .details-counters {
@ -388,6 +387,5 @@
.account__header__content { .account__header__content {
font-size: 14px; font-size: 14px;
color: $color1; color: $color1;
text-shadow: 0 0 2px $color8;
} }
} }

View file

@ -4,305 +4,13 @@
@import 'fonts/montserrat'; @import 'fonts/montserrat';
@import 'font-awesome'; @import 'font-awesome';
/* http://meyerweb.com/eric/tools/css/reset/ @import 'reset';
v2.0 | 20110126 @import 'basics';
License: none (public domain) @import 'containers';
*/ @import 'lists';
@import 'footer';
html, body, div, span, applet, object, iframe, @import 'compact_header';
h1, h2, h3, h4, h5, h6, p, blockquote, pre, @import 'landing_strip';
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 'forms'; @import 'forms';
@import 'accounts'; @import 'accounts';
@import 'stream_entries'; @import 'stream_entries';

View file

@ -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;
}

View file

@ -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;
}
}
}

View file

@ -59,7 +59,7 @@
z-index: 2; z-index: 2;
} }
@media screen and (min-width: 1024px) { @media screen and (min-width: 1025px) {
.column-icon-clear { .column-icon-clear {
top: 10px; 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 { .columns-area {
padding: 0; padding: 0;
} }

View file

@ -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;
}
}
}

View file

@ -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;
}
}
}
}

View file

@ -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;
}
}

View file

@ -0,0 +1,8 @@
.no-list {
list-style: none;
li {
display: inline-block;
margin: 0 5px;
}
}

View file

@ -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;
}

View file

@ -26,7 +26,7 @@ module Localized
end end
def default_locale def default_locale
ENV.fetch('DEFAULT_LOCALE') { ENV.fetch('DEFAULT_LOCALE') {
http_accept_language.compatible_language_from(I18n.available_locales) || I18n.default_locale http_accept_language.compatible_language_from(I18n.available_locales) || I18n.default_locale
} }
end end

View file

@ -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

View file

@ -2,15 +2,11 @@
module Settings module Settings
module Exports module Exports
class BlockedAccountsController < ApplicationController class BlockedAccountsController < BaseController
before_action :authenticate_user! private
def index def export_accounts
export_data = Export.new(current_account.blocking).to_csv current_account.blocking
respond_to do |format|
format.csv { send_data export_data, filename: 'blocking.csv' }
end
end end
end end
end end

View file

@ -2,15 +2,11 @@
module Settings module Settings
module Exports module Exports
class FollowingAccountsController < ApplicationController class FollowingAccountsController < BaseController
before_action :authenticate_user! private
def index def export_accounts
export_data = Export.new(current_account.following).to_csv current_account.following
respond_to do |format|
format.csv { send_data export_data, filename: 'following.csv' }
end
end end
end end
end end

View file

@ -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

View file

@ -9,5 +9,6 @@ class Settings::ExportsController < ApplicationController
@total_storage = current_account.media_attachments.sum(:file_file_size) @total_storage = current_account.media_attachments.sum(:file_file_size)
@total_follows = current_account.following.count @total_follows = current_account.following.count
@total_blocks = current_account.blocking.count @total_blocks = current_account.blocking.count
@total_mutes = current_account.muting.count
end end
end end

View file

@ -27,8 +27,6 @@ class StreamEntriesController < ApplicationController
def embed def embed
response.headers['X-Frame-Options'] = 'ALLOWALL' response.headers['X-Frame-Options'] = 'ALLOWALL'
@external_links = true
return gone if @stream_entry.activity.nil? return gone if @stream_entry.activity.nil?
render layout: 'embedded' render layout: 'embedded'

View file

@ -1,7 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
class XrdController < ApplicationController class XrdController < ApplicationController
before_action :set_default_format_json, only: :webfinger
before_action :set_default_format_xml, only: :host_meta before_action :set_default_format_xml, only: :host_meta
def host_meta def host_meta
@ -31,20 +30,8 @@ class XrdController < ApplicationController
request.format = 'xml' if request.headers['HTTP_ACCEPT'].nil? && params[:format].nil? request.format = 'xml' if request.headers['HTTP_ACCEPT'].nil? && params[:format].nil?
end end
def set_default_format_json
request.format = 'json' if request.headers['HTTP_ACCEPT'].nil? && params[:format].nil?
end
def username_from_resource def username_from_resource
if resource_param =~ /\Ahttps?:\/\// WebfingerResource.new(resource_param).username
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
end end
def pem_to_magic_key(public_key) def pem_to_magic_key(public_key)

View file

@ -6,10 +6,21 @@ module Admin::AccountsHelper
end end
def filter_link_to(text, more_params) 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 end
def table_link_to(icon, text, path, options = {}) def table_link_to(icon, text, path, options = {})
link_to safe_join([fa_icon(icon), text]), path, options.merge(class: 'table-action-link') link_to safe_join([fa_icon(icon), text]), path, options.merge(class: 'table-action-link')
end 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 end

View file

@ -6,15 +6,16 @@ module SettingsHelper
de: 'Deutsch', de: 'Deutsch',
es: 'Español', es: 'Español',
eo: 'Esperanto', eo: 'Esperanto',
pt: 'Português',
fr: 'Français', fr: 'Français',
hu: 'Magyar', hu: 'Magyar',
uk: 'Українська', no: 'Norsk',
'zh-CN': '简体中文', pt: 'Português',
fi: 'Suomi', fi: 'Suomi',
ru: 'Русский', ru: 'Русский',
uk: 'Українська',
ja: '日本語', ja: '日本語',
'zh-CN': '简体中文',
'zh-HK': '繁體中文(香港)',
}.freeze }.freeze
def human_locale(locale) def human_locale(locale)

View file

@ -2,22 +2,40 @@
module StreamEntriesHelper module StreamEntriesHelper
def display_name(account) 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 end
def acct(account) 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 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']
classes << 'entry-reblog u-repost-of h-cite' if status.reblog? classes << 'entry-predecessor' if is_predecessor
classes << 'entry-predecessor u-in-reply-to h-cite' if is_predecessor classes << 'entry-reblog' if status.reblog?
classes << 'entry-successor u-comment h-cite' if is_successor classes << 'entry-successor' if is_successor
classes << 'entry-center h-entry' if include_threads classes << 'entry-center' if include_threads
classes.join(' ') classes.join(' ')
end 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) def rtl?(text)
return false if text.empty? return false if text.empty?
@ -30,4 +48,10 @@ module StreamEntriesHelper
rtl_size / ltr_size > 0.3 rtl_size / ltr_size > 0.3
end end
private
def embedded_view?
params[:controller] == 'stream_entries' && params[:action] == 'embed'
end
end end

View file

@ -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: :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, '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:preferredUsername', account.username)
append_element(author, 'poco:displayName', account.display_name) unless account.display_name.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) unless account.note.blank? append_element(author, 'poco:note', Formatter.instance.simplified_format(account).to_str) if account.note?
append_element(author, 'mastodon:scope', account.locked? ? :private : :public) append_element(author, 'mastodon:scope', account.locked? ? :private : :public)
author author
@ -327,7 +327,7 @@ class AtomSerializer
end end
def serialize_status_attributes(entry, status) 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') append_element(entry, 'content', Formatter.instance.format(status.proper).to_str, type: 'html')
status.mentions.each do |mentioned| status.mentions.each do |mentioned|

View file

@ -95,6 +95,6 @@ class Formatter
end end
def mention_html(match, account) def mention_html(match, account)
"#{match.split('@').first}<a href=\"#{TagManager.instance.url_for(account)}\" class=\"h-card u-url p-nickname mention\">@<span>#{account.username}</span></a>" "#{match.split('@').first}<span class=\"h-card\"><a href=\"#{TagManager.instance.url_for(account)}\" class=\"u-url mention\">@#{account.username}</a></span>"
end end
end end

View file

@ -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

View file

@ -7,9 +7,10 @@ class UserMailer < Devise::Mailer
def confirmation_instructions(user, token, _opts = {}) def confirmation_instructions(user, token, _opts = {})
@resource = user @resource = user
@token = token @token = token
@instance = Rails.configuration.x.local_domain
I18n.with_locale(@resource.locale || I18n.default_locale) do 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
end end
@ -18,7 +19,7 @@ class UserMailer < Devise::Mailer
@token = token @token = token
I18n.with_locale(@resource.locale || I18n.default_locale) do 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
end end
@ -26,7 +27,7 @@ class UserMailer < Devise::Mailer
@resource = user @resource = user
I18n.with_locale(@resource.locale || I18n.default_locale) do 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 end
end end

View file

@ -3,7 +3,7 @@
class Import < ApplicationRecord class Import < ApplicationRecord
self.inheritance_column = false self.inheritance_column = false
enum type: [:following, :blocking] enum type: [:following, :blocking, :muting]
belongs_to :account belongs_to :account

View file

@ -12,7 +12,7 @@ class AfterBlockService < BaseService
home_key = FeedManager.instance.key(:home, account.id) home_key = FeedManager.instance.key(:home, account.id)
redis.pipelined do 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) redis.zrem(home_key, status.id)
end end
end end

View file

@ -34,7 +34,7 @@ class FanOutOnWriteService < BaseService
def deliver_to_followers(status) def deliver_to_followers(status)
Rails.logger.debug "Delivering status #{status.id} to followers" 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) FeedInsertWorker.perform_async(status.id, follower.id)
end end
end end

View file

@ -12,7 +12,7 @@ class MuteService < BaseService
def clear_home_timeline(account, target_account) def clear_home_timeline(account, target_account)
home_key = FeedManager.instance.key(:home, account.id) 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) redis.zrem(home_key, status.id)
end end
end end

View file

@ -12,7 +12,7 @@ class SuspendAccountService < BaseService
private private
def purge_content def purge_content
@account.statuses.find_each do |status| @account.statuses.reorder(nil).find_each do |status|
RemoveStatusService.new.call(status) RemoveStatusService.new.call(status)
end end

View file

@ -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'

View file

@ -13,7 +13,7 @@
%h1.name %h1.name
%span.p-name.emojify= display_name(@account) %span.p-name.emojify= display_name(@account)
%small %small
%span.p-nickname= "@#{@account.username}" %span= "@#{@account.username}"
= fa_icon('lock') if @account.locked? = fa_icon('lock') if @account.locked?
.details .details
.bio .bio

View file

@ -21,9 +21,9 @@
%i.fa.fa-check %i.fa.fa-check
%td= distance_of_time_in_words(Time.now, subscription.expires_at) %td= distance_of_time_in_words(Time.now, subscription.expires_at)
%td %td
- if subscription.last_successful_delivery_at.nil? - if subscription.last_successful_delivery_at?
%i.fa.fa-times
- else
= l subscription.last_successful_delivery_at = l subscription.last_successful_delivery_at
- else
%i.fa.fa-times
= paginate @subscriptions = paginate @subscriptions

View file

@ -1,12 +1,12 @@
- content_for :page_title do - content_for :page_title do
Reports = t('reports.reports')
.filters .filters
.filter-subset .filter-subset
%strong Status %strong= t('reports.status')
%ul %ul
%li= filter_link_to 'Unresolved', action_taken: nil %li= filter_link_to t('reports.unresolved'), action_taken: nil
%li= filter_link_to 'Resolved', action_taken: '1' %li= filter_link_to t('reports.resolved'), action_taken: '1'
= form_tag do = form_tag do
@ -14,10 +14,10 @@
%thead %thead
%tr %tr
%th %th
%th ID %th= t('reports.id')
%th Target %th= t('reports.target')
%th Reported by %th= t('reports.reported_by')
%th Comment %th= t('reports.comment.label')
%th %th
%tbody %tbody
- @reports.each do |report| - @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.target_account.acct, admin_account_path(report.target_account.id)
%td= link_to report.account.acct, admin_account_path(report.account.id) %td= link_to report.account.acct, admin_account_path(report.account.id)
%td= truncate(report.comment, length: 30, separator: ' ') %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 = paginate @reports

View file

@ -1,20 +1,18 @@
- content_for :page_title do - content_for :page_title do
= "Report ##{@report.id}" = t('reports.report', id: @report.id)
.report-accounts .report-accounts
.report-accounts__item .report-accounts__item
%strong Reported account: %strong= t('reports.reported_account')
= render partial: 'authorize_follow/card', locals: { account: @report.target_account } = render partial: 'authorize_follow/card', locals: { account: @report.target_account }
.report-accounts__item .report-accounts__item
%strong Reported by: %strong= t('reports.reported_by')
= render partial: 'authorize_follow/card', locals: { account: @report.account } = render partial: 'authorize_follow/card', locals: { account: @report.account }
%p %p
%strong Comment: %strong= t('reports.comment.label')
- if @report.comment.blank? \:
None = @report.comment.presence || t('reports.comment.none')
- else
= @report.comment
- unless @statuses.empty? - unless @statuses.empty?
%hr/ %hr/
@ -24,7 +22,7 @@
.activity-stream.activity-stream-headless .activity-stream.activity-stream-headless
.entry= render partial: 'stream_entries/simple_status', locals: { status: status } .entry= render partial: 'stream_entries/simple_status', locals: { status: status }
.report-status__actions .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' = fa_icon 'trash'
- if !@report.action_taken? - if !@report.action_taken?
@ -32,10 +30,10 @@
%div{ style: 'overflow: hidden' } %div{ style: 'overflow: hidden' }
%div{ style: 'float: right' } %div{ style: 'float: right' }
= link_to 'Silence account', silence_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 'Suspend account', suspend_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' } %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? - elsif !@report.action_taken_by_account.nil?
%hr/ %hr/

View file

@ -1,52 +1,40 @@
- content_for :page_title do - content_for :page_title do
Site Settings = t('admin.settings.title')
%table.table %table.table
%colgroup %colgroup
%col{ width: '35%' }/ %col{ width: '35%' }/
%thead %thead
%tr %tr
%th Setting %th= t('admin.settings.setting')
%th Click to edit %th= t('admin.settings.click_to_edit')
%tbody %tbody
%tr %tr
%td{ rowspan: 2 } %td{ rowspan: 2 }
%strong Contact information %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: 'Enter a username' %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 %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 %tr
%td %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']) %td= best_in_place @settings['site_title'], :value, url: admin_setting_path(@settings['site_title'])
%tr %tr
%td %td
%strong Site description %strong= t('admin.settings.site_description.title')
%br/ %p= t('admin.settings.site_description.desc_html')
Displayed as a paragraph on the frontpage and used as a meta tag.
%br/
You can use HTML tags, in particular
%code= '<a>'
and
%code= '<em>'
%td= best_in_place @settings['site_description'], :value, as: :textarea, url: admin_setting_path(@settings['site_description']) %td= best_in_place @settings['site_description'], :value, as: :textarea, url: admin_setting_path(@settings['site_description'])
%tr %tr
%td %td
%strong Extended site description %strong= t('admin.settings.site_description_extended.title')
%br/ %p= t('admin.settings.site_description_extended.desc_html')
Displayed on extended information page
%br/
You can use HTML tags
%td= best_in_place @settings['site_extended_description'], :value, as: :textarea, url: admin_setting_path(@settings['site_extended_description']) %td= best_in_place @settings['site_extended_description'], :value, as: :textarea, url: admin_setting_path(@settings['site_extended_description'])
%tr %tr
%td %td
%strong Open registration %strong= t('admin.settings.registrations.open.title')
%td= best_in_place @settings['open_registrations'], :value, as: :checkbox, collection: { false: 'Disabled', true: 'Enabled'}, url: admin_setting_path(@settings['open_registrations']) %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 %tr
%td %td
%strong Closed registration message %strong= t('admin.settings.registrations.closed_message.title')
%br/ %p= t('admin.settings.registrations.closed_message.desc_html')
Displayed on frontpage when registrations are closed
%br/
You can use HTML tags
%td= best_in_place @settings['closed_registrations_message'], :value, as: :textarea, url: admin_setting_path(@settings['closed_registrations_message']) %td= best_in_place @settings['closed_registrations_message'], :value, as: :textarea, url: admin_setting_path(@settings['closed_registrations_message'])

View file

@ -7,5 +7,5 @@
%strong.emojify= display_name(account) %strong.emojify= display_name(account)
%span= "@#{account.acct}" %span= "@#{account.acct}"
- unless account.note.blank? - if account.note?
.account__header__content.emojify= Formatter.instance.simplified_format(account) .account__header__content.emojify= Formatter.instance.simplified_format(account)

View file

@ -1,5 +1,5 @@
- content_for :page_title do - content_for :page_title do
The page you were looking for doesn't exist = t('errors.404')
- content_for :content do - content_for :content do
The page you were looking for doesn't exist = t('errors.404')

View file

@ -1,5 +1,5 @@
- content_for :page_title do - content_for :page_title do
The page you were looking for doesn't exist anymore = t('errors.410')
- content_for :content do - content_for :content do
The page you were looking for doesn't exist anymore = t('errors.410')

View file

@ -1,5 +1,5 @@
- content_for :page_title do - content_for :page_title do
Security verification failed = t('errors.422.title')
- content_for :content do - content_for :content do
Security verification failed. Are you blocking cookies? = t('errors.422.content')

View file

@ -15,3 +15,7 @@
%th= t('exports.blocks') %th= t('exports.blocks')
%td= @total_blocks %td= @total_blocks
%td= table_link_to 'download', t('exports.csv'), settings_exports_blocks_path(format: :csv) %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)

View file

@ -1,18 +1,19 @@
.detailed-status.light .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
%div.avatar %div.avatar
= image_tag status.account.avatar.url(:original), width: 48, height: 48, alt: '', class: 'u-photo' = image_tag status.account.avatar.url(:original), width: 48, height: 48, alt: '', class: 'u-photo'
%span.display-name %span.display-name
%strong.p-name.emojify= display_name(status.account) %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< .status__content.p-name.emojify<
- unless status.spoiler_text.blank? - if status.spoiler_text?
%p{ style: 'margin-bottom: 0' }< %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') %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? - unless status.media_attachments.empty?
- if status.media_attachments.first.video? - if status.media_attachments.first.video?
@ -30,7 +31,7 @@
%div.detailed-status__meta %div.detailed-status__meta
%data.dt-published{ value: status.created_at.to_time.iso8601 } %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) %span= l(status.created_at)
· ·
- if status.application - if status.application
@ -49,4 +50,4 @@
- if user_signed_in? - 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'

View file

@ -1,23 +1,23 @@
.status.light .status.light
.status__header .status__header
.status__meta .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 } %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 .status__avatar
%div %div
= image_tag status.account.avatar(:original), width: 48, height: 48, alt: '', class: 'u-photo' = image_tag status.account.avatar(:original), width: 48, height: 48, alt: '', class: 'u-photo'
%span.display-name %span.display-name
%strong.p-name.emojify= display_name(status.account) %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< .status__content.p-name.emojify<
- unless status.spoiler_text.blank? - if status.spoiler_text?
%p{ style: 'margin-bottom: 0' }< %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') %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? - unless status.media_attachments.empty?
.status__attachments .status__attachments

View file

@ -1,12 +1,22 @@
- include_threads ||= false - include_threads ||= false
- is_predecessor ||= false - is_predecessor ||= false
- is_successor ||= 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 - 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 - 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? - if status.reblog?
.pre-header .pre-header
%div.pre-header__icon %div.pre-header__icon
@ -19,4 +29,4 @@
= render partial: centered ? 'stream_entries/detailed_status' : 'stream_entries/simple_status', locals: { status: status.proper } = render partial: centered ? 'stream_entries/detailed_status' : 'stream_entries/simple_status', locals: { status: status.proper }
- if include_threads - 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}

View file

@ -23,5 +23,5 @@
- if !user_signed_in? && !Rails.configuration.x.single_user_mode - if !user_signed_in? && !Rails.configuration.x.single_user_mode
= render partial: 'shared/landing_strip', locals: { account: @stream_entry.account } = 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 } = render partial: "stream_entries/#{@type}", locals: { @type.to_sym => @stream_entry.activity, include_threads: true }

View file

@ -1,5 +1,12 @@
<p>Welcome <%= @resource.email %>!</p> <p>Welcome <%= @resource.email %> !</p>
<p>You can confirm your Mastodon account email through the link below:</p> <p>You just created an account on <%= @instance %>.</p>
<p><%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %></p> <p>To confirm your inscription, please click on the following link : <br>
<%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %>
<p>Please also check out our <%= link_to 'terms and conditions', terms_url %>.</p>
<p>Sincerely,<p>
<p>The <%= @instance %> team</p>

View file

@ -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) %> <%= confirmation_url(@resource, confirmation_token: @token) %>
Please also check out our terms and conditions <%= terms_url %>
Sincerely,
The <%= @instance %> team

View file

@ -1,5 +1,14 @@
<p>Bienvenue <%= @resource.email %>&nbsp;!</p> <p>Bonjour <%= @resource.email %>&nbsp;!<p>
<p>Vous pouvez confirmer le courriel de votre compte Mastodon en cliquant sur le lien ci-dessous&nbsp;:</p> <p>Vous venez de vous créer un compte sur <%= @instance %> et nous vous en remercions :)</p>
<p><%= link_to 'Confirmer mon compte', confirmation_url(@resource, confirmation_token: @token) %></p> <p>Pour confirmer votre inscription, merci de cliquer sur le lien suivant : <br>
<%= link_to 'Confirmer mon compte', confirmation_url(@resource, confirmation_token: @token) %></p>
<p>Après votre première connexion, vous pourrez accéder à la documentation de l'outil.</p>
<p>Pensez également à jeter un œil à nos <%= link_to 'conditions d\'utilisation', terms_url %>.</p>
<p>Amicalement,</p>
<p>L'équipe <%= @instance %></p>

View file

@ -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) %> <%= 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 %>

View file

@ -16,6 +16,8 @@ class ImportWorker
process_blocks process_blocks
when 'following' when 'following'
process_follows process_follows
when 'muting'
process_mutes
end end
@import.destroy @import.destroy
@ -35,6 +37,18 @@ class ImportWorker
CSV.new(import_contents).reject(&:blank?) CSV.new(import_contents).reject(&:blank?)
end 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 def process_blocks
import_rows.each do |row| import_rows.each do |row|
begin begin

View file

@ -19,7 +19,7 @@ class Pubsubhubbub::DeliveryWorker
headers['User-Agent'] = 'Mastodon/PubSubHubbub' 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['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) response = HTTP.timeout(:per_operation, write: 50, connect: 20, read: 50)
.headers(headers) .headers(headers)

View file

@ -25,7 +25,22 @@ module Mastodon
# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. # 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.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 config.i18n.default_locale = :en

View file

@ -2,5 +2,5 @@
Rails.application.configure do Rails.application.configure do
config.x.email_domains_blacklist = ENV.fetch('EMAIL_DOMAIN_BLACKLIST') { 'mvrht.com' } 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 end

View file

@ -17,7 +17,7 @@ en:
unconfirmed: You have to confirm your email address before continuing. unconfirmed: You have to confirm your email address before continuing.
mailer: mailer:
confirmation_instructions: confirmation_instructions:
subject: 'Mastodon: Confirmation instructions' subject: 'Mastodon: Confirmation instructions for %{instance}'
password_change: password_change:
subject: 'Mastodon: Password changed' subject: 'Mastodon: Password changed'
reset_password_instructions: reset_password_instructions:

View file

@ -17,13 +17,13 @@ fr:
unconfirmed: Vous devez valider votre compte pour continuer. unconfirmed: Vous devez valider votre compte pour continuer.
mailer: mailer:
confirmation_instructions: confirmation_instructions:
subject: Instructions de confirmation subject: "Merci de confirmer votre inscription sur %{instance}"
password_change: password_change:
subject: Votre mot de passe a été modifié avec succés. subject: Votre mot de passe a été modifié avec succés.
reset_password_instructions: reset_password_instructions:
subject: Instructions pour modifier le mot de passe subject: Instructions pour changer votre mot de passe
unlock_instructions: unlock_instructions:
subject: Instructions pour déverrouiller le compte subject: Instructions pour déverrouiller votre compte
omniauth_callbacks: omniauth_callbacks:
failure: 'Nous n''avons pas pu vous authentifier via %{kind} : ''%{reason}''.' failure: 'Nous n''avons pas pu vous authentifier via %{kind} : ''%{reason}''.'
success: Authentifié avec succès via %{kind}. success: Authentifié avec succès via %{kind}.

View file

@ -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} 被法被儲存︰"

View file

@ -60,15 +60,15 @@ ja:
title: 認証コード title: 認証コード
authorized_applications: authorized_applications:
buttons: buttons:
revoke: revoke:
confirmations: confirmations:
revoke: 本当に取り消しますか? revoke: 本当に取り消しますか?
index: index:
application: アプリケーション application: アプリ
created_at: 認証済み created_at: 許可した日時
date_format: "%Y年%m月%d日 %H時%M分%S秒" date_format: "%Y年%m月%d日 %H時%M分%S秒"
scopes: アクセス権 scopes: アクセス権
title: あなたの認証済みアプリケーション title: 認証済みアプリケーション
errors: errors:
messages: messages:
access_denied: リソースの所有者または認証サーバーが要求を拒否しました。 access_denied: リソースの所有者または認証サーバーが要求を拒否しました。
@ -83,7 +83,7 @@ ja:
expired: アクセストークンの有効期限が切れています expired: アクセストークンの有効期限が切れています
revoked: アクセストークンは取り消されています。 revoked: アクセストークンは取り消されています。
unknown: アクセストークンが無効です。 unknown: アクセストークンが無効です。
resource_owner_authenticator_not_configured: Doorkeeper.configure.resource_owner_authenticatorが設定されていないため、リソース所有者の検索に失敗しました。 resource_owner_authenticator_not_configured: Doorkeeper.configure.resource_owner_authenticator が設定されていないため、リソース所有者の検索に失敗しました。
server_error: 認証サーバーに予期せぬ例外が発生したため、リクエストを実行できなくなりました。 server_error: 認証サーバーに予期せぬ例外が発生したため、リクエストを実行できなくなりました。
temporarily_unavailable: 現在、認証サーバーに一時的な過負荷が掛かっているか、またはメンテナンス中のため、リクエストを処理できません。 temporarily_unavailable: 現在、認証サーバーに一時的な過負荷が掛かっているか、またはメンテナンス中のため、リクエストを処理できません。
unauthorized_client: クライアントはこのメゾットで要求を実行する権限がありません。 unauthorized_client: クライアントはこのメゾットで要求を実行する権限がありません。

View file

@ -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: 以你的名義發佈文章

View file

@ -76,6 +76,7 @@ en:
x_seconds: "%{count}s" x_seconds: "%{count}s"
exports: exports:
blocks: You block blocks: You block
mutes: You mute
csv: CSV csv: CSV
follows: You follow follows: You follow
storage: Media storage storage: Media storage
@ -92,6 +93,7 @@ en:
types: types:
blocking: Blocking list blocking: Blocking list
following: Following list following: Following list
muting: Muting list
upload: Upload upload: Upload
landing_strip_html: <strong>%{name}</strong> is a user on <strong>%{domain}</strong>. You can follow them or interact with them if you have an account anywhere in the fediverse. If you don't, you can <a href="%{sign_up_path}">sign up here</a>. landing_strip_html: <strong>%{name}</strong> is a user on <strong>%{domain}</strong>. You can follow them or interact with them if you have an account anywhere in the fediverse. If you don't, you can <a href="%{sign_up_path}">sign up here</a>.
media_attachments: media_attachments:
@ -170,3 +172,54 @@ en:
users: users:
invalid_email: The e-mail address is invalid invalid_email: The e-mail address is invalid
invalid_otp_token: Invalid two-factor code invalid_otp_token: Invalid two-factor code
will_paginate:
page_gap: "&hellip;"
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.<br>You can use HTML tags, in particular <code>&lt;a&gt;</code> and <code>&lt;em&gt;</code>."
site_description_extended:
title: Extended site description
desc_html: "Displayed on extended information page<br>You can use HTML tags"
registrations:
open:
title: Open registration
enabled: Enabled
disabled: Disabled
closed_message:
title: Closed registration message
desc_html: "Displayed on frontpage when registrations are closed<br>You can use HTML tags"

View file

@ -2,52 +2,166 @@
es: es:
about: about:
about_mastodon: Mastodon es un servidor de red social <em>libre y de código abierto</em>. Una alternativa <em>descentralizada</em> a plataformas comerciales, que evita el riesgo de que una única compañía monopolice tu comunicación. Cualquiera puede ejecutar Mastodon y participar sin problemas en la <em>red social</em>. about_mastodon: Mastodon es un servidor de red social <em>libre y de código abierto</em>. Una alternativa <em>descentralizada</em> a plataformas comerciales, que evita el riesgo de que una única compañía monopolice tu comunicación. Cualquiera puede ejecutar Mastodon y participar sin problemas en la <em>red social</em>.
about_this: Acerca de esta instancia
apps: Apps
business_email: 'Correo de negocios:'
closed_registrations: Los registros están actualmente cerrados en esta instancia.
contact: Contacto
description_headline: ¿Qué es %{domain}?
domain_count_after: otras instancias
domain_count_before: Conectado a
features:
api: API pública para aplicaciones y servicios
blocks: Moderación de contenido
characters: 500 caracteres por publicación
chronology: Las historias son cronológicas
ethics: 'Diseño etico: sin anuncios, sin rastreos'
gifv: Videos cortos y GIFV
privacy: Configuraciones de privacidad ajustables
public: Historia federada
features_headline: Lo que distingue a Mastodon
get_started: Comenzar get_started: Comenzar
links: Enlaces
other_instances: Otras instancias
source_code: Código fuente source_code: Código fuente
terms: Términos de uso status_count_after: estados
status_count_before: Que han escrito
terms: Términos
user_count_after: usuarios registrados
user_count_before: Tenemos
accounts: accounts:
follow: Seguir follow: Seguir
followers: Seguidores followers: Seguidores
following: Siguiendo following: Siguiendo
nothing_here: "¡No hay nada aquí!" nothing_here: ¡No hay nada aquí!
people_followed_by: Usuarios a quien %{name} sigue people_followed_by: Usuarios a quien %{name} sigue
people_who_follow: Usuarios que siguen a %{name} people_who_follow: Usuarios que siguen a %{name}
posts: Publicaciones posts: Toots
remote_follow: Seguir
unfollow: Dejar de seguir unfollow: Dejar de seguir
application_mailer: application_mailer:
settings: 'Cambiar preferencias de correo: %{link}'
signature: Notificaciones de Mastodon desde %{instance} signature: Notificaciones de Mastodon desde %{instance}
view: 'Vista:'
applications:
invalid_url: La URL proporcionada es incorrecta
auth: auth:
change_password: Cambiar contraseña change_password: Cambiar contraseña
didnt_get_confirmation: "¿No recibió instrucciones de confirmación?" didnt_get_confirmation: ¿No recibió el correo de confirmación?
forgot_password: "¿Olvidó su contraseña?" forgot_password: ¿Olvidaste tu contraseña?
login: Iniciar sesión login: Iniciar sesión
logout: Cerrar sesión
register: Registrarse register: Registrarse
resend_confirmation: Volver a enviar las instrucciones de confirmación resend_confirmation: Volver a enviar el correo de confirmación
reset_password: Restablecer contraseña reset_password: Restablecer contraseña
set_new_password: Establecer nueva contraseña set_new_password: Establecer nueva contraseña
authorize_follow:
error: Desafortunadamente, ha ocurrido un error buscando la cuenta remota
follow: Seguir
prompt_html: 'Tú (<strong>%{self}</strong>) has solicitado seguir:'
title: Seguir %{acct}
datetime:
distance_in_words:
about_x_hours: "%{count}h"
about_x_months: "%{count}m"
about_x_years: "%{count}y"
almost_x_years: "%{count}y"
half_a_minute: Justo ahora
less_than_x_minutes: "%{count}m"
less_than_x_seconds: Justo ahora
over_x_years: "%{count}y"
x_days: "%{count}d"
x_minutes: "%{count}m"
x_months: "%{count}m"
x_seconds: "%{count}s"
exports:
blocks: Personas que has bloqueado
csv: CSV
follows: Personas que sigues
storage: Almacenamiento
generic: generic:
changes_saved_msg: "¡Cambios guardados con éxito!" changes_saved_msg: ¡Cambios guardados con éxito!
powered_by: powered by %{link} powered_by: powered by %{link}
save_changes: Guardar cambios save_changes: Guardar cambios
validation_errors: validation_errors:
one: "¡Algo no está todavía bien! Por favor, revise el error más abajo" one: ¡Algo no está bien! Por favor, revisa el error
other: "¡Algo no está todavía bien! Por favor, revise %{count} errores más abajo" other: ¡Algo no está bien! Por favor, revise %{count} errores más abajo
imports:
preface: Puedes importar ciertos datos, como todas las personas que estás siguiendo o bloqueando en tu cuenta en esta instancia, desde archivos exportados de otra instancia.
success: Sus datos se han cargado correctamente y serán procesados en brevedad
types:
blocking: Lista de bloqueados
following: Lista de seguidos
upload: Cargar
landing_strip_html: <strong>%{name}</strong> es un usuario en <strong>%{domain}</strong>. Puedes seguirlo(a) o interactuar con el o ella si tienes una cuenta en cualquier parte del fediverse. Si no tienes una, puedes <a href="%{sign_up_path}">registrar aquí</a>.
notification_mailer: notification_mailer:
digest:
body: 'Un resumen de lo que te perdiste en %{instance} desde tu última visita el %{since}:'
mention: "%{name} te ha mencionado en:"
new_followers_summary:
one: ¡Hurra!. Alguien más te ha comenzado a seguir
other: ¡Genial!. Te han seguido %{count} nuevas personas
subject:
one: "1 nueva notificación desde tu última visita \U0001F418"
other: "%{count} nuevas notificaciones desde tu última visita \U0001F418"
favourite: favourite:
body: 'Su estado fue marcado como favorito por %{name}:' body: 'Tu estado fue marcado como favorito por %{name}:'
subject: "%{name} marcó como favorito su estado" subject: "%{name} marcó como favorito tu estado"
follow: follow:
body: "¡%{name} le está ahora siguiendo!" body: "¡%{name} te está siguiendo!"
subject: "%{name} le esta ahora siguiendo" subject: "%{name} te está siguiendo"
follow_request:
body: "%{name} ha solicitado seguirte"
subject: 'Seguidor pendiente: %{name}'
mention: mention:
body: 'Fue mencionado por %{name} en:' body: 'Fuiste mencionado por %{name} en:'
subject: Fue mencionado por %{name} subject: Fuiste mencionado por %{name}
reblog: reblog:
body: 'Su estado fue vuelto a publicar por %{name}:' body: '%{name} ha retooteado tu estado'
subject: "%{name} volvió a publicar su estado" subject: "%{name} ha retooteado tu estado"
pagination: pagination:
next: Próximo next: Próximo
prev: Anterior prev: Anterior
remote_follow:
acct: Ingesa el usuario@dominio de la persona que quieres seguir
missing_resource: No se pudo encontrar la URL de redirección necesaria para su cuenta.
proceed: Proceder a seguir
prompt: 'Vas a seguir a:'
settings: settings:
authorized_apps: Aplicaciones autorizadas
back: Volver al inicio
edit_profile: Editar perfil edit_profile: Editar perfil
export: Exportar información
import: Importar
preferences: Preferencias preferences: Preferencias
settings: Ajustes
two_factor_auth: Aute/ción. de dos factores
statuses:
open_in_web: Abrir en web
over_character_limit: Límite de caracteres de %{max} superado
show_more: Mostrar más
visibilities:
private: Sólo mostrar a seguidores
public: Público
unlisted: Público, pero no mostrar en la historia federada
stream_entries:
click_to_show: Click para mostrar
reblogged: retooteado
sensitive_content: Contenido sensible
time:
formats:
default: "%b %d, %Y, %H:%M"
two_factor_auth:
description_html: Sí habilitas la <strong>autenticación de dos factores</strong>, se requerirá estar en posesión de su teléfono, lo que generará tokens para que usted pueda iniciar sesión.
disable: Deshabilitar
enable: Habilitar
instructions_html: "<strong>Escanea este código QR desde Google Authenticator o una aplicación similar en su teléfono</strong>. Desde ahora, esta aplicación va a generar tokens que tienes que ingresar cuando quieras iniciar sesión."
plaintext_secret_html: 'Código en texto plano: <samp>%{secret}</samp>'
warning: Sí no puedes configurar una aplicación de autenticación ahora, deberás deshabilitar la autenticación de dos factores o no podrás iniciar sesión.
users:
invalid_email: La dirección de correo es incorrecta
invalid_otp_token: Código de dos factores incorrecto
media_attachments:
validations:
too_many: No se pueden adjuntar más de 4 archivos
images_and_video: No se puede adjuntar un video a un estado que ya contenga imágenes

View file

@ -40,7 +40,7 @@ fr:
remote_follow: Suivre à distance remote_follow: Suivre à distance
unfollow: Ne plus suivre unfollow: Ne plus suivre
application_mailer: application_mailer:
settings: 'Changer les préférences courriel : ${link}' settings: 'Changer les préférences courriel : %{link}'
signature: Notifications de Mastodon depuis %{instance} signature: Notifications de Mastodon depuis %{instance}
view: 'Voir :' view: 'Voir :'
applications: applications:
@ -167,3 +167,54 @@ fr:
users: users:
invalid_email: L'adresse courriel est invalide invalid_email: L'adresse courriel est invalide
invalid_otp_token: Le code d'authentification à deux facteurs est invalide invalid_otp_token: Le code d'authentification à deux facteurs est invalide
will_paginate:
page_gap: "&hellip;"
errors:
404: La page que vous recherchez n'existe pas.
410: La page que vous recherchez n'existe plus.
422:
title: Vérification de sécurité échouée
content: Vérification de sécurité échouée. Bloquez-vous les cookies ?
reports:
reports: Signalements
status: Statut
unresolved: Non résolus
resolved: Résolus
id: ID
target: Cible
reported_by: Signalé par
comment:
label: Commentaire
none: Aucun
view: Voir
report: "Signalement #%{id}"
delete: Supprimer
reported_account: Compte signalé
reported_by: Signalé par
silence_account: Rendre le compte muet
suspend_account: Suspendre le compte
mark_as_resolved: Marqué comme résolu
admin:
settings:
title: Paramètres du site
setting: Paramètre
click_to_edit: Cliquez pour éditer
contact_information:
label: Informations de contact
username: Entrez un nom d'utilisateur
email: Entrez une adresse courriel publique
site_title: Titre du site
site_description:
title: Description du site
desc_html: "Affichée sous la forme d'un paragraphe sur la page d'accueil et utilisée comme balise meta.<br>Vous pouvez utiliser des balises HTML, en particulier <code>&lt;a&gt;</code> et <code>&lt;em&gt;</code>."
site_description_extended:
title: Description étendue du site
desc_html: "Affichée sur la page d'informations complémentaires du site<br>Vous pouvez utiliser des balises HTML"
registrations:
open:
title: Inscriptions
enabled: Activées
disabled: Désactivées
closed_message:
title: Message de fermeture des inscriptions
desc_html: "Affiché sur la page d'accueil lorsque les inscriptions sont fermées<br>Vous pouvez utiliser des balises HTML"

View file

@ -1,32 +1,32 @@
--- ---
ja: ja:
about: about:
about_mastodon: Mastodon は<em>無料でオープンソース</em>のソーシャルネットワークです。 従来のプラットフォームとは違う<em>分散型</em>で、これはあなたの会話が一つの会社によって独占されるのを防ぎます。自分の信頼できるサーバーを選びます&mdash; どのサーバーを選んでも、誰とでも会話することができます。 だれでも自分の Mastodon サーバーを作ることができ、<em>シームレスにソーシャルネットワークに参加</em>できます。 about_mastodon: Mastodon は<em>自由でオープンソース</em>なソーシャルネットワークです。商用プラットフォームの代替となる<em>分散型</em>を採用し、あなたのやりとりが一つの会社によって独占されるのを防ぎます。信頼できるインスタンスを選択してください &mdash; どのインスタンスを選んでも、誰とでもやりとりすることができます。 だれでも自分の Mastodon インスタンスを作ることができ、シームレスに<em>ソーシャルネットワーク</em>に参加できます。
about_this: このサーバーについて about_this: このインスタンスについて
apps: アプリ apps: アプリ
business_email: 'ビジネスメールアドレス:' business_email: 'ビジネスメールアドレス:'
closed_registrations: 現在このサーバーでの新規登録は受け付けていません。 closed_registrations: 現在このインスタンスでの新規登録は受け付けていません。
contact: 連絡先 contact: 連絡先
description_headline: '%{domain}とは?' description_headline: '%{domain} とは?'
domain_count_after: 個のサーバー domain_count_after: 個のインスタンス
domain_count_before: 接続中 domain_count_before: 接続中
features: features:
api: アプリやその他サービスにAPIを公開 api: アプリやその他サービスにAPIを公開
blocks: ブロックやミュートの種類は豊富 blocks: 豊富なブロックやミュート機能
characters: 1投稿は500文字まで可能 characters: 1つの投稿は500文字まで利用可能
chronology: 時系列に沿ったタイムライン chronology: 時系列順のタイムライン
ethics: 広告も行動追跡もなく、プライバシーにも配慮 ethics: 広告もトラッキングもありません
gifv: GIFVや短い動画にも対応 gifv: GIFVや短い動画にも対応
privacy: 細かく投稿ごとに公開範囲が設定可能 privacy: 投稿ごとに公開範囲を細かく設定可能
public: 公開タイムライン public: 公開タイムライン
features_headline: Mastodonの特徴 features_headline: Mastodon の特徴
get_started: 始め get_started: 参加す
links: リンク links: リンク
other_instances: 他のサーバー other_instances: 他のインスタンス
source_code: ソースコード source_code: ソースコード
status_count_after: トゥート status_count_after: トゥート
status_count_before: トゥート数 status_count_before: トゥート数
terms: 規約 terms: プライバシーポリシー
user_count_after: user_count_after:
user_count_before: ユーザー数 user_count_before: ユーザー数
accounts: accounts:
@ -34,14 +34,14 @@ ja:
followers: フォロワー followers: フォロワー
following: フォロー中 following: フォロー中
nothing_here: 何もありません nothing_here: 何もありません
people_followed_by: '%{name}さんをフォロー中のアカウント' people_followed_by: '%{name} さんをフォロー中のアカウント'
people_who_follow: '%{name}さんがフォロー中のアカウント' people_who_follow: '%{name} さんがフォロー中のアカウント'
posts: 投稿 posts: 投稿
remote_follow: リモートフォロー remote_follow: リモートフォロー
unfollow: フォロー解除 unfollow: フォロー解除
application_mailer: application_mailer:
settings: 'メール設定の変更: %{link}' settings: 'メール設定の変更: %{link}'
signature: 'Mastodon %{instance}サーバーからの通知' signature: 'Mastodon %{instance} インスタンスからの通知'
view: 'View:' view: 'View:'
applications: applications:
invalid_url: URLが無効です invalid_url: URLが無効です
@ -51,15 +51,15 @@ ja:
forgot_password: パスワードをお忘れですか? forgot_password: パスワードをお忘れですか?
login: ログイン login: ログイン
logout: ログアウト logout: ログアウト
register: サインアップ register: 登録する
resend_confirmation: 確認メールを再送する resend_confirmation: 確認メールを再送する
reset_password: パスワード再発行 reset_password: パスワード再発行
set_new_password: 新しいパスワード set_new_password: 新しいパスワード
authorize_follow: authorize_follow:
error: 残念ながら、リモートアカウントにエラーが発生しました。 error: 残念ながら、リモートアカウントにエラーが発生しました。
follow: フォロー follow: フォロー
prompt_html: 'あなた (<strong>%{self}</strong>) は以下のアカウントのフォローをリクエストしました:' prompt_html: 'あなた<strong>%{self}</strong>は以下のアカウントのフォローをリクエストしました:'
title: '%{acct}をフォロー' title: '%{acct} をフォロー'
datetime: datetime:
distance_in_words: distance_in_words:
about_x_hours: "%{count}時間" about_x_hours: "%{count}時間"
@ -87,42 +87,42 @@ ja:
one: エラーが発生しました。以下のエラーを確認してください one: エラーが発生しました。以下のエラーを確認してください
other: エラーが発生しました。以下の%{count}個のエラーを確認してください other: エラーが発生しました。以下の%{count}個のエラーを確認してください
imports: imports:
preface: このサーバーのあなたのアカウントにフォロー、ブロック、などの他のサーバーからエクスポートされたファイルの情報をインポートできます。 preface: 他のインスタンスでエクスポートされたファイルから、フォロー/ブロックした情報をこのインスタンス上のアカウントにインポートできます。
success: ファイルは正常にアップロードされ、現在処理中です。しばらくしてから確認してください success: ファイルは正常にアップロードされ、現在処理中です。しばらくしてから確認してください
types: types:
blocking: ブロック中のアカウントリスト blocking: ブロック中のアカウントリスト
following: フォロー中のアカウントリスト following: フォロー中のアカウントリスト
upload: アップロード upload: アップロード
landing_strip_html: <strong>%{name}</strong>さんはサーバー<strong>%{domain}</strong>のユーザーです。アカウントさえ持っていればフォローしたり会話したりできます。もしお持ちでないなら<a href="%{sign_up_path}">こちら</a>からサインアップできます。 landing_strip_html: <strong>%{name}</strong> さんはインスタンス <strong>%{domain}</strong> のユーザーです。アカウントさえ持っていればフォローしたり会話したりできます。もしお持ちでないなら <a href="%{sign_up_path}">こちら</a> からサインアップできます。
media_attachments: media_attachments:
validations: validations:
images_and_video: 既に画像が追加されている場合動画を追加することはできません。 images_and_video: 既に画像が追加されているため、動画を追加することはできません。
too_many: 追加できるファイルは4つまでです。 too_many: 追加できるファイルは4つまでです。
notification_mailer: notification_mailer:
digest: digest:
body: '%{instance}での最後のログインからの出来事:' body: '%{instance} での最後のログインからの出来事:'
mention: "%{name}さんがあなたに返信しました:" mention: "%{name} さんがあなたに返信しました:"
new_followers_summary: new_followers_summary:
one: 新たなフォロワーを獲得しました! one: 新たなフォロワーを獲得しました!
other: '%{count}人の新たなフォロワーを獲得しました!' other: '%{count} 人の新たなフォロワーを獲得しました!'
subject: subject:
one: "新しい1の通知 \U0001F418" one: "新しい1の通知 \U0001F418"
other: "新しい%{count}の通知 \U0001F418" other: "新しい%{count}の通知 \U0001F418"
favourite: favourite:
body: 'あなたのステータスが%{name}さんにいいねされました:' body: 'あなたのトゥートが %{name} さんにお気に入り登録されました:'
subject: "%{name}さんがあなたのステータスをいいねしました" subject: "%{name} さんがあなたのトゥートをお気に入りに登録しました"
follow: follow:
body: "%{name}さんにフォローされています" body: "%{name} さんにフォローされています"
subject: "%{name}さんにフォローされています" subject: "%{name} さんにフォローされています"
follow_request: follow_request:
body: "%{name}さんがあなたにフォローをリクエストしました。" body: "%{name} さんがあなたにフォローをリクエストしました。"
subject: '%{name}さんからのフォローリクエスト' subject: '%{name} さんからのフォローリクエスト'
mention: mention:
body: '%{name}さんから返信がありました:' body: '%{name} さんから返信がありました:'
subject: '%{name}さんに返信されました' subject: '%{name} さんに返信されました'
reblog: reblog:
body: 'あなたのステータスが%{name}さんにブーストされました:' body: 'あなたのトゥートが %{name} さんにブーストされました:'
subject: "あなたのステータスが%{name}さんにブーストされました" subject: "あなたのトゥートが %{name} さんにブーストされました"
pagination: pagination:
next: next:
prev: prev:
@ -134,7 +134,7 @@ ja:
settings: settings:
authorized_apps: 認証済みアプリ authorized_apps: 認証済みアプリ
back: 戻る back: 戻る
edit_profile: プロフィール編集 edit_profile: プロフィール編集
export: データのエクスポート export: データのエクスポート
import: データのインポート import: データのインポート
preferences: ユーザー設定 preferences: ユーザー設定
@ -142,14 +142,14 @@ ja:
two_factor_auth: 二段階認証 two_factor_auth: 二段階認証
statuses: statuses:
open_in_web: Webで開く open_in_web: Webで開く
over_character_limit: '%{max}文字までしか入力できません' over_character_limit: '上限は %{max}文字までです'
show_more: もっと見る show_more: もっと見る
visibilities: visibilities:
private: フォロワーだけに見せる private: Private - フォロワーだけに見せる
public: 公開 public: Public - 全体に公開する
unlisted: 公開されますが、公開タイムラインには載りません unlisted: Unlisted - トゥートは公開するが、公開タイムラインには表示しない
stream_entries: stream_entries:
click_to_show: 見るにはクリック click_to_show: クリックして表示
reblogged: ブーストされました reblogged: ブーストされました
sensitive_content: 不適切なコンテンツの可能性があります sensitive_content: 不適切なコンテンツの可能性があります
time: time:
@ -171,3 +171,31 @@ ja:
invalid_otp_token: 二段階認証コードが間違っています invalid_otp_token: 二段階認証コードが間違っています
will_paginate: will_paginate:
page_gap: "&hellip;" page_gap: "&hellip;"
errors:
404: お探しのページは見つかりませんでした。
410: お探しのページはもう存在しません。
422:
title: セキュリティ認証に失敗
content: セキュリティ認証に失敗しました。Cookieをブロックしていませんか
admin:
settings:
title: サイト設定
setting: 設定
click_to_edit: クリックして編集
contact_information:
label: 連絡先情報
username: ユーザー名を入力
email: 公開するメールアドレスを入力
site_title: サイトのタイトル
site_description:
title: サイトの説明文
desc_html: "トップページへの表示と meta タグに使用されます。<br>HTMLタグ、特に<code>&lt;a&gt;</code> and <code>&lt;em&gt;</code>が利用可能です。"
site_description_extended:
title: サイトの詳細な説明
desc_html: "インスタンスについてのページに表示されます。<br>HTMLタグが利用可能です。"
registrations:
open:
title: 新規登録を受け付ける
enabled: 有効
disabled: 無効

View file

@ -104,8 +104,8 @@
one: "1 ny hendelse siden ditt siste besøk \U0001F418" one: "1 ny hendelse siden ditt siste besøk \U0001F418"
other: "%{count} nye hendelser siden ditt siste besøk \U0001F418" other: "%{count} nye hendelser siden ditt siste besøk \U0001F418"
favourite: favourite:
body: 'Din status ble satt som favoritt av %{name}' body: 'Din status ble likt av %{name}'
subject: "%{name} satte din status som favoritt." subject: "%{name} likte din status."
follow: follow:
body: "%{name} følger deg!" body: "%{name} følger deg!"
subject: "%{name} følger deg" subject: "%{name} følger deg"

View file

@ -1,24 +1,40 @@
--- ---
es: es:
simple_form: simple_form:
hints:
defaults:
avatar: PNG, GIF o JPG. Máximo 2MB. Será escalado a 120x120px
display_name: Máximo 30 caracteres
header: PNG, GIF o JPG. Máximo 2MB. Será escalado a 700x335px
locked: Requiere que manualmente apruebes seguidores y las publicaciones serán mostradas solamente a tus seguidores
note: Máximo 160 caracteres
imports:
data: Archivo CSV exportado desde otra instancia de Mastodon
labels: labels:
defaults: defaults:
avatar: Avatar avatar: Avatar
confirm_new_password: Confirmar nueva contraseña confirm_new_password: Confirmar nueva contraseña
confirm_password: Confirmar contraseña confirm_password: Confirmar contraseña
current_password: Contraseña actual current_password: Contraseña actual
data: Información
display_name: Mostrar nombre display_name: Mostrar nombre
email: Dirección de correo electrónico email: Dirección de correo electrónico
header: Img. cabecera header: Img. cabecera
locale: Idioma locale: Idioma
new_password: Nueva contraseña new_password: Nueva contraseña
note: Biografía note: Biografía
otp_attempt: Código de dos factores
password: Contraseña password: Contraseña
setting_default_privacy: Privacidad de publicaciones
type: Importar tipo
username: Nombre de usuario username: Nombre de usuario
interactions:
must_be_follower: Bloquear notificaciones de personas que no te siguen
must_be_following: Bloquear notificaciones de personas que no sigues
notification_emails: notification_emails:
favourite: Enviar correo electrónico cuando alguien de a favorito en su publicación favourite: Enviar correo electrónico cuando alguien de a favorito en su publicación
follow: Enviar correo electrónico cuando alguien le siga follow: Enviar correo electrónico cuando alguien te siga
mention: Enviar correo electrónico cuando alguien le mencione mention: Enviar correo electrónico cuando alguien te mencione
reblog: Enviar correo electrónico cuando alguien comparta su publicación reblog: Enviar correo electrónico cuando alguien comparta su publicación
'no': 'No' 'no': 'No'
required: required:

View file

@ -3,17 +3,17 @@ ja:
simple_form: simple_form:
hints: hints:
defaults: defaults:
avatar: PNGやGIF、JPGは2MBまでです。120x120pxまで縮小されます。 avatar: 2MBまでのPNGやGIF、JPGが利用可能です。120x120pxまで縮小されます。
display_name: 名前は30文字まで設定することができます。 display_name: 名前は30文字まで設定することができます。
header: PNGやGIF、JPGは2MBまでです。 700x335pxまで縮小されます。 header: 2MBまでのPNGやGIF、JPGが利用可能です。 700x335pxまで縮小されます。
locked: フォロワーを手動で承認する必要があります。デフォルトでは投稿範囲はフォロワーまでです。 locked: フォロワーを手動で承認する必要があります。デフォルトではトゥートの公開範囲はフォロワーのみです。
note: プロフィールは30文字まで設定することができます。 note: プロフィールは160文字まで設定することができます。
imports: imports:
data: CSVファイルからデータをインポートしました。 data: 他の Mastodon サーバーからエクスポートしたCSVファイルを選択して下さい
labels: labels:
defaults: defaults:
avatar: カウント avatar: イコン
confirm_new_password: 新しいパスワード(確認用) confirm_new_password: 新しいパスワード(確認用)
confirm_password: 新しいパスワード confirm_password: 新しいパスワード
current_password: 現在のパスワード current_password: 現在のパスワード
data: データ data: データ
@ -26,19 +26,20 @@ ja:
note: プロフィール note: プロフィール
otp_attempt: 二段階認証コード otp_attempt: 二段階認証コード
password: パスワード password: パスワード
setting_default_privacy: 投稿範囲 setting_default_privacy: 投稿の公開範囲
type: インポートするファイルの種類 type: インポートする項目
username: ユーザー名 username: ユーザー名
setting_boost_modal: ブーストする前に確認ダイアログを表示する
interactions: interactions:
must_be_follower: フォロワー以外からの通知をブロック must_be_follower: フォロワー以外からの通知をブロック
must_be_following: フォローしていないユーザーからの通知をブロック must_be_following: フォローしていないユーザーからの通知をブロック
notification_emails: notification_emails:
digest: タイムラインからピックアップしてメールで通知する digest: タイムラインからピックアップしてメールで通知する
favourite: いいねされた時メールで通知する favourite: お気に入りに登録された時にメールで通知する
follow: フォローされた時メールで通知する follow: フォローされた時メールで通知する
follow_request: フォローリクエストを受けた時メールで通知する follow_request: フォローリクエストを受けた時メールで通知する
mention: 返信された時メールで通知する mention: 返信が来た時にメールで通知する
reblog: あなたのトゥートがブーストされた時メールで通知する reblog: トゥートがブーストされた時メールで通知する
'no': 'いいえ' 'no': 'いいえ'
required: required:
mark: "*" mark: "*"

View file

@ -34,7 +34,7 @@
must_be_following: Blokker meldinger fra folk du ikke følger must_be_following: Blokker meldinger fra folk du ikke følger
notification_emails: notification_emails:
digest: Send oppsummerings eposter digest: Send oppsummerings eposter
favourite: Send e-post når noen setter din status som favoritt favourite: Send e-post når noen liker din status
follow: Send e-post når noen følger deg follow: Send e-post når noen følger deg
follow_request: Send e-post når noen spør om å få følge deg follow_request: Send e-post når noen spør om å få følge deg
mention: Send e-post når noen nevner deg mention: Send e-post når noen nevner deg

View file

@ -0,0 +1,46 @@
---
zh-HK:
simple_form:
hints:
defaults:
avatar: 支援 PNG, GIF 或 JPG 圖片,檔案大小上限為 2MB會被縮裁成 120x120px
display_name: 最多 30 個字元
header: 支援 PNG, GIF 或 JPG 圖片,檔案大小上限為 2MB會被縮裁成 700x335px
locked: 你必須人手核准每個用戶對你的關注請求,而你的文章私隱會被預設為「只有關注你的人能看」
note: 最多 160 個字元
imports:
data: 自其他服務站匯出的 CSV 檔案
labels:
defaults:
avatar: 個人頭像
confirm_new_password: 確認新密碼
confirm_password: 確認密碼
current_password: 目前密碼
data: 資料
display_name: 顯示名稱
email: 電郵地址
header: 個人頁面頂部
locale: 語言
locked: 將用戶轉為「私人」
new_password: 新密碼
note: 簡介
otp_attempt: 雙重認證碼
password: 密碼
setting_default_privacy: 文章預設私隱度
type: 匯入資料類型
username: 用戶名稱
interactions:
must_be_follower: 隱藏沒有關注你的用戶的通知
must_be_following: 隱藏你不關注的用戶的通知
notification_emails:
digest: 定期電郵摘要
favourite: 當有用戶喜歡你的文章時,發電郵通知
follow: 當有用戶關注你時,發電郵通知
follow_request: 當有用戶要求關注你時,發電郵通知
mention: 當有用戶在文章提及你時,發電郵通知
reblog: 當有用戶轉推你的文章時,發電郵通知
'no': '否'
required:
mark: "*"
text: 必須填寫
'yes': '是'

172
config/locales/zh-HK.yml Normal file
View file

@ -0,0 +1,172 @@
---
zh-HK:
about:
about_mastodon: Mastodon (長毛象)是一個<em>自由、開放源碼</em>的社交網站。它是一個分散式的服務,避免你的通訊被單一商業機構壟斷操控。請你選擇一家你信任的 Mastodon 服務站,在上面建立帳號,然後你就可以和任一 Mastodon 服務站上的用戶互通,享受無縫的<em>社交網絡</em>交流。
about_this: 關於本服務站
apps: 應用程式
business_email: 商業電郵︰
closed_registrations: 本服務站暫時停止接受登記。
contact: 聯絡
description_headline: 關於 %{domain}
domain_count_after: 個其他服務站
domain_count_before: 已連接至
features:
api: 開放 API供各式應用程式及服務連入
blocks: 完善的封鎖用戶、靜音功能
characters: 每篇文章最多 500 字
chronology: 時間軸忠實按時序顯示文章,不作多餘處理
ethics: 良心設計︰沒有廣告,不追蹤你的使用行為
gifv: 支援顯示 GIFV 短片圖組
privacy: 可逐篇文章設定私隱度
public: 公共時間軸
features_headline: 甚麼讓 Mastodon 與眾不同
get_started: 立即登記
links: 連結
other_instances: 其他服務站
source_code: 源代碼
status_count_after: 篇文章
status_count_before: 他們共發佈了
terms: 使用條款
user_count_after: 位使用者
user_count_before: 這裏共註冊有
accounts:
follow: 關注
followers: 關注者
following: 正在關注
nothing_here: 暫時未有內容可以顯示
people_followed_by: '%{name} 關注的人'
people_who_follow: 關注 %{name} 的人
posts: 文章
remote_follow: 跨站關注
unfollow: 取消關注
application_mailer:
settings: '修改電郵設定︰ %{link}'
signature: 來自 %{instance} 的 Mastodon 通知
view: '進入瀏覽︰'
applications:
invalid_url: 所提供的網址不正確
auth:
change_password: 登入資訊
didnt_get_confirmation: 沒有收到確認指示電郵?
forgot_password: 忘記了密碼?
login: 登入
logout: 登出
register: 登記
resend_confirmation: 重發確認指示電郵
reset_password: 重設密碼
set_new_password: 設定新密碼
authorize_follow:
error: 對不起,尋找這個跨站用戶的過程發生錯誤
follow: 關注
prompt_html: '你 (<strong>%{self}</strong>) 正準備關注︰'
title: 關注 %{acct}
datetime:
distance_in_words:
about_x_hours: "%{count}小時前"
about_x_months: "%{count}個月前"
about_x_years: "%{count}年前"
almost_x_years: "接近%{count}年前"
half_a_minute: 剛剛
less_than_x_minutes: "少於%{count}分鐘前"
less_than_x_seconds: 剛剛
over_x_years: "%{count}y"
x_days: "%{count}日"
x_minutes: "%{count}分鐘"
x_months: "%{count}個月"
x_seconds: "%{count}秒"
exports:
blocks: 被你封鎖的用戶
csv: CSV
follows: 你所關注的用戶
storage: 媒體容量大小
generic:
changes_saved_msg: 已成功儲存修改
powered_by: 網站由 %{link} 開發
save_changes: 儲存修改
validation_errors:
one: 提交的資料有問題
other: 提交的資料有 %{count} 項問題
imports:
preface: 你可以在此匯入你在其他服務站所匯出的資料檔,包括︰你所關注的用戶,被你封鎖的用戶。
success: 你已成功上載資料檔,我們正將資料匯入,請稍候
types:
blocking: 被你封鎖的用戶名單
following: 你所關注的用戶名單
upload: 上載
landing_strip_html: <strong>%{name}</strong> 是一個在 <strong>%{domain}</strong> 的用戶。只要你有任何 Mastodon 服務站、或者聯盟網站的用戶,便可以跨站關注此站用戶,或者與他們互動。如果你沒有這類用戶,歡迎在<a href="%{sign_up_path}">此處登記</a>。
media_attachments:
validations:
images_and_video: 不能在已有圖片的文章上加入影片
too_many: 不可以加入超過 4 個檔案
notification_mailer:
digest:
body: '這是自從你在%{since}使用%{instance}以後,你錯失了的訊息︰'
mention: "%{name} 在此提及了你︰"
new_followers_summary:
one: 你新獲得了 1 位關注者了!恭喜!
other: 你新獲得了 %{count} 位關注者了!好厲害!
subject:
one: "自從上次登入以來,你收到 1 則新的通知 \U0001F418"
other: "自從上次登入以來,你收到 %{count} 則新的通知 \U0001F418"
favourite:
body: '你的文章獲得 %{name} 的喜愛'
subject: "%{name} 喜歡你的文章"
follow:
body: "%{name} 開始關注你!"
subject: "%{name} 現正關注你"
follow_request:
body: "%{name} 要求關注你"
subject: '等候關注你的用戶︰ %{name}'
mention:
body: '%{name} 在文章中提及你︰'
subject: '%{name} 在文章中提及你'
reblog:
body: '你的文章得到 %{name} 的轉推'
subject: "%{name} 轉推了你的文章"
pagination:
next: 下一頁
prev: 上一頁
truncate: "……"
remote_follow:
acct: 請輸入你的︰用戶名稱@服務點域名
missing_resource: 無法找到你用戶的轉接網址
proceed: 下一步
prompt: '你希望關注︰'
settings:
authorized_apps: 授權應用程式
back: 回到 Mastodon
edit_profile: 修改個人資料
export: 匯出
import: 匯入
preferences: 偏好設定
settings: 設定
two_factor_auth: 雙重認證
statuses:
open_in_web: 開啟網頁
over_character_limit: 超過了 %{max} 字的限制
show_more: 顯示更多
visibilities:
private: 只有關注你的人能看
public: 公開
unlisted: 公開,但不在公共時間軸顯示
stream_entries:
click_to_show: 點擊顯示
reblogged: 轉推
sensitive_content: 敏感內容
time:
formats:
default: "%Y年%-m月%d日 %H:%M"
two_factor_auth:
code_hint: 請輸入你認證器產生的代碼,以確認設定
description_html: 當你啟用<strong>雙重認證</strong>後,你登入時將需要使你手機、或其他種類認證器產生的代碼。
disable: 停用
enable: 啟用
enabled_success: 已成功啟用雙重認證
instructions_html: <strong>請用你手機的認證器應用程式(如 Google Authenticator、Authy掃描這裏的 QR 圖形碼</strong>。在雙重認證啟用後,你登入時將須要使用此應用程式產生的認證碼。
manual_instructions: 如果你無法掃描 QR 圖形碼,請手動輸入這個文字密碼︰
setup: 設定
warning: 如果你現在無法正確設定你的應用程式,請即「停用」雙重認證,否則日後可能無法登入本站。
wrong_code: 你輸入的認證碼並不正確!可能伺服器時間和你手機不一致,請檢查你手機的時鐘,或與本站管理員聯絡。
users:
invalid_email: 電郵地址格式不正確
invalid_otp_token: 雙重認證確認碼不正確

View file

@ -15,7 +15,7 @@ Rails.application.routes.draw do
end end
get '.well-known/host-meta', to: 'xrd#host_meta', as: :host_meta get '.well-known/host-meta', to: 'xrd#host_meta', as: :host_meta
get '.well-known/webfinger', to: 'xrd#webfinger', as: :webfinger get '.well-known/webfinger', to: 'xrd#webfinger', as: :webfinger, defaults: { format: 'json' }
devise_for :users, path: 'auth', controllers: { devise_for :users, path: 'auth', controllers: {
sessions: 'auth/sessions', sessions: 'auth/sessions',
@ -57,6 +57,7 @@ Rails.application.routes.draw do
namespace :exports, constraints: { format: :csv } do namespace :exports, constraints: { format: :csv } do
resources :follows, only: :index, controller: :following_accounts resources :follows, only: :index, controller: :following_accounts
resources :blocks, only: :index, controller: :blocked_accounts resources :blocks, only: :index, controller: :blocked_accounts
resources :mutes, only: :index, controller: :muted_accounts
end end
resource :two_factor_auth, only: [:show, :new, :create] do resource :two_factor_auth, only: [:show, :new, :create] do

View file

@ -1 +1 @@
[The documentation has moved to its own repository](https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Tuning-guide.md) [The documentation has moved to its own repository](https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Tuning.md)

View file

@ -13,11 +13,12 @@ RSpec.describe Api::V1::NotificationsController, type: :controller do
describe 'GET #index' do describe 'GET #index' do
before do before do
status = PostStatusService.new.call(user.account, 'Test') first_status = PostStatusService.new.call(user.account, 'Test')
@reblog = ReblogService.new.call(other.account, status) @reblog_of_first_status = ReblogService.new.call(other.account, first_status)
@mention = PostStatusService.new.call(other.account, 'Hello @alice') mentioning_status = PostStatusService.new.call(other.account, 'Hello @alice')
@favourite = FavouriteService.new.call(other.account, status) @mention_from_status = mentioning_status.mentions.first
@follow = FollowService.new.call(other.account, 'alice') @favourite = FavouriteService.new.call(other.account, first_status)
@follow = FollowService.new.call(other.account, 'alice')
end end
describe 'with no options' do describe 'with no options' do
@ -30,19 +31,19 @@ RSpec.describe Api::V1::NotificationsController, type: :controller do
end end
it 'includes reblog' do it 'includes reblog' do
expect(assigns(:notifications).map(&:activity_id)).to include(@reblog.id) expect(assigns(:notifications).map(&:activity)).to include(@reblog_of_first_status)
end end
it 'includes mention' do it 'includes mention' do
expect(assigns(:notifications).map(&:activity_id)).to include(@mention.mentions.first.id) expect(assigns(:notifications).map(&:activity)).to include(@mention_from_status)
end end
it 'includes favourite' do it 'includes favourite' do
expect(assigns(:notifications).map(&:activity_id)).to include(@favourite.id) expect(assigns(:notifications).map(&:activity)).to include(@favourite)
end end
it 'includes follow' do it 'includes follow' do
expect(assigns(:notifications).map(&:activity_id)).to include(@follow.id) expect(assigns(:notifications).map(&:activity)).to include(@follow)
end end
end end
@ -56,19 +57,19 @@ RSpec.describe Api::V1::NotificationsController, type: :controller do
end end
it 'includes reblog' do it 'includes reblog' do
expect(assigns(:notifications).map(&:activity_id)).to include(@reblog.id) expect(assigns(:notifications).map(&:activity)).to include(@reblog_of_first_status)
end end
it 'excludes mention' do it 'excludes mention' do
expect(assigns(:notifications).map(&:activity_id)).to_not include(@mention.mentions.first.id) expect(assigns(:notifications).map(&:activity)).to_not include(@mention_from_status)
end end
it 'includes favourite' do it 'includes favourite' do
expect(assigns(:notifications).map(&:activity_id)).to include(@favourite.id) expect(assigns(:notifications).map(&:activity)).to include(@favourite)
end end
it 'includes follow' do it 'includes follow' do
expect(assigns(:notifications).map(&:activity_id)).to include(@follow.id) expect(assigns(:notifications).map(&:activity)).to include(@follow)
end end
end end
end end

View file

@ -11,7 +11,7 @@ describe Settings::Exports::BlockedAccountsController do
expect(response).to have_http_status(:success) expect(response).to have_http_status(:success)
expect(response.content_type).to eq 'text/csv' expect(response.content_type).to eq 'text/csv'
expect(response.headers['Content-Disposition']).to eq 'attachment; filename="blocking.csv"' expect(response.headers['Content-Disposition']).to eq 'attachment; filename="blocked_accounts.csv"'
end end
end end
end end

View file

@ -11,7 +11,7 @@ describe Settings::Exports::FollowingAccountsController do
expect(response).to have_http_status(:success) expect(response).to have_http_status(:success)
expect(response.content_type).to eq 'text/csv' expect(response.content_type).to eq 'text/csv'
expect(response.headers['Content-Disposition']).to eq 'attachment; filename="following.csv"' expect(response.headers['Content-Disposition']).to eq 'attachment; filename="following_accounts.csv"'
end end
end end
end end

View file

@ -0,0 +1,17 @@
require 'rails_helper'
describe Settings::Exports::MutedAccountsController do
before do
sign_in Fabricate(:user), scope: :user
end
describe 'GET #index' do
it 'returns a csv of the muting accounts' do
get :index, format: :csv
expect(response).to have_http_status(:success)
expect(response.content_type).to eq 'text/csv'
expect(response.headers['Content-Disposition']).to eq 'attachment; filename="muted_accounts.csv"'
end
end
end

View file

@ -17,4 +17,14 @@ RSpec.describe StreamEntriesController, type: :controller do
expect(response).to have_http_status(:success) expect(response).to have_http_status(:success)
end end
end end
describe 'GET #embed' do
it 'returns embedded view of status' do
get :embed, params: { account_username: alice.username, id: status.stream_entry.id }
expect(response).to have_http_status(:success)
expect(response.headers['X-Frame-Options']).to eq 'ALLOWALL'
expect(response).to render_template(layout: 'embedded')
end
end
end end

View file

@ -14,12 +14,12 @@ RSpec.describe XrdController, type: :controller do
let(:alice) { Fabricate(:account, username: 'alice') } let(:alice) { Fabricate(:account, username: 'alice') }
it 'returns http success when account can be found' do it 'returns http success when account can be found' do
get :webfinger, params: { resource: alice.to_webfinger_s } get :webfinger, params: { resource: alice.to_webfinger_s }, format: :json
expect(response).to have_http_status(:success) expect(response).to have_http_status(:success)
end end
it 'returns http not found when account cannot be found' do it 'returns http not found when account cannot be found' do
get :webfinger, params: { resource: 'acct:not@existing.com' } get :webfinger, params: { resource: 'acct:not@existing.com' }, format: :json
expect(response).to have_http_status(:not_found) expect(response).to have_http_status(:not_found)
end end
end end

View file

@ -0,0 +1,30 @@
import { expect } from 'chai';
import { mount } from 'enzyme';
import sinon from 'sinon';
import Column from '../../../../../../app/assets/javascripts/components/features/ui/components/column';
import ColumnHeader from '../../../../../../app/assets/javascripts/components/features/ui/components/column_header';
describe('<Column />', () => {
describe('<ColumnHeader /> click handler', () => {
beforeEach(() => {
global.requestAnimationFrame = sinon.spy();
});
it('runs the scroll animation if the column contains scrollable content', () => {
const wrapper = mount(
<Column heading="notifications">
<div className="scrollable" />
</Column>
);
wrapper.find(ColumnHeader).simulate('click');
expect(global.requestAnimationFrame.called).to.equal(true);
});
it('does not try to scroll if there is no scrollable content', () => {
const wrapper = mount(<Column heading="notifications" />);
wrapper.find(ColumnHeader).simulate('click');
expect(global.requestAnimationFrame.called).to.equal(false);
});
});
});

View file

@ -0,0 +1,88 @@
require 'rails_helper'
describe WebfingerResource do
describe '#username' do
describe 'with a URL value' do
it 'raises with an unrecognized route' do
resource = 'https://example.com/users/alice/other'
expect {
WebfingerResource.new(resource).username
}.to raise_error(ActiveRecord::RecordNotFound)
end
it 'raises with a string that doesnt start with URL' do
resource = 'website for http://example.com/users/alice/other'
expect {
WebfingerResource.new(resource).username
}.to raise_error(ActiveRecord::RecordNotFound)
end
it 'finds the username in a valid https route' do
resource = 'https://example.com/users/alice'
result = WebfingerResource.new(resource).username
expect(result).to eq 'alice'
end
it 'finds the username in a mixed case http route' do
resource = 'HTTp://exAMPLEe.com/users/alice'
result = WebfingerResource.new(resource).username
expect(result).to eq 'alice'
end
it 'finds the username in a valid http route' do
resource = 'http://example.com/users/alice'
result = WebfingerResource.new(resource).username
expect(result).to eq 'alice'
end
end
describe 'with a username and hostname value' do
it 'raises on a non-local domain' do
resource = 'user@remote-host.com'
expect {
WebfingerResource.new(resource).username
}.to raise_error(ActiveRecord::RecordNotFound)
end
it 'finds username for a local domain' do
Rails.configuration.x.local_domain = 'example.com'
resource = 'alice@example.com'
result = WebfingerResource.new(resource).username
expect(result).to eq 'alice'
end
end
describe 'with an acct value' do
it 'raises on a non-local domain' do
resource = 'acct:user@remote-host.com'
expect {
WebfingerResource.new(resource).username
}.to raise_error(ActiveRecord::RecordNotFound)
end
it 'raises on a nonsense domain' do
resource = 'acct:user@remote-host@remote-hostess.remote.local@remote'
expect {
WebfingerResource.new(resource).username
}.to raise_error(ActiveRecord::RecordNotFound)
end
it 'finds the username for a local account' do
Rails.configuration.x.local_domain = 'example.com'
resource = 'acct:alice@example.com'
result = WebfingerResource.new(resource).username
expect(result).to eq 'alice'
end
end
end
end

View file

@ -0,0 +1,33 @@
require "rails_helper"
describe "The webfinger route" do
let(:alice) { Fabricate(:account, username: 'alice') }
describe "requested without accepts headers" do
it "returns a json response" do
get webfinger_url, params: { resource: alice.to_webfinger_s }
expect(response).to have_http_status(:success)
expect(response.content_type).to eq "application/jrd+json"
end
end
describe "requested with html in accepts headers" do
it "returns a json response" do
headers = { 'HTTP_ACCEPT' => 'text/html' }
get webfinger_url, params: { resource: alice.to_webfinger_s }, headers: headers
expect(response).to have_http_status(:success)
expect(response.content_type).to eq "application/jrd+json"
end
end
describe "requested with xml format" do
it "returns an xml response" do
get webfinger_url(resource: alice.to_webfinger_s, format: :xml)
expect(response).to have_http_status(:success)
expect(response.content_type).to eq "application/xrd+xml"
end
end
end