Compare commits

..

4 commits

Author SHA1 Message Date
Zhongqi Ma
fb3bde74cb
Merge f5000d46da into 5492e9eb6e 2023-07-14 14:04:36 +02:00
Baptiste Jonglez
f5000d46da Remove unused test 2023-02-03 20:50:24 +01:00
Zhongqi Ma
43807fb7f1 Update budget_test.py
Added 2 tests checking for validate_name() in MemberForm()
2023-02-03 20:49:40 +01:00
Zhongqi Ma
707ba186f3 Updated checks for validate_name() in MemberForm()
The database allows users to deactivate an account with a non-zero value, and create a new user with the same name, reactivating the previous user will allow two users of the same name. This change assures that new user names can not be the same as deactivated users with associated bills (Users that are not deleted from deactivation).
2023-02-03 20:49:29 +01:00
132 changed files with 4069 additions and 11281 deletions

View file

@ -14,7 +14,9 @@ CONTRIBUTORS
docker-compose.* docker-compose.*
Dockerfile Dockerfile
docs docs
LICENSE
Makefile Makefile
MANIFEST.in MANIFEST.in
README.md
SECURITY.md SECURITY.md
tox.ini tox.ini

View file

@ -1,122 +0,0 @@
name: CI
on:
push:
branches: [ 'main', 'stable-*' ]
pull_request:
branches: [ 'main', 'stable-*' ]
jobs:
lint:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- name: Install uv and set the python version
uses: astral-sh/setup-uv@v4
with:
python-version: "3.11"
- name: Run Lint
run: make lint
test:
# Dependency on linting to avoid running our expensive matrix test for nothing
needs: lint
runs-on: ubuntu-22.04
# Use postgresql and MariaDB versions of Debian bookworm
services:
postgres:
image: postgres:15
ports:
- 5432:5432
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: ihatemoney
POSTGRES_DB: ihatemoney_ci
options:
--health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
mariadb:
image: mariadb:10.11
env:
MARIADB_ROOT_PASSWORD: ihatemoney
MARIADB_DATABASE: ihatemoney_ci
options: >-
--health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
ports:
- 3306:3306
strategy:
fail-fast: false
matrix:
python-version: [3.9, "3.10", "3.11", "3.12"]
dependencies: [normal]
database: [sqlite]
# Test other databases with only a few versions of Python
# (Debian bullseye has 3.9, bookworm has 3.11)
include:
- python-version: 3.9
dependencies: normal
database: postgresql
- python-version: 3.9
dependencies: normal
database: mariadb
- python-version: 3.11
dependencies: normal
database: postgresql
- python-version: 3.11
dependencies: normal
database: mariadb
# Try a few variants with the minimal versions supported
- python-version: 3.9
dependencies: minimal
database: sqlite
- python-version: "3.10"
dependencies: minimal
database: sqlite
- python-version: "3.11"
dependencies: minimal
database: sqlite
- python-version: "3.11"
dependencies: minimal
database: postgresql
- python-version: "3.11"
dependencies: minimal
database: mariadb
- python-version: "3.12"
dependencies: minimal
database: sqlite
steps:
- uses: actions/checkout@v3
- name: Install uv and set the python version
uses: astral-sh/setup-uv@v4
with:
python-version: ${{ matrix.python-version }}
- name: Change dependencies to minimal supported versions
# This sed comment installs the minimal supported version
# for all versions except for "requires-python"
# This is to detect that the minimum versions are really
# supported, in the CI
run: sed -i -e '/requires-python/!s/>=/==/g; /requires-python/!s/~=.*==\(.*\)/==\1/g; /requires-python/!s/~=/==/g;' pyproject.toml
if: matrix.dependencies == 'minimal'
- name: Run tests
run: uv run --extra dev --extra database pytest .
env:
# Setup the DATABASE_URI depending on the matrix we are using.
TESTING_SQLALCHEMY_DATABASE_URI: ${{
matrix.database == 'sqlite'
&& 'sqlite:///budget.db'
|| matrix.database == 'postgresql'
&& 'postgresql+psycopg2://postgres:ihatemoney@localhost:5432/ihatemoney_ci'
|| 'mysql+pymysql://root:ihatemoney@localhost:3306/ihatemoney_ci'
}}
docs:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- name: Install uv and set the python version
uses: astral-sh/setup-uv@v4
with:
python-version: "3.11"
- name: Build docs
run: make build-docs

View file

@ -1,16 +1,16 @@
name: Docker build name: CI to Docker Hub
on: on:
push: push:
tags: ['*'] tags: ['*']
branches: [ 'main', 'stable-*' ] branches: [ master ]
pull_request: pull_request:
branches: [ 'main', 'stable-*' ] branches: [ master ]
jobs: jobs:
test: test:
runs-on: ubuntu-22.04 runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v2
@ -18,8 +18,8 @@ jobs:
- name: Test image # Checks we are able to run the container. - name: Test image # Checks we are able to run the container.
run: docker compose -f docker-compose.test.yml run sut run: docker compose -f docker-compose.test.yml run sut
build_upload: build:
runs-on: ubuntu-22.04 runs-on: ubuntu-latest
needs: test needs: test
if: github.event_name != 'pull_request' if: github.event_name != 'pull_request'
steps: steps:

103
.github/workflows/test-docs.yml vendored Normal file
View file

@ -0,0 +1,103 @@
name: Test & Docs
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
# Use postgresql and MariaDB versions of Debian buster
services:
postgres:
image: postgres:11
ports:
- 5432:5432
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: ihatemoney
POSTGRES_DB: ihatemoney_ci
options:
--health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
mariadb:
image: mariadb:10.3
env:
MARIADB_ROOT_PASSWORD: ihatemoney
MARIADB_DATABASE: ihatemoney_ci
options: >-
--health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
ports:
- 3306:3306
strategy:
fail-fast: false
matrix:
python-version: [3.7, 3.8, 3.9, "3.10", "3.11"]
dependencies: [normal]
database: [sqlite]
# Test other databases only with one version of Python (Debian buster has 3.7)
include:
- python-version: 3.7
dependencies: normal
database: postgresql
- python-version: 3.7
dependencies: normal
database: mariadb
# Try a few variants with the minimal versions supported
- python-version: 3.7
dependencies: minimal
database: sqlite
- python-version: 3.7
dependencies: minimal
database: postgresql
- python-version: 3.7
dependencies: minimal
database: mariadb
- python-version: 3.9
dependencies: minimal
database: sqlite
- python-version: "3.10"
dependencies: minimal
database: sqlite
- python-version: "3.11"
dependencies: minimal
database: sqlite
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
cache-dependency-path: '**/setup.cfg'
- name: Change dependencies to minimal supported versions
run: sed -i -e 's/>=/==/g; s/~=.*==\(.*\)/==\1/g; s/~=/==/g;' setup.cfg
if: matrix.dependencies == 'minimal'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install tox
# Run tox using the version of Python in `PATH`
- name: Run Tox with sqlite
run: tox -e py
if: matrix.database == 'sqlite'
env:
TESTING_SQLALCHEMY_DATABASE_URI: 'sqlite:///budget.db'
- name: Run Tox with postgresql
run: tox -e py
if: matrix.database == 'postgresql'
env:
TESTING_SQLALCHEMY_DATABASE_URI: 'postgresql+psycopg2://postgres:ihatemoney@localhost:5432/ihatemoney_ci'
- name: Run Tox with mariadb
run: tox -e py
if: matrix.database == 'mariadb'
env:
TESTING_SQLALCHEMY_DATABASE_URI: 'mysql+pymysql://root:ihatemoney@localhost:3306/ihatemoney_ci'
- name: Run Lint & Docs
run: tox -e lint_docs
if: matrix.python-version == '3.11'

4
.gitignore vendored
View file

@ -1,6 +1,5 @@
*.pyc *.pyc
*.egg-info *.egg-info
*.mo
dist dist
.venv .venv
docs/_build/ docs/_build/
@ -17,5 +16,4 @@ ihatemoney/budget.db
.DS_Store .DS_Store
.idea .idea
.python-version .python-version
.coverage*
prof

View file

@ -1,11 +1,7 @@
version: 2 version: 2
build:
os: ubuntu-22.04
tools:
python: "3.11"
python: python:
version: "3.7"
install: install:
- method: pip - method: pip
path: . path: .

11
.travis.yml Normal file
View file

@ -0,0 +1,11 @@
sudo: false
language: python
python:
- "3.7"
- "3.8"
- "3.9"
script: tox
before_install:
- python -m pip install --upgrade pip virtualenv
install:
- pip install tox-travis

View file

@ -2,93 +2,10 @@
This document describes changes between each past release. This document describes changes between each past release.
## 6.2.0 (unreleased) ## 6.0.1 (unreleased)
- Add support for python 3.12 (#757)
- Migrate from setup.cfg to pyproject.toml (#1243)
- Update to wtforms 3.1 (#1248)
- Document [repository rules](https://ihatemoney.readthedocs.io/en/latest/contributing.html#repository-rules) (#1253)
- Add "reimbursement" bills and allow to create them directly from the "Settle" page (#1290)
- Remove support for python 3.7
- Replace the black linter by ruff
- Replace virtualenv and pip by uv
- Remove tox
## 6.1.5 (2024-03-19)
- Fix README and changelog not being displayed on PyPI
- Fix ability to change project settings when project has existing currency (#1292)
- Update translations for Dutch and German
## 6.1.4 (2023-12-14)
- Fix missing markdown include in manifest (#1274)
- Update translations for Chinese, Turkish, Czech, Spanish (Latin America), Swedish
## 6.1.3 (2023-11-23)
- Revert update to flask and werkzeug 2.3 because of a regression (see #1272)
## 6.1.2 (2023-11-19)
- Fix password generation command line crash (#1242)
- Update to flask and werkzeug 2.3 (#1244)
## 6.1.1 (2023-10-04)
### Currency conversion API workarounds
We are using an external API for currency conversion. This API recently
started requiring an API key, and this broke I Hate Money in many ways.
This release adds a set of workarounds for this issue. This should restore
basic functionality such as adding bills. However, we had to disable
some operations to prevent crashing:
- Setting or changing the default currency on an existing project is no longer possible.
However, setting a project to "No currency" is still possible.
- Adding or editing a bill with a currency that differs from the default currency
of the project is no longer possible
[Longer-term solutions are being discussed](https://github.com/spiral-project/ihatemoney/issues/1232).
If you are using currencies in your projects, your input is welcome.
### Added
- Simplifies adding a bill with keyboard only (#1221)
- Add details of bills in history (#1223)
- Remember last "For whom?" field when adding a new bill (#1222)
- Speed up unit tests (#1214)
- Update translations for Spanish, Russian, Kannada, Swedish, Polish, German, and Italian
### Fixed ### Fixed
- Fix remembering the last selected payer when switching project (#1224) - Fix docker-compose example (#1164)
## 6.1.0 (2023-07-29)
### Added
- Add RSS feed for each project (#1158)
- Security: require private code to edit a project settings (#1204)
### Fixed
- Fix 404 page crash (#1201)
## 6.0.1 (2023-07-22)
### Added
- Add support for `APPLICATION_ROOT` in Docker container (#1189)
- Improve docker-compose example: admin password and volume for database (#1169)
### Fixed
- Fix docker-compose example quoting (#1164)
- Fix crash when using existing sessions (migrate them to dict) (#1194)
- Add newly created projects to the list of projects (#1193)
## 6.0.0 (2023-07-13) ## 6.0.0 (2023-07-13)

View file

@ -23,7 +23,6 @@ DavidRThrashJr
donkers donkers
Edwin Smulders Edwin Smulders
Elizabeth Sherrock Elizabeth Sherrock
Éloi Rivard
eMerzh eMerzh
Erwan Lacoudre Erwan Lacoudre
Feth AREZKI Feth AREZKI
@ -33,7 +32,6 @@ Glandos
Heimen Stoffels Heimen Stoffels
James Leong James Leong
Jocelyn Delalande Jocelyn Delalande
Jojo144
Lod Lod
Luc Didry Luc Didry
Lucas Verney Lucas Verney

View file

@ -26,7 +26,6 @@ ENV DEBUG="False" \
SHOW_ADMIN_EMAIL="True" \ SHOW_ADMIN_EMAIL="True" \
SQLALCHEMY_DATABASE_URI="sqlite:////database/ihatemoney.db" \ SQLALCHEMY_DATABASE_URI="sqlite:////database/ihatemoney.db" \
SQLALCHEMY_TRACK_MODIFICATIONS="False" \ SQLALCHEMY_TRACK_MODIFICATIONS="False" \
APPLICATION_ROOT="/" \
ENABLE_CAPTCHA="False" \ ENABLE_CAPTCHA="False" \
LEGAL_LINK="" LEGAL_LINK=""

3
MANIFEST.in Normal file
View file

@ -0,0 +1,3 @@
include *.rst
recursive-include ihatemoney *.rst *.py *.yaml *.po *.mo *.html *.css *.js *.eot *.svg *.woff *.txt *.png *.webp *.ini *.cfg *.j2 *.jpg *.gif *.ico
include LICENSE CONTRIBUTORS CHANGELOG.rst

View file

@ -1,40 +1,60 @@
VIRTUALENV = uv venv VIRTUALENV = python3 -m venv
SPHINX_BUILDDIR = docs/_build
VENV := $(shell realpath $${VIRTUAL_ENV-.venv}) VENV := $(shell realpath $${VIRTUAL_ENV-.venv})
BIN := uv tool run PYTHON = $(VENV)/bin/python3
PIP := uv pip
PYTHON = $(BIN)/python3
DEV_STAMP = $(VENV)/.dev_env_installed.stamp DEV_STAMP = $(VENV)/.dev_env_installed.stamp
INSTALL_STAMP = $(VENV)/.install.stamp INSTALL_STAMP = $(VENV)/.install.stamp
TEMPDIR := $(shell mktemp -d) TEMPDIR := $(shell mktemp -d)
ZOPFLIPNG := zopflipng ZOPFLIPNG := zopflipng
MAGICK_MOGRIFY := mogrify MAGICK_MOGRIFY := mogrify
.PHONY: all
all: install ## Alias for install
.PHONY: install
install: virtualenv setup.cfg $(INSTALL_STAMP) ## Install dependencies
$(INSTALL_STAMP):
$(VENV)/bin/pip install -U pip
$(VENV)/bin/pip install -e .
touch $(INSTALL_STAMP)
.PHONY: virtualenv .PHONY: virtualenv
virtualenv: $(PYTHON) virtualenv: $(PYTHON)
$(PYTHON): $(PYTHON):
$(VIRTUALENV) $(VENV) $(VIRTUALENV) $(VENV)
.PHONY: install-dev
install-dev: virtualenv setup.cfg $(INSTALL_STAMP) $(DEV_STAMP) ## Install development dependencies
$(DEV_STAMP): $(PYTHON)
$(VENV)/bin/pip install -Ue .[dev]
touch $(DEV_STAMP)
.PHONY: remove-install-stamp
remove-install-stamp:
rm $(INSTALL_STAMP)
.PHONY: update
update: remove-install-stamp install ## Update the dependencies
.PHONY: serve .PHONY: serve
serve: build-translations ## Run the ihatemoney server serve: install ## Run the ihatemoney server
@echo 'Running ihatemoney on http://localhost:5000' @echo 'Running ihatemoney on http://localhost:5000'
FLASK_DEBUG=1 FLASK_APP=ihatemoney.wsgi uv run flask run --host=0.0.0.0 FLASK_DEBUG=1 FLASK_APP=ihatemoney.wsgi $(VENV)/bin/flask run --host=0.0.0.0
.PHONY: test .PHONY: test
test: test: install-dev ## Run the tests
uv run --extra dev --extra database pytest . $(VENV)/bin/tox
.PHONY: lint .PHONY: black
lint: black: install-dev ## Run the tests
uv tool run ruff check . $(VENV)/bin/black --target-version=py37 .
uv tool run vermin --no-tips --violations -t=3.8- .
.PHONY: format .PHONY: isort
format: isort: install-dev ## Run the tests
uv tool run ruff format . $(VENV)/bin/isort .
.PHONY: release .PHONY: release
release: # Release a new version (see https://ihatemoney.readthedocs.io/en/latest/contributing.html#how-to-release) release: install-dev ## Release a new version (see https://ihatemoney.readthedocs.io/en/latest/contributing.html#how-to-release)
uv run --extra dev fullreleas $(VENV)/bin/fullrelease
.PHONY: compress-showcase .PHONY: compress-showcase
compress-showcase: compress-showcase:
@ -52,30 +72,27 @@ compress-assets: compress-showcase ## Compress static assets
.PHONY: build-translations .PHONY: build-translations
build-translations: ## Build the translations build-translations: ## Build the translations
uv run --extra dev pybabel compile -d ihatemoney/translations $(VENV)/bin/pybabel compile -d ihatemoney/translations
.PHONY: extract-translations .PHONY: update-translations
extract-translations: ## Extract new translations from source code update-translations: ## Extract new translations from source code
uv run --extra dev pybabel extract --add-comments "I18N:" --strip-comments --omit-header --no-location --mapping-file ihatemoney/babel.cfg -o ihatemoney/messages.pot ihatemoney $(VENV)/bin/pybabel extract --add-comments "I18N:" --strip-comments --omit-header --no-location --mapping-file ihatemoney/babel.cfg -o ihatemoney/messages.pot ihatemoney
uv run --extra dev pybabel update -i ihatemoney/messages.pot -d ihatemoney/translations/ $(VENV)/bin/pybabel update -i ihatemoney/messages.pot -d ihatemoney/translations/
.PHONY: create-database-revision .PHONY: create-database-revision
create-database-revision: ## Create a new database revision create-database-revision: ## Create a new database revision
@read -p "Please enter a message describing this revision: " rev_message; \ @read -p "Please enter a message describing this revision: " rev_message; \
uv run python -m ihatemoney.manage db migrate -d ihatemoney/migrations -m "$${rev_message}" $(PYTHON) -m ihatemoney.manage db migrate -d ihatemoney/migrations -m "$${rev_message}"
.PHONY: create-empty-database-revision .PHONY: create-empty-database-revision
create-empty-database-revision: ## Create an empty database revision create-empty-database-revision: ## Create an empty database revision
@read -p "Please enter a message describing this revision: " rev_message; \ @read -p "Please enter a message describing this revision: " rev_message; \
uv run python -m ihatemoney.manage db revision -d ihatemoney/migrations -m "$${rev_message}" $(PYTHON) -m ihatemoney.manage db revision -d ihatemoney/migrations -m "$${rev_message}"
.PHONY: clean .PHONY: clean
clean: ## Destroy the virtual environment clean: ## Destroy the virtual environment
rm -rf .venv rm -rf .venv
build-docs:
uv run --extra doc sphinx-build -a -n -b html -d docs/_build/doctrees docs docs/_build/html
.PHONY: help .PHONY: help
help: ## Show the help indications help: ## Show the help indications
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

View file

@ -22,41 +22,10 @@ highly encouraged to do so.
## Requirements ## Requirements
- **Python**: version 3.8 to 3.12. - **Python**: version 3.7 to 3.11.
- **Backends**: SQLite, PostgreSQL, MariaDB (version 10.3.2 or above), - **Backends**: SQLite, PostgreSQL, MariaDB (version 10.3.2 or above),
Memory. Memory.
## Current direction (as of 2024)
Ihatemoney was started in 2011, and we believe the project has reached a certain
level of maturity now. The overall energy of contributors is not as high as it
used to be.
In addition, there are now several self-hosted alternatives (for instance
[cospend](https://github.com/julien-nc/cospend-nc/tree/main),
[spliit](https://github.com/spliit-app/spliit)).
As maintainers, we believe that the project is still relevant but should gear
towards some kind of "maintenance mode":
* **Simplicity** is now the main goal of the project. It has always been a compass
for the project, and the resulting software is appreciated by both users and
server administrators. For us, "simplicity" is positive and encompasses both
technical aspects (very few javascript code, manageable dependencies, small code
size...) and user-visible aspects (straightforward interface, no need to create
accounts for people you invite, same web interface on mobile...)
* **Stability** is prioritized over adding major new features. We found ourselves
complexifying the codebase (and the interface) while accepting some
contributions. Our goal now is to have a minimal set of features that do most of
the job. We believe this will help lower the maintainance burden.
* **User interface and user experience improvements** are always super welcome !
It is still possible to propose new features, but they should fit into
this new direction. Simplicity of the UI/UX and simplicity of the technical
implementation will be the main factors when deciding to accept new features.
## Contributing ## Contributing
Do you wish to contribute to IHateMoney? Fantastic! There's a lot of Do you wish to contribute to IHateMoney? Fantastic! There's a lot of

View file

@ -4,9 +4,9 @@
| Version | Supported | | Version | Supported |
| ------- | ------------------ | | ------- | ------------------ |
| 6.2.x | :heavy_check_mark: | | 5.0.x | :heavy_check_mark: |
| 6.1.x | :heavy_check_mark: | | 4.1.x | :heavy_check_mark: |
| <= 6.0 | :x: | | <= 4.0 | :x: |
## Reporting a Vulnerability ## Reporting a Vulnerability

View file

@ -23,7 +23,6 @@ SHOW_ADMIN_EMAIL = $SHOW_ADMIN_EMAIL
SQLACHEMY_DEBUG = DEBUG SQLACHEMY_DEBUG = DEBUG
SQLALCHEMY_DATABASE_URI = "$SQLALCHEMY_DATABASE_URI" SQLALCHEMY_DATABASE_URI = "$SQLALCHEMY_DATABASE_URI"
SQLALCHEMY_TRACK_MODIFICATIONS = $SQLALCHEMY_TRACK_MODIFICATIONS SQLALCHEMY_TRACK_MODIFICATIONS = $SQLALCHEMY_TRACK_MODIFICATIONS
APPLICATION_ROOT = "$APPLICATION_ROOT"
ENABLE_CAPTCHA = $ENABLE_CAPTCHA ENABLE_CAPTCHA = $ENABLE_CAPTCHA
LEGAL_LINK = "$LEGAL_LINK" LEGAL_LINK = "$LEGAL_LINK"
EOF EOF

View file

@ -35,7 +35,6 @@ services:
- SHOW_ADMIN_EMAIL=True - SHOW_ADMIN_EMAIL=True
- SQLALCHEMY_DATABASE_URI=sqlite:////database/ihatemoney.db - SQLALCHEMY_DATABASE_URI=sqlite:////database/ihatemoney.db
- SQLALCHEMY_TRACK_MODIFICATIONS=False - SQLALCHEMY_TRACK_MODIFICATIONS=False
- APPLICATION_ROOT=/
- ENABLE_CAPTCHA=False - ENABLE_CAPTCHA=False
- LEGAL_LINK= - LEGAL_LINK=
- PORT=8000 - PORT=8000

View file

@ -34,9 +34,9 @@ the token (of course, you need to authenticate):
$ curl --basic -u demo:demo https://ihatemoney.org/api/projects/demo/token $ curl --basic -u demo:demo https://ihatemoney.org/api/projects/demo/token
{"token": "WyJ0ZXN0Il0.Rt04fNMmxp9YslCRq8hB6jE9s1Q"} {"token": "WyJ0ZXN0Il0.Rt04fNMmxp9YslCRq8hB6jE9s1Q"}
Make sure to store this token securely: it allows almost full access to the Make sure to store this token securely: it allows full access to the
project. For instance, use it to obtain information about the project project. For instance, use it to obtain information about the project
(replace `PROJECT_TOKEN` with the actual token): (replace PROJECT_TOKEN with the actual token):
$ curl --oauth2-bearer "PROJECT_TOKEN" https://ihatemoney.org/api/projects/demo $ curl --oauth2-bearer "PROJECT_TOKEN" https://ihatemoney.org/api/projects/demo
@ -51,8 +51,7 @@ simply create an URL of the form:
https://ihatemoney.org/demo/join/PROJECT_TOKEN https://ihatemoney.org/demo/join/PROJECT_TOKEN
Such a link grants read-write access to the project associated with the token, Such a link grants full access to the project associated with the token.
but it does not allow to change project settings.
### Projects ### Projects
@ -68,8 +67,8 @@ A project needs the following arguments:
- `name`: the project name (string) - `name`: the project name (string)
- `id`: the project identifier (string without special chars or - `id`: the project identifier (string without special chars or
spaces) spaces)
- `password`: the project password / private code (string) - `password`: the project password / secret code (string)
- `contact_email`: the contact email, used to recover the private code (string) - `contact_email`: the contact email (string)
Optional arguments: Optional arguments:
@ -84,9 +83,7 @@ Here is the command:
-d 'name=yay&id=yay&password=yay&contact_email=yay@notmyidea.org' -d 'name=yay&id=yay&password=yay&contact_email=yay@notmyidea.org'
"yay" "yay"
As you can see, the API returns the identifier of the project. It might be different As you can see, the API returns the identifier of the project.
from what you requested, because the ID is normalized (remove special characters,
change to lowercase, etc).
#### Getting information about the project #### Getting information about the project
@ -111,12 +108,7 @@ Updating a project is done with the `PUT` verb:
$ curl --basic -u yay:yay -X PUT\ $ curl --basic -u yay:yay -X PUT\
https://ihatemoney.org/api/projects/yay -d\ https://ihatemoney.org/api/projects/yay -d\
'name=yay&id=yay&current_password=yay&password=newyay&contact_email=youpi@notmyidea.org' 'name=yay&id=yay&password=yay&contact_email=youpi@notmyidea.org'
You need to give the current private code as the `current_password` field. This is a security
measure to ensure that knowledge of an auth token is not enough to update settings.
Note that in any case you can never change the ID of a project.
#### Deleting a project #### Deleting a project
@ -133,7 +125,7 @@ You can get all the members with a `GET` on
[{"weight": 1, "activated": true, "id": 31, "name": "Arnaud"}, [{"weight": 1, "activated": true, "id": 31, "name": "Arnaud"},
{"weight": 1, "activated": true, "id": 32, "name": "Alexis"}, {"weight": 1, "activated": true, "id": 32, "name": "Alexis"},
{"weight": 1, "activated": true, "id": 33, "name": "Olivier"}, {"weight": 1, "activated": true, "id": 33, "name": "Olivier"},
{"weight": 1, "activated": true, "id": 34, "name": "Jeanne"}] {"weight": 1, "activated": true, "id": 34, "name": "Fred"}]
Add a member with a `POST` request on `/api/projects/<id>/members`: Add a member with a `POST` request on `/api/projects/<id>/members`:
@ -244,7 +236,7 @@ You can get some project stats with a `GET` on
"balance": 10.5 "balance": 10.5
}, },
{ {
"member": {"activated": true, "id": 2, "name": "jeanne", "weight": 1.0}, "member": {"activated": true, "id": 2, "name": "fred", "weight": 1.0},
"paid": 5, "paid": 5,
"spent": 15.5, "spent": 15.5,
"balance": -10.5 "balance": -10.5

View file

@ -1,12 +1,9 @@
import datetime
templates_path = ["_templates"] templates_path = ["_templates"]
source_suffix = ".rst" source_suffix = ".rst"
master_doc = "index" master_doc = "index"
project = "I hate money" project = "I hate money"
year = datetime.datetime.now().strftime("%Y") copyright = "2011-2021, The 'I hate money' team"
copyright = f"2011-{year}, The 'I hate money' team"
version = "5.0" version = "5.0"
release = "5.0" release = "5.0"

View file

@ -127,11 +127,11 @@ ADMIN_PASSWORD needs to be set.
## APPLICATION_ROOT ## APPLICATION_ROOT
By default, ihatemoney will be served at domain root (e.g: If empty, ihatemoney will be served at domain root (e.g:
*http://domain.tld*), if set to `"/somestring"`, it will be served from a *http://domain.tld*), if set to `"somestring"`, it will be served from a
"folder" (e.g: *http://domain.tld/somestring*). "folder" (e.g: *http://domain.tld/somestring*).
- **Default value:** `"/"` - **Default value:** `""` (empty string)
## BABEL_DEFAULT_TIMEZONE ## BABEL_DEFAULT_TIMEZONE
@ -173,14 +173,6 @@ URL you want.
- **Default value:** `""` (empty string) - **Default value:** `""` (empty string)
- **Production value:** The URL of your chosing. - **Production value:** The URL of your chosing.
## SITE_NAME
It is possible to change the name of the site to something at your liking.
- **Default value:** `"I Hate Money"` (empty string)
- **Production value:** The name of your choosing
## Configuring email sending ## Configuring email sending
By default, Ihatemoney sends emails using a local SMTP server, but it's By default, Ihatemoney sends emails using a local SMTP server, but it's
@ -196,12 +188,3 @@ project](https://pythonhosted.org/flask-mail/#configuring-flask-mail)
- **MAIL_USERNAME** : default **None** - **MAIL_USERNAME** : default **None**
- **MAIL_PASSWORD** : default **None** - **MAIL_PASSWORD** : default **None**
- **DEFAULT_MAIL_SENDER** : default **None** - **DEFAULT_MAIL_SENDER** : default **None**
## Configuring password hashes
The werkzeug [generate_password_hash](https://werkzeug.palletsprojects.com/utils/#werkzeug.security.generate_password_hash)
is used to create password hashes. By default the default werkzeug values
are used, however you can customize those values with:
- **PASSWORD_HASH_METHOD** : default **None**
- **PASSWORD_HASH_SALT_LENGTH** : default **None**

View file

@ -1,37 +1,5 @@
# Contributing # Contributing
## Current direction (as of 2024)
Ihatemoney was started in 2011, and we believe the project has reached a certain
level of maturity now. The overall energy of contributors is not as high as it
used to be.
In addition, there are now several self-hosted alternatives (for instance
[cospend](https://github.com/julien-nc/cospend-nc/tree/main),
[spliit](https://github.com/spliit-app/spliit)).
As maintainers, we believe that the project is still relevant but should gear
towards some kind of "maintenance mode":
* **Simplicity** is now the main goal of the project. It has always been a compass
for the project, and the resulting software is appreciated by both users and
server administrators. For us, "simplicity" is positive and encompasses both
technical aspects (very few javascript code, manageable dependencies, small code
size...) and user-visible aspects (straightforward interface, no need to create
accounts for people you invite, same web interface on mobile...)
* **Stability** is prioritized over adding major new features. We found ourselves
complexifying the codebase (and the interface) while accepting some
contributions. Our goal now is to have a minimal set of features that do most of
the job. We believe this will help lower the maintainance burden.
* **User interface and user experience improvements** are always super welcome !
It is still possible to propose new features, but they should fit into
this new direction. Simplicity of the UI/UX and simplicity of the technical
implementation will be the main factors when deciding to accept new features.
## How to contribute ## How to contribute
You would like to contribute? First, thanks a bunch! This project is a You would like to contribute? First, thanks a bunch! This project is a
@ -78,15 +46,6 @@ Thanks again!
(setup-dev-environment)= (setup-dev-environment)=
## Set up a dev environment ## Set up a dev environment
### Requirements
In addition to general {ref}`requirements<system-requirements>`, you will need
**uv**. It recommended to install uv [system
wide](https://docs.astral.sh/uv/getting-started/installation/#standalone-installer)
as it is a kind of replacement for pip.
### Getting the sources
You must develop on top of the Git master branch: You must develop on top of the Git master branch:
git clone https://github.com/spiral-project/ihatemoney.git git clone https://github.com/spiral-project/ihatemoney.git
@ -192,7 +151,7 @@ We are using [black](https://black.readthedocs.io/en/stable/) and
Python files in this project. Be sure to run it locally on your files. Python files in this project. Be sure to run it locally on your files.
To do so, just run: To do so, just run:
make lint make black isort
You can also integrate them with your dev environment (as a You can also integrate them with your dev environment (as a
*format-on-save* hook, for instance). *format-on-save* hook, for instance).
@ -231,19 +190,6 @@ revision file which can be created with the following command:
You then need to write the migration steps yourself. You then need to write the migration steps yourself.
## Repository rules
- Please, try to keep it to **one pull request per feature:** if you want to do more
than one thing, send multiple pull requests. It will be easier for us to review and
merge.
- **Document your code:** if you add a new feature, please document it in the
- All the people working on this project do it on their spare time. So please, be
patient if we don't answer right away.
- We try to have two maintainers review the pull requests before merging it. So please,
be patient if we don't merge it right away. After one week, only one maintainer approval
is required.
## How to build the documentation ? ## How to build the documentation ?
The documentation is using The documentation is using
@ -315,9 +261,10 @@ In order to issue a new release, follow the following steps:
make compress-assets make compress-assets
- Extract the translations: - Build the translations:
make extract-translations make update-translations
make build-translations
- If you're not completely sure of yourself at this point, you can - If you're not completely sure of yourself at this point, you can
optionally: create a new branch, push it, open a pull request, check optionally: create a new branch, push it, open a pull request, check
@ -334,7 +281,7 @@ Index](https://pypi.org) (PyPI) and publish a tag in the git repository.
::: {note} ::: {note}
The above command will prompt for version number, handle The above command will prompt for version number, handle
`CHANGELOG.md` and `pyproject.toml` updates, package creation, `CHANGELOG.md` and `setup.cfg` updates, package creation,
pypi upload. It will prompt you before each step to get your consent. pypi upload. It will prompt you before each step to get your consent.
::: :::

View file

@ -26,7 +26,7 @@ hub](https://hub.docker.com/r/ihatemoney/ihatemoney/).
This is probably the simplest way to get something running. Once you This is probably the simplest way to get something running. Once you
have Docker installed on your system, just issue : have Docker installed on your system, just issue :
docker run -d -p 8000:8000 ihatemoney/ihatemoney:latest docker run -d -p 8000:8000 ihatemoney/ihatemoney
Ihatemoney is now available on <http://localhost:8000>. Ihatemoney is now available on <http://localhost:8000>.
@ -54,10 +54,6 @@ To enable the Admin dashboard, first generate a hashed password with:
docker run -it --rm --entrypoint ihatemoney ihatemoney/ihatemoney generate_password_hash docker run -it --rm --entrypoint ihatemoney ihatemoney/ihatemoney generate_password_hash
:::{note}
The generated password hash is salted. Which means that the same password will generate a different hash each time. This is normal and expected behavior.
:::
At the prompt, enter a password to use for the admin dashboard. The At the prompt, enter a password to use for the admin dashboard. The
command will print the hashed password string. command will print the hashed password string.
@ -66,18 +62,12 @@ Add these additional environment variables to the docker run invocation:
-e ACTIVATE_ADMIN_DASHBOARD=True \ -e ACTIVATE_ADMIN_DASHBOARD=True \
-e ADMIN_PASSWORD=<hashed_password_string> \ -e ADMIN_PASSWORD=<hashed_password_string> \
:::{note}
If you are using a `docker-compose.yml` file and need to include a password hash, use `$$` instead of `$` to escape the dollar sign. This ensures that the hash is treated as a literal string rather than a variable in Bash.
:::
Additional gunicorn parameters can be passed using the docker `CMD` Additional gunicorn parameters can be passed using the docker `CMD`
parameter. For example, use the following command to add more gunicorn parameter. For example, use the following command to add more gunicorn
workers: workers:
docker run -d -p 8000:8000 ihatemoney/ihatemoney -w 3 docker run -d -p 8000:8000 ihatemoney/ihatemoney -w 3
If needed, there is a `docker-compose.yml` file available as an example on the [project repository](https://github.com/spiral-project/ihatemoney/blob/master/docker-compose.yml)
(cloud)= (cloud)=
## On a Cloud Provider ## On a Cloud Provider
@ -93,7 +83,7 @@ Some Paas (Platform-as-a-Service), provide a documentation or even a quick insta
«Ihatemoney» depends on: «Ihatemoney» depends on:
- **Python**: any version from 3.8 to 3.12 will work. - **Python**: any version from 3.7 to 3.11 will work.
- **A database backend**: choose among SQLite, PostgreSQL, MariaDB (>= - **A database backend**: choose among SQLite, PostgreSQL, MariaDB (>=
10.3.2). 10.3.2).
- **Virtual environment** (recommended): [python3-venv]{.title-ref} - **Virtual environment** (recommended): [python3-venv]{.title-ref}

View file

@ -11,48 +11,39 @@ expenses in the first place!
That being said, there are a few mechanisms to limit the impact of a That being said, there are a few mechanisms to limit the impact of a
malicious member and to manage changes in membership (e.g. ensuring that malicious member and to manage changes in membership (e.g. ensuring that
a previous member can no longer access the project). But these a previous member can no longer access the project). But these
mechanisms don't prevent a malicious member from breaking things in mechanisms don\'t prevent a malicious member from breaking things in
your project! your project!
## Security model ## Security model
A project has four main parameters when it comes to security: A project has three main parameters when it comes to security:
- **project identifier** (equivalent to a \"login\") - **project identifier** (equivalent to a \"login\")
- **private code** (equivalent to a \"password\") - **private code** (equivalent to a \"password\")
- **auth token** (cryptographically derived from the private code) - **token** (cryptographically derived from the private code)
- **feed token** (also cryptographically derived from the private code)
Somebody with the **private code** can: Somebody with the private code can:
- access the project through the web interface or the API - access the project through the web interface or the API
- add, modify or remove participants
- add, modify or remove bills - add, modify or remove bills
- view statistics of the project
- view project history - view project history
- change basic settings of the project - change basic settings of the project
- change the email address associated to the project - change the email address associated to the project
- change the private code of the project - change the private code of the project
- delete the project
Somebody with the **auth token** can manipulate the project through the API: Somebody with the token can manipulate the project through the API to do
essentially the same thing:
- access the project - access the project
- add, modify or remove participants
- add, modify or remove bills - add, modify or remove bills
- view statistics of the project - change basic settings of the project
- delete the project - change the email address associated to the project
- change the private code of the project
The auth token is not enough to change basic settings of the project, The token can also be used to build \"invitation links\". These links
or to change the email address or the private code.
The auth token can also be used to build "invitation links". These links
allow to login on the web interface without knowing the private code, allow to login on the web interface without knowing the private code,
see below. see below.
Somebody with the **feed token** can only access a read-only view of the project
through a RSS feed (at `/<project_id>/feed/<token>.xml`).
## Giving access to a project ## Giving access to a project
There are two main ways to give access to a project to a new person: There are two main ways to give access to a project to a new person:
@ -66,36 +57,25 @@ The second method is interesting because it does not reveal the private
code. In particular, somebody that is logged-in through the invitation code. In particular, somebody that is logged-in through the invitation
link will not be able to change the private code, because the web link will not be able to change the private code, because the web
interface requires a confirmation of the existing private code to change interface requires a confirmation of the existing private code to change
it. Similarly, changing other important settings or deleting the project it. However, a motivated person could extract the token from the
from the web interface requires knowledge of the private code.
However, a motivated person could extract the auth token from the
invitation link, use it to access the project through the API, and invitation link, use it to access the project through the API, and
delete the project through the API. This is a [known issue](https://github.com/spiral-project/ihatemoney/issues/1206). change the private code through the API.
## Removing access to a project ## Removing access to a project
If a person should no longer be able to access a project, the only way If a person should no longer be able to access a project, the only way
is to change the private code for the whole project. is to change the private code.
This will prevent anybody from logging in with the old private code. This will also automatically change the token: old invitation links
However, anybody with an existing session cookie will still have won\'t work anymore, and anybody with the old token will no longer be
access to the project. This is a [known issue](https://github.com/spiral-project/ihatemoney/issues/857) able to access the project through the API.
that should be fixed.
Changing the private code will automatically change the auth token:
old invitation links won't work anymore, and anybody with the old token
will no longer be able to access the project through the API.
This will also automatically change the feed token, so that existing
links to the RSS feed for the project will no longer work.
## Recovering access to a project ## Recovering access to a project
If the private code is no longer known, the creator of the project can If the private code is no longer known, the creator of the project can
still recover access. He/she must have provided an email address when still recover access. He/she must have provided an email address when
creating the project, and Ihatemoney can send a reset link to this email creating the project, and Ihatemoney can send a reset link to this email
address (classical "forgot your password" functionality). address (classical \"forgot your password\" functionality).
Note, however, that somebody with the private code could have changed Note, however, that somebody with the private code could have changed
the email address in the settings at any time. the email address in the settings at any time.
@ -111,6 +91,6 @@ Note, however, that the history feature is primarily meant to protect
against mistakes: a malicious member can easily remove all entries from against mistakes: a malicious member can easily remove all entries from
the history! the history!
The best defense against this kind of issues is... backups! All data The best defense against this kind of issues is\... backups! All data
for a project can be exported through the settings page or through the for a project can be exported through the settings page or through the
API. The server administrator can also backup the database. API.

View file

@ -1,11 +0,0 @@
import sys
from hatchling.builders.hooks.plugin.interface import BuildHookInterface
class CustomBuildHook(BuildHookInterface):
def initialize(self, version, build_data):
sys.path.insert(0, "./ihatemoney")
from babel_utils import compile_catalogs
compile_catalogs()

View file

@ -1,2 +1,3 @@
[python: **.py] [python: **.py]
[jinja2: **/templates/**.html] [jinja2: **/templates/**.html]
extensions=jinja2.ext.autoescape,jinja2.ext.with_

View file

@ -1,11 +0,0 @@
from pathlib import Path
from babel.messages.frontend import compile_catalog
def compile_catalogs():
cmd = compile_catalog()
cmd.directory = Path(__file__).parent / "translations"
cmd.statistics = True
cmd.finalize_options()
cmd.run()

View file

@ -47,14 +47,11 @@ ACTIVATE_ADMIN_DASHBOARD = False
# service over plain HTTP. # service over plain HTTP.
SESSION_COOKIE_SECURE = True SESSION_COOKIE_SECURE = True
# Set this to a URL path under which the application will be served. Defaults to "/"
APPLICATION_ROOT = "/"
# You can activate an optional CAPTCHA if you want to. It can be helpful # You can activate an optional CAPTCHA if you want to. It can be helpful
# to filter spammer bots. # to filter spammer bots.
ENABLE_CAPTCHA = False # ENABLE_CAPTCHA = True
# You may want to point to a special legal page, for instance to give information # You may want to point to a special legal page, for instance to give information
# about GDPR, or how you handle the data of your users. # about GDPR, or how you handle the data of your users.
# Set this variable to the URL you want. # Set this variable to the URL you want.
LEGAL_LINK = "" # LEGAL_LINK = ""

View file

@ -36,181 +36,13 @@ class CurrencyConverter(object, metaclass=Singleton):
return rates return rates
def get_currencies(self, with_no_currency=True): def get_currencies(self, with_no_currency=True):
currencies = [ rates = [
"AED", rate
"AFN", for rate in self.get_rates()
"ALL", if with_no_currency or rate != self.no_currency
"AMD",
"ANG",
"AOA",
"ARS",
"AUD",
"AWG",
"AZN",
"BAM",
"BBD",
"BDT",
"BGN",
"BHD",
"BIF",
"BMD",
"BND",
"BOB",
"BRL",
"BSD",
"BTC",
"BTN",
"BWP",
"BYN",
"BZD",
"CAD",
"CDF",
"CHF",
"CLF",
"CLP",
"CNH",
"CNY",
"COP",
"CRC",
"CUC",
"CUP",
"CVE",
"CZK",
"DJF",
"DKK",
"DOP",
"DZD",
"EGP",
"ERN",
"ETB",
"EUR",
"FJD",
"FKP",
"GBP",
"GEL",
"GGP",
"GHS",
"GIP",
"GMD",
"GNF",
"GTQ",
"GYD",
"HKD",
"HNL",
"HRK",
"HTG",
"HUF",
"IDR",
"ILS",
"IMP",
"INR",
"IQD",
"IRR",
"ISK",
"JEP",
"JMD",
"JOD",
"JPY",
"KES",
"KGS",
"KHR",
"KMF",
"KPW",
"KRW",
"KWD",
"KYD",
"KZT",
"LAK",
"LBP",
"LKR",
"LRD",
"LSL",
"LYD",
"MAD",
"MDL",
"MGA",
"MKD",
"MMK",
"MNT",
"MOP",
"MRU",
"MUR",
"MVR",
"MWK",
"MXN",
"MYR",
"MZN",
"NAD",
"NGN",
"NIO",
"NOK",
"NPR",
"NZD",
"OMR",
"PAB",
"PEN",
"PGK",
"PHP",
"PKR",
"PLN",
"PYG",
"QAR",
"RON",
"RSD",
"RUB",
"RWF",
"SAR",
"SBD",
"SCR",
"SDG",
"SEK",
"SGD",
"SHP",
"SLL",
"SOS",
"SRD",
"SSP",
"STD",
"STN",
"SVC",
"SYP",
"SZL",
"THB",
"TJS",
"TMT",
"TND",
"TOP",
"TRY",
"TTD",
"TWD",
"TZS",
"UAH",
"UGX",
"USD",
"UYU",
"UZS",
"VEF",
"VES",
"VND",
"VUV",
"WST",
"XAF",
"XAG",
"XAU",
"XCD",
"XDR",
"XOF",
"XPD",
"XPF",
"XPT",
"YER",
"ZAR",
"ZMW",
"ZWL",
] ]
if with_no_currency: rates.sort(key=lambda rate: "" if rate == self.no_currency else rate)
currencies.append(self.no_currency) return rates
return currencies
def exchange_currency(self, amount, source_currency, dest_currency): def exchange_currency(self, amount, source_currency, dest_currency):
if ( if (

View file

@ -3,19 +3,14 @@ DEBUG = SQLACHEMY_ECHO = False
SQLALCHEMY_DATABASE_URI = "sqlite:////tmp/ihatemoney.db" SQLALCHEMY_DATABASE_URI = "sqlite:////tmp/ihatemoney.db"
SQLALCHEMY_TRACK_MODIFICATIONS = False SQLALCHEMY_TRACK_MODIFICATIONS = False
SECRET_KEY = "tralala" SECRET_KEY = "tralala"
SITE_NAME = "I Hate Money"
MAIL_DEFAULT_SENDER = "Budget manager <admin@example.com>" MAIL_DEFAULT_SENDER = "Budget manager <admin@example.com>"
SHOW_ADMIN_EMAIL = True SHOW_ADMIN_EMAIL = True
ACTIVATE_DEMO_PROJECT = True ACTIVATE_DEMO_PROJECT = True
ACTIVATE_ADMIN_DASHBOARD = False
ADMIN_PASSWORD = "" ADMIN_PASSWORD = ""
ALLOW_PUBLIC_PROJECT_CREATION = True ALLOW_PUBLIC_PROJECT_CREATION = True
ACTIVATE_ADMIN_DASHBOARD = False
SESSION_COOKIE_SECURE = True SESSION_COOKIE_SECURE = True
APPLICATION_ROOT = "/"
ENABLE_CAPTCHA = False
LEGAL_LINK = ""
SUPPORTED_LANGUAGES = [ SUPPORTED_LANGUAGES = [
"az",
"ca", "ca",
"cs", "cs",
"de", "de",
@ -48,3 +43,5 @@ SUPPORTED_LANGUAGES = [
"uk", "uk",
"zh_Hans", "zh_Hans",
] ]
ENABLE_CAPTCHA = False
LEGAL_LINK = ""

View file

@ -9,7 +9,7 @@ from flask_babel import lazy_gettext as _
from flask_wtf.file import FileAllowed, FileField, FileRequired from flask_wtf.file import FileAllowed, FileField, FileRequired
from flask_wtf.form import FlaskForm from flask_wtf.form import FlaskForm
from markupsafe import Markup from markupsafe import Markup
from werkzeug.security import check_password_hash from werkzeug.security import check_password_hash, generate_password_hash
from wtforms.fields import ( from wtforms.fields import (
BooleanField, BooleanField,
DateField, DateField,
@ -39,11 +39,10 @@ from wtforms.validators import (
) )
from ihatemoney.currency_convertor import CurrencyConverter from ihatemoney.currency_convertor import CurrencyConverter
from ihatemoney.models import Bill, BillType, LoggingMode, Person, Project from ihatemoney.models import Bill, LoggingMode, Person, Project
from ihatemoney.utils import ( from ihatemoney.utils import (
em_surround, em_surround,
eval_arithmetic_expression, eval_arithmetic_expression,
generate_password_hash,
render_localized_currency, render_localized_currency,
slugify, slugify,
) )
@ -67,9 +66,6 @@ def get_billform_for(project, set_default=True, **kwargs):
if form.original_currency.data is None: if form.original_currency.data is None:
form.original_currency.data = project.default_currency form.original_currency.data = project.default_currency
# Used in validate_original_currency
form.project_currency = project.default_currency
show_no_currency = form.original_currency.data == CurrencyConverter.no_currency show_no_currency = form.original_currency.data == CurrencyConverter.no_currency
form.original_currency.choices = [ form.original_currency.choices = [
@ -90,6 +86,7 @@ def get_billform_for(project, set_default=True, **kwargs):
class CommaDecimalField(DecimalField): class CommaDecimalField(DecimalField):
"""A class to deal with comma in Decimal Field""" """A class to deal with comma in Decimal Field"""
def process_formdata(self, value): def process_formdata(self, value):
@ -124,11 +121,6 @@ class CalculatorStringField(StringField):
class EditProjectForm(FlaskForm): class EditProjectForm(FlaskForm):
name = StringField(_("Project name"), validators=[DataRequired()]) name = StringField(_("Project name"), validators=[DataRequired()])
current_password = PasswordField(
_("Current private code"),
description=_("Enter existing private code to edit project"),
validators=[DataRequired()],
)
# If empty -> don't change the password # If empty -> don't change the password
password = PasswordField( password = PasswordField(
_("New private code"), _("New private code"),
@ -162,13 +154,6 @@ class EditProjectForm(FlaskForm):
for currency_name in self.currency_helper.get_currencies() for currency_name in self.currency_helper.get_currencies()
] ]
def validate_current_password(self, field):
project = Project.query.get(self.id.data)
if project is None:
raise ValidationError(_("Unknown error"))
if not check_password_hash(project.password, self.current_password.data):
raise ValidationError(_("Invalid private code."))
@property @property
def logging_preference(self): def logging_preference(self):
"""Get the LoggingMode object corresponding to current form data.""" """Get the LoggingMode object corresponding to current form data."""
@ -187,20 +172,12 @@ class EditProjectForm(FlaskForm):
and field.data == CurrencyConverter.no_currency and field.data == CurrencyConverter.no_currency
and project.has_multiple_currencies() and project.has_multiple_currencies()
): ):
msg = _( raise ValidationError(
"This project cannot be set to 'no currency'" _(
" because it contains bills in multiple currencies." "This project cannot be set to 'no currency'"
" because it contains bills in multiple currencies."
)
) )
raise ValidationError(msg)
if (
project is not None
and field.data != project.default_currency
and project.has_bills()
):
msg = _(
"Cannot change project currency because currency conversion is broken"
)
raise ValidationError(msg)
def update(self, project): def update(self, project):
"""Update the project with the information from the form""" """Update the project with the information from the form"""
@ -235,9 +212,7 @@ class ImportProjectForm(FlaskForm):
class ProjectForm(EditProjectForm): class ProjectForm(EditProjectForm):
id = StringField(_("Project identifier"), validators=[DataRequired()]) id = StringField(_("Project identifier"), validators=[DataRequired()])
# Remove this field that is inherited from EditProjectForm # This field overrides the one from EditProjectForm
current_password = None
# This field overrides the one from EditProjectForm (to make it mandatory)
password = PasswordField(_("Private code"), validators=[DataRequired()]) password = PasswordField(_("Private code"), validators=[DataRequired()])
submit = SubmitField(_("Create the project")) submit = SubmitField(_("Create the project"))
@ -363,12 +338,6 @@ class BillForm(FlaskForm):
payed_for = SelectMultipleField( payed_for = SelectMultipleField(
_("For whom?"), validators=[DataRequired()], coerce=int _("For whom?"), validators=[DataRequired()], coerce=int
) )
bill_type = SelectField(
_("Bill Type"),
choices=BillType.choices(),
coerce=BillType,
default=BillType.EXPENSE,
)
submit = SubmitField(_("Submit")) submit = SubmitField(_("Submit"))
submit2 = SubmitField(_("Submit and add a new one")) submit2 = SubmitField(_("Submit and add a new one"))
@ -382,14 +351,12 @@ class BillForm(FlaskForm):
payer_id=self.payer.data, payer_id=self.payer.data,
project_default_currency=project.default_currency, project_default_currency=project.default_currency,
what=self.what.data, what=self.what.data,
bill_type=self.bill_type.data,
) )
def save(self, bill, project): def save(self, bill, project):
bill.payer_id = self.payer.data bill.payer_id = self.payer.data
bill.amount = self.amount.data bill.amount = self.amount.data
bill.what = self.what.data bill.what = self.what.data
bill.bill_type = BillType(self.bill_type.data)
bill.external_link = self.external_link.data bill.external_link = self.external_link.data
bill.date = self.date.data bill.date = self.date.data
bill.owers = Person.query.get_by_ids(self.payed_for.data, project) bill.owers = Person.query.get_by_ids(self.payed_for.data, project)
@ -403,7 +370,6 @@ class BillForm(FlaskForm):
self.payer.data = bill.payer_id self.payer.data = bill.payer_id
self.amount.data = bill.amount self.amount.data = bill.amount
self.what.data = bill.what self.what.data = bill.what
self.bill_type.data = bill.bill_type
self.external_link.data = bill.external_link self.external_link.data = bill.external_link
self.original_currency.data = bill.original_currency self.original_currency.data = bill.original_currency
self.date.data = bill.date self.date.data = bill.date
@ -425,17 +391,6 @@ class BillForm(FlaskForm):
# See https://github.com/python-babel/babel/issues/821 # See https://github.com/python-babel/babel/issues/821
raise ValidationError(f"Result is too high: {field.data}") raise ValidationError(f"Result is too high: {field.data}")
def validate_original_currency(self, field):
# Workaround for currency API breakage
# See #1232
if field.data not in [CurrencyConverter.no_currency, self.project_currency]:
msg = _(
"Failed to convert from %(bill_currency)s currency to %(project_currency)s",
bill_currency=field.data,
project_currency=self.project_currency,
)
raise ValidationError(msg)
class MemberForm(FlaskForm): class MemberForm(FlaskForm):
name = StringField(_("Name"), validators=[DataRequired()], filters=[strip_filter]) name = StringField(_("Name"), validators=[DataRequired()], filters=[strip_filter])

View file

@ -38,10 +38,7 @@ def history_sort_key(history_item_dict):
def describe_version(version_obj): def describe_version(version_obj):
"""Use the base model str() function to describe a version object""" """Use the base model str() function to describe a version object"""
if not version_obj: return parent_class(type(version_obj)).__str__(version_obj)
return ""
else:
return parent_class(type(version_obj)).__str__(version_obj)
def describe_owers_change(version, human_readable_names): def describe_owers_change(version, human_readable_names):
@ -86,26 +83,10 @@ def get_history(project, human_readable_names=True):
"time": version.transaction.issued_at, "time": version.transaction.issued_at,
"operation_type": version.operation_type, "operation_type": version.operation_type,
"object_type": object_type, "object_type": object_type,
"bill_details": None,
"object_desc": object_str, "object_desc": object_str,
"ip": version.transaction.remote_addr, "ip": version.transaction.remote_addr,
} }
if object_type == "Bill":
if version.operation_type == Operation.INSERT or not version.previous:
detailed_version = version
else:
detailed_version = version.previous
details = {
"date": detailed_version.date,
"payer": describe_version(detailed_version.payer),
"amount": detailed_version.amount,
"owers": [describe_version(o) for o in detailed_version.owers],
"external_link": detailed_version.external_link,
"original_currency": detailed_version.original_currency,
}
common_properties["bill_details"] = details
if version.operation_type == Operation.UPDATE: if version.operation_type == Operation.UPDATE:
# Only iterate the changeset if the previous version # Only iterate the changeset if the previous version
# Was logged # Was logged

View file

@ -4,14 +4,14 @@ import getpass
import os import os
import random import random
import sys import sys
import datetime
import click import click
from flask.cli import FlaskGroup from flask.cli import FlaskGroup
from werkzeug.security import generate_password_hash
from ihatemoney.models import Project, db from ihatemoney.models import Project, db
from ihatemoney.run import create_app from ihatemoney.run import create_app
from ihatemoney.utils import create_jinja_env, generate_password_hash from ihatemoney.utils import create_jinja_env
@click.group(cls=FlaskGroup, create_app=create_app) @click.group(cls=FlaskGroup, create_app=create_app)
@ -33,14 +33,14 @@ def runserver(ctx):
ctx.forward(run) ctx.forward(run)
@cli.command(name="generate_password_hash") @click.command(name="generate_password_hash")
def password_hash(): def password_hash():
"""Get password from user and hash it without printing it in clear text.""" """Get password from user and hash it without printing it in clear text."""
password = getpass.getpass(prompt="Password: ") password = getpass.getpass(prompt="Password: ")
print(generate_password_hash(password)) print(generate_password_hash(password))
@cli.command() @click.command()
@click.argument( @click.argument(
"config_file", "config_file",
type=click.Choice( type=click.Choice(
@ -94,31 +94,5 @@ def delete_project(project_name):
db.session.commit() db.session.commit()
@cli.command()
@click.argument("print_emails", default=False)
@click.argument("bills", default=0) # default values will get total projects
@click.argument("days", default=73000) # approximately 200 years
def get_project_count(print_emails, bills, days):
"""Count projets with at least x bills and at less than x days old"""
projects = [
pr
for pr in Project.query.all()
if pr.get_bills().count() > bills
and pr.get_bills()[0].date
> datetime.date.today() - datetime.timedelta(days=days)
]
click.secho("Number of projects: " + str(len(projects)))
if print_emails:
emails = set([pr.contact_email for pr in projects])
emails_str = ", ".join(emails)
if len(emails) > 1:
click.secho("Contact emails: " + emails_str)
elif len(emails) == 1:
click.secho("Contact email: " + emails_str)
else:
click.secho("No contact emails found")
if __name__ == "__main__": if __name__ == "__main__":
cli() cli()

View file

@ -10,12 +10,6 @@ msgstr ""
msgid "Project name" msgid "Project name"
msgstr "" msgstr ""
msgid "Current private code"
msgstr ""
msgid "Enter existing private code to edit project"
msgstr ""
msgid "New private code" msgid "New private code"
msgstr "" msgstr ""
@ -37,12 +31,6 @@ msgstr ""
msgid "Setting a default currency enables currency conversion between bills" msgid "Setting a default currency enables currency conversion between bills"
msgstr "" msgstr ""
msgid "Unknown error"
msgstr ""
msgid "Invalid private code."
msgstr ""
msgid "" msgid ""
"This project cannot be set to 'no currency' because it contains bills in " "This project cannot be set to 'no currency' because it contains bills in "
"multiple currencies." "multiple currencies."
@ -78,6 +66,12 @@ msgstr ""
msgid "Enter private code to confirm deletion" msgid "Enter private code to confirm deletion"
msgstr "" msgstr ""
msgid "Unknown error"
msgstr ""
msgid "Invalid private code."
msgstr ""
msgid "Get in" msgid "Get in"
msgstr "" msgstr ""
@ -241,9 +235,6 @@ msgstr ""
msgid "Password successfully reset." msgid "Password successfully reset."
msgstr "" msgstr ""
msgid "Project settings have been changed successfully."
msgstr ""
msgid "Unable to parse CSV" msgid "Unable to parse CSV"
msgstr "" msgstr ""
@ -734,9 +725,6 @@ msgstr ""
msgid "Settings" msgid "Settings"
msgstr "" msgstr ""
msgid "RSS Feed"
msgstr ""
msgid "Other projects :" msgid "Other projects :"
msgstr "" msgstr ""
@ -759,7 +747,7 @@ msgstr ""
msgid "Documentation" msgid "Documentation"
msgstr "" msgstr ""
msgid "Administration Dashboard" msgid "Administation Dashboard"
msgstr "" msgstr ""
msgid "Legal information" msgid "Legal information"
@ -813,10 +801,13 @@ msgstr ""
msgid "Nothing to list yet." msgid "Nothing to list yet."
msgstr "" msgstr ""
msgid "Add your first bill" msgid "You probably want to"
msgstr "" msgstr ""
msgid "Add the first participant" msgid "add a bill"
msgstr ""
msgid "add participants"
msgstr "" msgstr ""
msgid "Password reminder" msgid "Password reminder"
@ -839,14 +830,21 @@ msgstr ""
msgid "Invite people to join this project" msgid "Invite people to join this project"
msgstr "" msgstr ""
msgid "Share an invitation link" msgid "Share Identifier & code"
msgstr "" msgstr ""
msgid "" msgid ""
"The easiest way to invite people is to give them the following invitation" "You can share the project identifier and the private code by any "
" link.<br />They will be able to access the project, manage participants," "communication means."
" add/edit/delete bills. However, they will not have access to important " msgstr ""
"settings such as changing the private code or deleting the whole project."
msgid "Identifier:"
msgstr ""
msgid "Share the Link"
msgstr ""
msgid "You can directly share the following link via your prefered medium"
msgstr "" msgstr ""
msgid "Scan QR code" msgid "Scan QR code"
@ -859,28 +857,10 @@ msgid "Send via Emails"
msgstr "" msgstr ""
msgid "" msgid ""
"Specify a list of email adresses (separated by comma) of people you want " "Specify a (comma separated) list of email adresses you want to notify "
"to notify about the creation of this project. We will send them an email " "about the\n"
"with the invitation link." " creation of this budget management project and we will "
msgstr "" "send them an email for you."
msgid "Share Identifier & code"
msgstr ""
msgid ""
"You can share the project identifier and the private code by any "
"communication means.<br />Anyone with the private code will have access "
"to the full project, including changing settings such as the private code"
" or project email address, or even deleting the whole project."
msgstr ""
msgid "Identifier:"
msgstr ""
msgid "Private code:"
msgstr ""
msgid "the private code was defined when you created the project"
msgstr "" msgstr ""
msgid "Who pays?" msgid "Who pays?"

View file

@ -1,34 +0,0 @@
"""new bill type attribute added
Revision ID: 7a9b38559992
Revises: 927ed575acbd
Create Date: 2022-12-10 17:25:38.387643
"""
# revision identifiers, used by Alembic.
revision = "7a9b38559992"
down_revision = "927ed575acbd"
from alembic import op
import sqlalchemy as sa
from ihatemoney.models import BillType
def upgrade():
billtype_enum = sa.Enum(BillType)
billtype_enum.create(op.get_bind(), checkfirst=True)
op.add_column(
"bill",
sa.Column("bill_type", billtype_enum, server_default=BillType.EXPENSE.name),
)
op.add_column("bill_version", sa.Column("bill_type", sa.UnicodeText()))
def downgrade():
op.drop_column("bill", "bill_type")
op.drop_column("bill_version", "bill_type")
billtype_enum = sa.Enum(BillType)
billtype_enum.drop(op.get_bind())

View file

@ -1,6 +1,5 @@
from collections import defaultdict from collections import defaultdict
import datetime import datetime
from enum import Enum
import itertools import itertools
from dateutil.parser import parse from dateutil.parser import parse
@ -19,10 +18,11 @@ from sqlalchemy import orm
from sqlalchemy.sql import func from sqlalchemy.sql import func
from sqlalchemy_continuum import make_versioned, version_class from sqlalchemy_continuum import make_versioned, version_class
from sqlalchemy_continuum.plugins import FlaskPlugin from sqlalchemy_continuum.plugins import FlaskPlugin
from werkzeug.security import generate_password_hash
from ihatemoney.currency_convertor import CurrencyConverter from ihatemoney.currency_convertor import CurrencyConverter
from ihatemoney.monkeypath_continuum import PatchedTransactionFactory from ihatemoney.monkeypath_continuum import PatchedTransactionFactory
from ihatemoney.utils import generate_password_hash, get_members, same_bill from ihatemoney.utils import get_members, same_bill
from ihatemoney.versioning import ( from ihatemoney.versioning import (
ConditionalVersioningManager, ConditionalVersioningManager,
LoggingMode, LoggingMode,
@ -51,16 +51,6 @@ make_versioned(
], ],
) )
class BillType(Enum):
EXPENSE = "Expense"
REIMBURSEMENT = "Reimbursement"
@classmethod
def choices(cls):
return [(choice.value, choice.value) for choice in cls]
db = SQLAlchemy() db = SQLAlchemy()
@ -123,33 +113,22 @@ class Project(db.Model):
- dict mapping each member to how much he/she should be paid by - dict mapping each member to how much he/she should be paid by
others (i.e. how much he/she has paid for bills) others (i.e. how much he/she has paid for bills)
balance spent paid
""" """
balances, should_pay, should_receive = (defaultdict(int) for time in (1, 2, 3)) balances, should_pay, should_receive = (defaultdict(int) for time in (1, 2, 3))
for bill in self.get_bills_unordered().all(): for bill in self.get_bills_unordered().all():
should_receive[bill.payer.id] += bill.converted_amount
total_weight = sum(ower.weight for ower in bill.owers) total_weight = sum(ower.weight for ower in bill.owers)
for ower in bill.owers:
if bill.bill_type == BillType.EXPENSE: should_pay[ower.id] += (
should_receive[bill.payer.id] += bill.converted_amount ower.weight * bill.converted_amount / total_weight
for ower in bill.owers: )
should_pay[ower.id] += (
ower.weight * bill.converted_amount / total_weight
)
if bill.bill_type == BillType.REIMBURSEMENT:
should_receive[bill.payer.id] += bill.converted_amount
for ower in bill.owers:
should_receive[ower.id] -= bill.converted_amount
for person in self.members: for person in self.members:
balance = should_receive[person.id] - should_pay[person.id] balance = should_receive[person.id] - should_pay[person.id]
balances[person.id] = balance balances[person.id] = balance
return ( return balances, should_pay, should_receive
balances,
should_pay,
should_receive,
)
@property @property
def balance(self): def balance(self):
@ -182,8 +161,7 @@ class Project(db.Model):
""" """
monthly = defaultdict(lambda: defaultdict(float)) monthly = defaultdict(lambda: defaultdict(float))
for bill in self.get_bills_unordered().all(): for bill in self.get_bills_unordered().all():
if bill.bill_type == BillType.EXPENSE: monthly[bill.date.year][bill.date.month] += bill.converted_amount
monthly[bill.date.year][bill.date.month] += bill.converted_amount
return monthly return monthly
@property @property
@ -209,6 +187,7 @@ class Project(db.Model):
) )
return pretty_transactions return pretty_transactions
# cache value for better performance
members = {person.id: person for person in self.members} members = {person.id: person for person in self.members}
settle_plan = settle(self.balance.items()) or [] settle_plan = settle(self.balance.items()) or []
@ -224,6 +203,22 @@ class Project(db.Model):
return prettify(transactions, pretty_output) return prettify(transactions, pretty_output)
def exactmatch(self, credit, debts):
"""Recursively try and find subsets of 'debts' whose sum is equal to credit"""
if not debts:
return None
if debts[0]["balance"] > credit:
return self.exactmatch(credit, debts[1:])
elif debts[0]["balance"] == credit:
return [debts[0]]
else:
match = self.exactmatch(credit - debts[0]["balance"], debts[1:])
if match:
match.append(debts[0])
else:
match = self.exactmatch(credit, debts[1:])
return match
def has_bills(self): def has_bills(self):
"""return if the project do have bills or not""" """return if the project do have bills or not"""
return self.get_bills_unordered().count() > 0 return self.get_bills_unordered().count() > 0
@ -342,7 +337,6 @@ class Project(db.Model):
pretty_bills.append( pretty_bills.append(
{ {
"what": bill.what, "what": bill.what,
"bill_type": bill.bill_type.value,
"amount": round(bill.amount, 2), "amount": round(bill.amount, 2),
"currency": bill.original_currency, "currency": bill.original_currency,
"date": str(bill.date), "date": str(bill.date),
@ -414,7 +408,6 @@ class Project(db.Model):
new_bill = Bill( new_bill = Bill(
amount=b["amount"], amount=b["amount"],
date=parse(b["date"]), date=parse(b["date"]),
bill_type=b["bill_type"],
external_link="", external_link="",
original_currency=b["currency"], original_currency=b["currency"],
owers=Person.query.get_by_names(b["owers"], self), owers=Person.query.get_by_names(b["owers"], self),
@ -460,8 +453,7 @@ class Project(db.Model):
"""Generate a timed and serialized JsonWebToken """Generate a timed and serialized JsonWebToken
:param token_type: Either "auth" for authentication (invalidated when project code changed), :param token_type: Either "auth" for authentication (invalidated when project code changed),
or "reset" for password reset (invalidated after expiration), or "reset" for password reset (invalidated after expiration)
or "feed" for project feeds (invalidated when project code changed)
""" """
if token_type == "reset": if token_type == "reset":
@ -484,10 +476,9 @@ class Project(db.Model):
:param token: Serialized TimedJsonWebToken :param token: Serialized TimedJsonWebToken
:param token_type: Either "auth" for authentication (invalidated when project code changed), :param token_type: Either "auth" for authentication (invalidated when project code changed),
or "reset" for password reset (invalidated after expiration), or "reset" for password reset (invalidated after expiration)
or "feed" for project feeds (invalidated when project code changed) :param project_id: Project ID. Used for token_type "auth" to use the password as serializer
:param project_id: Project ID. Used for token_type "auth" and "feed" to use the password secret key.
as serializer secret key.
:param max_age: Token expiration time (in seconds). Only used with token_type "reset" :param max_age: Token expiration time (in seconds). Only used with token_type "reset"
""" """
loads_kwargs = {} loads_kwargs = {}
@ -545,15 +536,14 @@ class Project(db.Model):
db.session.commit() db.session.commit()
operations = ( operations = (
("Georg", 200, ("Amina", "Georg", "Alice"), "Food shopping", "Expense"), ("Georg", 200, ("Amina", "Georg", "Alice"), "Food shopping"),
("Alice", 20, ("Amina", "Alice"), "Beer !", "Expense"), ("Alice", 20, ("Amina", "Alice"), "Beer !"),
("Amina", 50, ("Amina", "Alice", "Georg"), "AMAP", "Expense"), ("Amina", 50, ("Amina", "Alice", "Georg"), "AMAP"),
) )
for payer, amount, owers, what, bill_type in operations: for payer, amount, owers, what in operations:
db.session.add( db.session.add(
Bill( Bill(
amount=amount, amount=amount,
bill_type=bill_type,
original_currency=project.default_currency, original_currency=project.default_currency,
owers=[members[name] for name in owers], owers=[members[name] for name in owers],
payer_id=members[payer].id, payer_id=members[payer].id,
@ -686,7 +676,6 @@ class Bill(db.Model):
date = db.Column(db.Date, default=datetime.datetime.now) date = db.Column(db.Date, default=datetime.datetime.now)
creation_date = db.Column(db.Date, default=datetime.datetime.now) creation_date = db.Column(db.Date, default=datetime.datetime.now)
what = db.Column(db.UnicodeText) what = db.Column(db.UnicodeText)
bill_type = db.Column(db.Enum(BillType))
external_link = db.Column(db.UnicodeText) external_link = db.Column(db.UnicodeText)
original_currency = db.Column(db.String(3)) original_currency = db.Column(db.String(3))
@ -706,7 +695,6 @@ class Bill(db.Model):
payer_id: int = None, payer_id: int = None,
project_default_currency: str = "", project_default_currency: str = "",
what: str = "", what: str = "",
bill_type: str = "Expense",
): ):
super().__init__() super().__init__()
self.amount = amount self.amount = amount
@ -716,7 +704,6 @@ class Bill(db.Model):
self.owers = owers self.owers = owers
self.payer_id = payer_id self.payer_id = payer_id
self.what = what self.what = what
self.bill_type = BillType(bill_type)
self.converted_amount = self.currency_helper.exchange_currency( self.converted_amount = self.currency_helper.exchange_currency(
self.amount, self.original_currency, project_default_currency self.amount, self.original_currency, project_default_currency
) )
@ -731,7 +718,6 @@ class Bill(db.Model):
"date": self.date, "date": self.date,
"creation_date": self.creation_date, "creation_date": self.creation_date,
"what": self.what, "what": self.what,
"bill_type": self.bill_type.value,
"external_link": self.external_link, "external_link": self.external_link,
"original_currency": self.original_currency, "original_currency": self.original_currency,
"converted_amount": self.converted_amount, "converted_amount": self.converted_amount,

View file

@ -103,7 +103,7 @@ def validate_configuration(app):
if "MAIL_DEFAULT_SENDER" not in app.config: if "MAIL_DEFAULT_SENDER" not in app.config:
app.config["MAIL_DEFAULT_SENDER"] = default_settings.DEFAULT_MAIL_SENDER app.config["MAIL_DEFAULT_SENDER"] = default_settings.DEFAULT_MAIL_SENDER
if type(app.config["MAIL_DEFAULT_SENDER"]) is tuple: if type(app.config["MAIL_DEFAULT_SENDER"]) == tuple:
(name, address) = app.config["MAIL_DEFAULT_SENDER"] (name, address) = app.config["MAIL_DEFAULT_SENDER"]
app.config["MAIL_DEFAULT_SENDER"] = f"{name} <{address}>" app.config["MAIL_DEFAULT_SENDER"] = f"{name} <{address}>"
warnings.warn( warnings.warn(

View file

@ -424,15 +424,6 @@ footer .footer-left {
display: table-cell; display: table-cell;
} }
/* used to tuncate long urls */
.truncated {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 250px;
display: inline-block;
vertical-align: top;
}
.balance .balance-value { .balance .balance-value {
text-align: right; text-align: right;

View file

@ -100,7 +100,6 @@
</div> </div>
{{ input(form.default_currency) }} {{ input(form.default_currency) }}
{{ input(form.current_password) }}
<div class="actions"> <div class="actions">
<button class="btn btn-primary">{{ _("Save changes") }}</button> <button class="btn btn-primary">{{ _("Save changes") }}</button>
</div> </div>
@ -179,11 +178,7 @@
<a class="badge badge-secondary" href="#" id="selectnone" onclick="selectCheckboxes(false)">{{_("No one")}}</a> <a class="badge badge-secondary" href="#" id="selectnone" onclick="selectCheckboxes(false)">{{_("No one")}}</a>
</p> </p>
<div class="d-flex flex-column flex-wrap overflow-auto" style="max-height: 20em;"> <div class="d-flex flex-column flex-wrap overflow-auto" style="max-height: 20em;">
{% for choices in form.payed_for.iter_choices() | sort(attribute='1') %} {% for key, value, checked in form.payed_for.iter_choices() | sort(attribute='1') %}
{# Compatibility with wtforms<3.1 #}
{% set key = choices[0] %}
{% set value = choices[1] %}
{% set checked = choices[2] %}
<p class="form-check text-break" style="max-width: 50%;"> <p class="form-check text-break" style="max-width: 50%;">
<label for="payed_for-{{key}}" class="form-check-label"> <label for="payed_for-{{key}}" class="form-check-label">
<input name="payed_for" type="checkbox" {% if checked %}checked{% endif %} class="form-check-input" value="{{key}}" id="payed_for-{{key}}"/> <input name="payed_for" type="checkbox" {% if checked %}checked{% endif %} class="form-check-input" value="{{key}}" id="payed_for-{{key}}"/>
@ -200,7 +195,6 @@
{% if g.project.default_currency != "XXX" %} {% if g.project.default_currency != "XXX" %}
{{ input(form.original_currency, inline=True, class="form-control custom-select") }} {{ input(form.original_currency, inline=True, class="form-control custom-select") }}
{% endif %} {% endif %}
{{ input(form.bill_type, inline=True) }}
{{ input(form.external_link, inline=True) }} {{ input(form.external_link, inline=True) }}
</details> </details>
</fieldset> </fieldset>
@ -236,8 +230,7 @@
{{ input(form.weight) }} {{ input(form.weight) }}
</fieldset> </fieldset>
<div class="actions"> <div class="actions">
<button class="btn btn-secondary input-group-addon" type="submit">{{ _("Save") }}</button> {{ form.submit(class="btn btn-primary") }}
<a href="{{ url_for(".list_bills") }}" class="btn btn-outline-secondary"> {{_("Cancel") }} </a>
</div> </div>
{% endmacro %} {% endmacro %}

View file

@ -91,21 +91,6 @@
{% endif %} {% endif %}
{% endmacro %} {% endmacro %}
{% macro bill_details(details, before=False) %}
{% set owers_list_str=details.owers|localize_list|safe %}
<details class="small">
<summary>{% if before %} {{ _("Details of the bill (before the change)") }} {% else %} {{ _("Details of the bill") }} {% endif %}</summary>
{{ _("Date:") }} {{ details.date|em_surround }}.
{{ _("Payer:") }} {{ details.payer|em_surround }}.
{{ _("Amount:") }} {{ details.amount|currency(details.original_currency)|em_surround }}.
{{ _("Owers:") }} {{ owers_list_str }}.
{% if details.external_link %}
{{ _("External link:") }}
<a class="truncated" href="{{ details.external_link }}">{{ details.external_link }}</a>
{% endif %}
</details>
{% endmacro %}
{% block sidebar %} {% block sidebar %}
<div id="table_overflow"> <div id="table_overflow">
{{ balance_table(show_weight=False, show_header=True) }} {{ balance_table(show_weight=False, show_header=True) }}
@ -192,16 +177,13 @@
{% trans %}Project {{ name }} added{% endtrans %} {% trans %}Project {{ name }} added{% endtrans %}
{% elif event.object_type == "Bill" %} {% elif event.object_type == "Bill" %}
{% trans %}Bill {{ name }} added{% endtrans %} {% trans %}Bill {{ name }} added{% endtrans %}
{{ bill_details(event.bill_details) }}
{% elif event.object_type == "Person" %} {% elif event.object_type == "Person" %}
{% trans %}Participant {{ name }} added{% endtrans %} {% trans %}Participant {{ name }} added{% endtrans %}
{% endif %} {% endif %}
{% elif event.operation_type == OperationType.UPDATE %} {% elif event.operation_type == OperationType.UPDATE %}
{% if event.object_type == "Project" %} {% if event.object_type == "Project" %}
{% if event.prop_changed == "password" %} {% if event.prop_changed == "password" %}
{{ _("Project private code changed") }} {{ _("Project private code changed") }}
{% elif event.prop_changed == "logging_preference" %} {% elif event.prop_changed == "logging_preference" %}
{{ change_to_logging_preference(event) }} {{ change_to_logging_preference(event) }}
{% elif event.prop_changed == "name" %} {% elif event.prop_changed == "name" %}
@ -213,58 +195,51 @@
{% else %} {% else %}
{{ _("Project settings modified") }} {{ _("Project settings modified") }}
{% endif %} {% endif %}
{% elif event.object_type == "Bill" %} {% elif event.prop_changed == "activated" %}
{% if event.prop_changed == "what" %} {% if event.val_after == False %}
{% set new_description=event.val_after|em_surround %} {% trans %}Participant {{ name }} deactivated{% endtrans %}
{% trans %}Bill {{ name }} renamed to {{ new_description }}{% endtrans %}
{% elif event.prop_changed == "external_link" %}
{{ bill_property_change(event, _("External link"), None, "<a class='truncated' href='{link}' >{link}</a>".format(link=event.val_after | escape) | safe | em_surround) }}
{% elif event.prop_changed == "owers_added" %}
{{ owers_changed(event, True)}}
{% elif event.prop_changed == "owers_removed" %}
{{ owers_changed(event, False)}}
{% elif event.prop_changed == "payer" %}
{{ bill_property_change(event, _("Payer"))}}
{% elif event.prop_changed == "amount" %}
{{ bill_property_change(event, _("Amount")) }}
{% elif event.prop_changed == "date" %}
{{ bill_property_change(event, _("Date")) }}
{% elif event.prop_changed == "original_currency" %}
{{ bill_property_change(event, _("Currency")) }}
{% elif event.prop_changed == "converted_amount" %}
{{ bill_property_change(event, _("Amount in %(currency)s", currency=g.project.default_currency)) }}
{% else %} {% else %}
{% trans %}Bill {{ name }} modified{% endtrans %} {% trans %}Participant {{ name }} reactivated{% endtrans %}
{% endif %} {% endif %}
{{ bill_details(event.bill_details, before=True) }} {% elif event.prop_changed == "name" %}
{% elif event.object_type == "Person" %}
{% if event.prop_changed == "activated" %}
{% if event.val_after == False %}
{% trans %}Participant {{ name }} deactivated{% endtrans %}
{% else %}
{% trans %}Participant {{ name }} reactivated{% endtrans %}
{% endif %}
{% elif event.prop_changed == "name" %}
{% set new_name=event.val_after|em_surround %} {% set new_name=event.val_after|em_surround %}
{% trans %}Participant {{ name }} renamed to {{ new_name }}{% endtrans %} {% trans %}Participant {{ name }} renamed to {{ new_name }}{% endtrans %}
{% elif event.prop_changed == "weight" %} {% elif event.prop_changed == "what" %}
{% set old_weight=event.val_before|em_surround %} {% set new_description=event.val_after|em_surround %}
{% set new_weight=event.val_after|em_surround %} {% trans %}Bill {{ name }} renamed to {{ new_description }}{% endtrans %}
{% trans %}Participant {{ name }}: weight changed from {{ old_weight }} to {{ new_weight }}{% endtrans %} {% elif event.prop_changed == "weight" %}
{% else %} {% set old_weight=event.val_before|em_surround %}
{% set new_weight=event.val_after|em_surround %}
{% trans %}Participant {{ name }}: weight changed from {{ old_weight }} to {{ new_weight }}{% endtrans %}
{% elif event.prop_changed == "external_link" %}
{{ bill_property_change(event, _("External link"), None, "<a href='{link}' >{link}</a>".format(link=event.val_after | escape) | safe | em_surround) }}
{% elif event.prop_changed == "owers_added" %}
{{ owers_changed(event, True)}}
{% elif event.prop_changed == "owers_removed" %}
{{ owers_changed(event, False)}}
{% elif event.prop_changed == "payer" %}
{{ bill_property_change(event, _("Payer"))}}
{% elif event.prop_changed == "amount" %}
{{ bill_property_change(event, _("Amount")) }}
{% elif event.prop_changed == "date" %}
{{ bill_property_change(event, _("Date")) }}
{% elif event.prop_changed == "original_currency" %}
{{ bill_property_change(event, _("Currency")) }}
{% elif event.prop_changed == "converted_amount" %}
{{ bill_property_change(event, _("Amount in %(currency)s", currency=g.project.default_currency)) }}
{% else %}
{% if event.object_type == "Bill" %}
{% trans %}Bill {{ name }} modified{% endtrans %}
{% elif event.object_type == "Person" %}
{% trans %}Participant {{ name }} modified{% endtrans %} {% trans %}Participant {{ name }} modified{% endtrans %}
{% endif %} {% endif %}
{% endif %} {% endif %}
{% elif event.operation_type == OperationType.DELETE %} {% elif event.operation_type == OperationType.DELETE %}
{% if event.object_type == "Bill" %} {% if event.object_type == "Bill" %}
{% trans %}Bill {{ name }} removed{% endtrans %} {% trans %}Bill {{ name }} removed{% endtrans %}
{{ bill_details(event.bill_details) }}
{% elif event.object_type == "Person" %} {% elif event.object_type == "Person" %}
{% trans %}Participant {{ name }} removed{% endtrans %} {% trans %}Participant {{ name }} removed{% endtrans %}
{% endif %} {% endif %}
{% else %} {% else %}
{# Should be unreachable #} {# Should be unreachable #}
{% if event.object_type == "Project" %} {% if event.object_type == "Project" %}

View file

@ -20,7 +20,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<html class="h-100"> <html class="h-100">
<head> <head>
<title>{{ SITE_NAME }} — {{ _("Account manager") }}{% block title %}{% endblock %}</title> <title>{{ _("Account manager") }}{% block title %}{% endblock %}</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8"> <meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel=stylesheet type=text/css href="{{ url_for("static", filename='css/main.css') }}"> <link rel=stylesheet type=text/css href="{{ url_for("static", filename='css/main.css') }}">
@ -103,7 +103,6 @@
{% if g.project %} {% if g.project %}
<li><a class="dropdown-item" href="{{ url_for("main.history") }}">{{ _("History") }}</a></li> <li><a class="dropdown-item" href="{{ url_for("main.history") }}">{{ _("History") }}</a></li>
<li><a class="dropdown-item" href="{{ url_for("main.edit_project") }}">{{ _("Settings") }}</a></li> <li><a class="dropdown-item" href="{{ url_for("main.edit_project") }}">{{ _("Settings") }}</a></li>
<li><a class="dropdown-item" href="{{ url_for("main.feed", token=g.project.generate_token("feed")) }}">{{ _("RSS Feed") }}</a></li>
{% endif %} {% endif %}
{% if session['projects'] and not ((session['projects'] | length) == 1 and g.project and g.project.id in session['projects']) %} {% if session['projects'] and not ((session['projects'] | length) == 1 and g.project and g.project.id in session['projects']) %}
@ -119,14 +118,12 @@
{% if session['is_admin'] %} {% if session['is_admin'] %}
<li><a class="dropdown-item" href="{{ url_for("main.dashboard") }}">{{ _("Dashboard") }}</a></li> <li><a class="dropdown-item" href="{{ url_for("main.dashboard") }}">{{ _("Dashboard") }}</a></li>
{% endif %} {% endif %}
{% if g.logout_form %}
<li> <li>
<form action="{{ url_for("main.exit") }}" method="post"> <form action="{{ url_for("main.exit") }}" method="post">
{{ g.logout_form.hidden_tag() }} {{ g.logout_form.hidden_tag() }}
{{ g.logout_form.submit(class="dropdown-item") }} {{ g.logout_form.submit(class="dropdown-item") }}
</form> </form>
</li> </li>
{% endif %}
</ul> </ul>
</li> </li>
{% endif %} {% endif %}
@ -168,7 +165,7 @@
<i class="icon book">{{ static_include("images/book.svg") | safe }}</i> <i class="icon book">{{ static_include("images/book.svg") | safe }}</i>
</a> </a>
{% if g.show_admin_dashboard_link %} {% if g.show_admin_dashboard_link %}
<a target="_blank" rel="noopener" data-toggle="tooltip" data-placement="top" title="{{ _('Administration Dashboard') }}" href="{{ url_for('main.dashboard') }}"> <a target="_blank" rel="noopener" data-toggle="tooltip" data-placement="top" title="{{ _('Administation Dashboard') }}" href="{{ url_for('main.dashboard') }}">
<i class="icon admin">{{ static_include("images/cog.svg") | safe }}</i> <i class="icon admin">{{ static_include("images/cog.svg") | safe }}</i>
</a> </a>
{% endif %} {% endif %}

View file

@ -11,11 +11,6 @@
{% block js %} {% block js %}
{% if add_bill %} $('#new-bill > a').click(); {% endif %} {% if add_bill %} $('#new-bill > a').click(); {% endif %}
// focus on first field when adding a bill
$("#bill-form").on('shown.bs.modal', function(){
$(this).find('#what').focus();
});
// ask for confirmation before removing an user // ask for confirmation before removing an user
$('.action.delete').each(function(){ $('.action.delete').each(function(){
var link = $(this).find('button'); var link = $(this).find('button');
@ -49,9 +44,6 @@
{% endblock %} {% endblock %}
{% block head %}
<link href="{{ url_for(".feed", token=g.project.generate_token("feed")) }}" type="application/rss+xml" rel="alternate" title="{{ g.project.name }}" />
{% endblock %}
{% block sidebar %} {% block sidebar %}
<div class="sidebar_content"> <div class="sidebar_content">
@ -106,7 +98,7 @@
</ul> </ul>
{% endif %} {% endif %}
<span id="new-bill" class="ml-auto pb-2" {% if not g.project.members %} data-toggle="tooltip" title="{{_('You should start by adding participants')}}" {% endif %}> <span id="new-bill" class="ml-auto pb-2" {% if not g.project.members %} data-toggle="tooltip" title="{{_('You should start by adding participants')}}" {% endif %}>
<a href="{{ url_for('.add_bill') }}" class="btn btn-primary {% if not g.project.members %} disabled {% endif %}" data-toggle="modal" data-keyboard="true" data-target="#bill-form" autofocus> <a href="{{ url_for('.add_bill') }}" class="btn btn-primary {% if not g.project.members %} disabled {% endif %}" data-toggle="modal" data-keyboard="false" data-target="#bill-form">
<i class="icon icon-white before-text">{{ static_include("images/plus.svg") | safe }}</i> <i class="icon icon-white before-text">{{ static_include("images/plus.svg") | safe }}</i>
{{ _("Add a new bill") }} {{ _("Add a new bill") }}
</a> </a>
@ -169,14 +161,13 @@
<h3>{{ _('No bills')}}</h3> <h3>{{ _('No bills')}}</h3>
<p> <p>
{{ _("Nothing to list yet.")}}<br /> {{ _("Nothing to list yet.")}}<br />
{%- if g.project.members %} {{ _("You probably want to") }}
<a href="{{ url_for('.add_bill') }}" data-toggle="modal" data-target="#bill-form"> {%- if g.project.members %} <a href="{{ url_for('.add_bill') }}" data-toggle="modal" data-target="#bill-form">
{{- _("Add your first bill") -}} {{- _("add a bill") -}}
</a> </a> ?
{% else %} {% else %} <a href="{{ url_for('.add_member') }}">
<a href="{{ url_for('.add_member') }}"> {{- _('add participants') -}}
{{- _("Add the first participant") -}} </a> ?
</a>
{%- endif -%} {%- endif -%}
</p> </p>
</div> </div>

View file

@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:atom="http://www.w3.org/2005/Atom"
>
<channel>
<title>I Hate Money — {{ g.project.name }}</title>
<description>{% trans project_name=g.project.name %}Latest bills from {{ project_name }}{% endtrans %}</description>
<atom:link href="{{ url_for(".feed", token=g.project.generate_token("feed"), _external=True) }}" rel="self" type="application/rss+xml" />
<link>{{ url_for(".list_bills", _external=True) }}</link>
{% for (weights, bill) in bills.items -%}
<item>
<title>{{ bill.what }} - {{ bill.amount|currency(bill.original_currency) }}</title>
<guid isPermaLink="false">{{ bill.id }}</guid>
<dc:creator>{{ bill.payer }}</dc:creator>
{% if bill.external_link %}<link>{{ bill.external_link }}</link>{% endif -%}
<description>{{ bill.date|dateformat("long") }} - {{ bill.owers|join(', ', 'name') }} : {{ (bill.amount/weights)|currency(bill.original_currency) }}</description>
<pubDate>{{ bill.creation_date.strftime("%a, %d %b %Y %T") }} +0000</pubDate>
</item>
{% endfor -%}
</channel>
</rss>

View file

@ -7,10 +7,20 @@
<tbody> <tbody>
<tr> <tr>
<td> <td>
<h3>{{ _('Share an invitation link') }}</h3> <h3>{{ _('Share Identifier & code') }}</h3>
</td> </td>
<td> <td>
{{ _("The easiest way to invite people is to give them the following invitation link.<br />They will be able to access the project, manage participants, add/edit/delete bills. However, they will not have access to important settings such as changing the private code or deleting the whole project.") }}</br> {{ _("You can share the project identifier and the private code by any communication means.") }}
<br />
<strong>{{ _('Identifier:') }}</strong> <a href="{{ url_for("main.list_bills", project_id=g.project.id) }}">{{ g.project.id }}</a>
</td>
</tr>
<tr>
<td>
<h3>{{ _('Share the Link') }}</h3>
</td>
<td>
{{ _("You can directly share the following link via your prefered medium") }}</br>
<a href="{{ url_for(".join_project", _external=True, project_id=g.project.id, token=g.project.generate_token()) }}"> <a href="{{ url_for(".join_project", _external=True, project_id=g.project.id, token=g.project.generate_token()) }}">
{{ url_for(".join_project", _external=True, project_id=g.project.id, token=g.project.generate_token()) }} {{ url_for(".join_project", _external=True, project_id=g.project.id, token=g.project.generate_token()) }}
</a> </a>
@ -30,26 +40,14 @@
<h3>{{ _('Send via Emails') }}</h3> <h3>{{ _('Send via Emails') }}</h3>
</td> </td>
<td> <td>
<p>{{ _("Specify a list of email adresses (separated by comma) of people you want to notify about the creation of this project. We will send them an email with the invitation link.") }}</p> <p>{{ _("Specify a (comma separated) list of email adresses you want to notify about the
creation of this budget management project and we will send them an email for you.") }}</p>
{% include "display_errors.html" %} {% include "display_errors.html" %}
<form class="invites form-horizontal" method="post" accept-charset="utf-8"> <form class="invites form-horizontal" method="post" accept-charset="utf-8">
{{ forms.invites(form) }} {{ forms.invites(form) }}
</form> </form>
</td> </td>
</tr> </tr>
<tr>
<td>
<h3>{{ _('Share Identifier & code') }}</h3>
</td>
<td>
<p>{{ _("You can share the project identifier and the private code by any communication means.<br />Anyone with the private code will have access to the full project, including changing settings such as the private code or project email address, or even deleting the whole project.") }}</p>
<p>
<strong>{{ _('Identifier:') }}</strong> <a href="{{ url_for("main.list_bills", project_id=g.project.id) }}">{{ g.project.id }}</a>
<br />
<strong>{{ _('Private code:') }}</strong> {{ _('the private code was defined when you created the project') }}
</p>
</td>
</tr>
</tbody> </tbody>
</table> </table>

View file

@ -9,22 +9,13 @@
{% block content %} {% block content %}
<table id="bill_table" class="split_bills table table-striped"> <table id="bill_table" class="split_bills table table-striped">
<thead><tr><th>{{ _("Who pays?") }}</th><th>{{ _("To whom?") }}</th><th>{{ _("How much?") }}</th><th>{{ _("Settled?") }}</th></tr></thead> <thead><tr><th>{{ _("Who pays?") }}</th><th>{{ _("To whom?") }}</th><th>{{ _("How much?") }}</th></tr></thead>
<tbody> <tbody>
{% for bill in bills %} {% for bill in bills %}
<tr receiver={{bill.receiver.id}}> <tr receiver={{bill.receiver.id}}>
<td>{{ bill.ower }}</td> <td>{{ bill.ower }}</td>
<td>{{ bill.receiver }}</td> <td>{{ bill.receiver }}</td>
<td>{{ bill.amount|currency }}</td> <td>{{ bill.amount|currency }}</td>
<td>
<span id="settle-bill" class="ml-auto pb-2">
<a href="{{ url_for('.settle', amount = bill.amount, ower_id = bill.ower.id, payer_id = bill.receiver.id) }}" class="btn btn-primary">
<div data-toggle="tooltip" title='{{ _("Click here to record that the money transfer has been done") }}'>
{{ ("Settle") }}
</div>
</a>
</span>
</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>

View file

@ -11,7 +11,7 @@
</tr> </tr>
</thead> </thead>
{%- endif %} {%- endif %}
{%- for member in g.project.members | sort(attribute='name') if member.activated or balance[member.id]|round(2)|abs > 0.01 %} {%- for member in g.project.members | sort(attribute='name') if member.activated or balance[member.id]|round(2) != 0 %}
<tr id="bal-member-{{ member.id }}" action="{% if member.activated %}delete{% else %}reactivate{% endif %}"> <tr id="bal-member-{{ member.id }}" action="{% if member.activated %}delete{% else %}reactivate{% endif %}">
<td class="balance-name">{{ member.name }} <td class="balance-name">{{ member.name }}
{%- if show_weight -%} {%- if show_weight -%}

View file

@ -1,14 +1,14 @@
import base64 import base64
import datetime import datetime
import json import json
import unittest
import pytest
from ihatemoney.tests.common.help_functions import em_surround from ihatemoney.tests.common.help_functions import em_surround
from ihatemoney.tests.common.ihatemoney_testcase import IhatemoneyTestCase from ihatemoney.tests.common.ihatemoney_testcase import IhatemoneyTestCase
class TestAPI(IhatemoneyTestCase): class APITestCase(IhatemoneyTestCase):
"""Tests the API""" """Tests the API"""
def api_create( def api_create(
@ -42,7 +42,7 @@ class TestAPI(IhatemoneyTestCase):
def get_auth(self, username, password=None): def get_auth(self, username, password=None):
password = password or username password = password or username
base64string = ( base64string = (
base64.encodebytes(f"{username}:{password}".encode("utf-8")) # noqa: E231 base64.encodebytes(f"{username}:{password}".encode("utf-8"))
.decode("utf-8") .decode("utf-8")
.replace("\n", "") .replace("\n", "")
) )
@ -57,7 +57,7 @@ class TestAPI(IhatemoneyTestCase):
resp = self.client.options( resp = self.client.options(
"/api/projects/raclette", headers=self.get_auth("raclette") "/api/projects/raclette", headers=self.get_auth("raclette")
) )
assert resp.headers["Access-Control-Allow-Origin"] == "*" self.assertEqual(resp.headers["Access-Control-Allow-Origin"], "*")
def test_basic_auth(self): def test_basic_auth(self):
# create a project # create a project
@ -94,32 +94,32 @@ class TestAPI(IhatemoneyTestCase):
}, },
) )
assert 400 == resp.status_code self.assertTrue(400, resp.status_code)
assert '{"contact_email": ["Invalid email address."]}\n' == resp.data.decode( self.assertEqual(
"utf-8" '{"contact_email": ["Invalid email address."]}\n', resp.data.decode("utf-8")
) )
# create it # create it
with self.app.mail.record_messages() as outbox: with self.app.mail.record_messages() as outbox:
resp = self.api_create("raclette") resp = self.api_create("raclette")
assert 201 == resp.status_code self.assertTrue(201, resp.status_code)
# Check that email messages have been sent. # Check that email messages have been sent.
assert len(outbox) == 1 self.assertEqual(len(outbox), 1)
assert outbox[0].recipients == ["raclette@notmyidea.org"] self.assertEqual(outbox[0].recipients, ["raclette@notmyidea.org"])
# create it twice should return a 400 # create it twice should return a 400
resp = self.api_create("raclette") resp = self.api_create("raclette")
assert 400 == resp.status_code self.assertTrue(400, resp.status_code)
assert "id" in json.loads(resp.data.decode("utf-8")) self.assertIn("id", json.loads(resp.data.decode("utf-8")))
# get information about it # get information about it
resp = self.client.get( resp = self.client.get(
"/api/projects/raclette", headers=self.get_auth("raclette") "/api/projects/raclette", headers=self.get_auth("raclette")
) )
assert 200 == resp.status_code self.assertTrue(200, resp.status_code)
expected = { expected = {
"members": [], "members": [],
"name": "raclette", "name": "raclette",
@ -129,9 +129,9 @@ class TestAPI(IhatemoneyTestCase):
"logging_preference": 1, "logging_preference": 1,
} }
decoded_resp = json.loads(resp.data.decode("utf-8")) decoded_resp = json.loads(resp.data.decode("utf-8"))
assert decoded_resp == expected self.assertDictEqual(decoded_resp, expected)
# edit should fail if we don't provide the current private code # edit should work
resp = self.client.put( resp = self.client.put(
"/api/projects/raclette", "/api/projects/raclette",
data={ data={
@ -143,43 +143,14 @@ class TestAPI(IhatemoneyTestCase):
}, },
headers=self.get_auth("raclette"), headers=self.get_auth("raclette"),
) )
assert 400 == resp.status_code
# edit should fail if we provide the wrong private code self.assertEqual(200, resp.status_code)
resp = self.client.put(
"/api/projects/raclette",
data={
"contact_email": "yeah@notmyidea.org",
"default_currency": "XXX",
"current_password": "fromage aux patates",
"password": "raclette",
"name": "The raclette party",
"project_history": "y",
},
headers=self.get_auth("raclette"),
)
assert 400 == resp.status_code
# edit with the correct private code should work
resp = self.client.put(
"/api/projects/raclette",
data={
"contact_email": "yeah@notmyidea.org",
"default_currency": "XXX",
"current_password": "raclette",
"password": "raclette",
"name": "The raclette party",
"project_history": "y",
},
headers=self.get_auth("raclette"),
)
assert 200 == resp.status_code
resp = self.client.get( resp = self.client.get(
"/api/projects/raclette", headers=self.get_auth("raclette") "/api/projects/raclette", headers=self.get_auth("raclette")
) )
assert 200 == resp.status_code self.assertEqual(200, resp.status_code)
expected = { expected = {
"name": "The raclette party", "name": "The raclette party",
"contact_email": "yeah@notmyidea.org", "contact_email": "yeah@notmyidea.org",
@ -189,7 +160,7 @@ class TestAPI(IhatemoneyTestCase):
"logging_preference": 1, "logging_preference": 1,
} }
decoded_resp = json.loads(resp.data.decode("utf-8")) decoded_resp = json.loads(resp.data.decode("utf-8"))
assert decoded_resp == expected self.assertDictEqual(decoded_resp, expected)
# password change is possible via API # password change is possible via API
resp = self.client.put( resp = self.client.put(
@ -197,19 +168,18 @@ class TestAPI(IhatemoneyTestCase):
data={ data={
"contact_email": "yeah@notmyidea.org", "contact_email": "yeah@notmyidea.org",
"default_currency": "XXX", "default_currency": "XXX",
"current_password": "raclette",
"password": "tartiflette", "password": "tartiflette",
"name": "The raclette party", "name": "The raclette party",
}, },
headers=self.get_auth("raclette"), headers=self.get_auth("raclette"),
) )
assert 200 == resp.status_code self.assertEqual(200, resp.status_code)
resp = self.client.get( resp = self.client.get(
"/api/projects/raclette", headers=self.get_auth("raclette", "tartiflette") "/api/projects/raclette", headers=self.get_auth("raclette", "tartiflette")
) )
assert 200 == resp.status_code self.assertEqual(200, resp.status_code)
# delete should work # delete should work
resp = self.client.delete( resp = self.client.delete(
@ -220,21 +190,21 @@ class TestAPI(IhatemoneyTestCase):
resp = self.client.get( resp = self.client.get(
"/api/projects/raclette", headers=self.get_auth("raclette") "/api/projects/raclette", headers=self.get_auth("raclette")
) )
assert 401 == resp.status_code self.assertEqual(401, resp.status_code)
def test_token_creation(self): def test_token_creation(self):
"""Test that token of project is generated""" """Test that token of project is generated"""
# Create project # Create project
resp = self.api_create("raclette") resp = self.api_create("raclette")
assert 201 == resp.status_code self.assertTrue(201, resp.status_code)
# Get token # Get token
resp = self.client.get( resp = self.client.get(
"/api/projects/raclette/token", headers=self.get_auth("raclette") "/api/projects/raclette/token", headers=self.get_auth("raclette")
) )
assert 200 == resp.status_code self.assertEqual(200, resp.status_code)
decoded_resp = json.loads(resp.data.decode("utf-8")) decoded_resp = json.loads(resp.data.decode("utf-8"))
@ -243,22 +213,8 @@ class TestAPI(IhatemoneyTestCase):
"/api/projects/raclette/token", "/api/projects/raclette/token",
headers={"Authorization": f"Basic {decoded_resp['token']}"}, headers={"Authorization": f"Basic {decoded_resp['token']}"},
) )
assert 200 == resp.status_code
# We shouldn't be able to edit project without private code self.assertEqual(200, resp.status_code)
resp = self.client.put(
"/api/projects/raclette",
data={
"contact_email": "yeah@notmyidea.org",
"default_currency": "XXX",
"password": "tartiflette",
"name": "The raclette party",
},
headers={"Authorization": f"Basic {decoded_resp['token']}"},
)
assert 400 == resp.status_code
expected_resp = {"current_password": ["This field is required."]}
assert expected_resp == json.loads(resp.data.decode("utf-8"))
def test_token_login(self): def test_token_login(self):
resp = self.api_create("raclette") resp = self.api_create("raclette")
@ -269,7 +225,7 @@ class TestAPI(IhatemoneyTestCase):
decoded_resp = json.loads(resp.data.decode("utf-8")) decoded_resp = json.loads(resp.data.decode("utf-8"))
resp = self.client.get(f"/raclette/join/{decoded_resp['token']}") resp = self.client.get(f"/raclette/join/{decoded_resp['token']}")
# Test that we are redirected. # Test that we are redirected.
assert 302 == resp.status_code self.assertEqual(302, resp.status_code)
def test_member(self): def test_member(self):
# create a project # create a project
@ -281,7 +237,7 @@ class TestAPI(IhatemoneyTestCase):
) )
self.assertStatus(200, req) self.assertStatus(200, req)
assert "[]\n" == req.data.decode("utf-8") self.assertEqual("[]\n", req.data.decode("utf-8"))
# add a member # add a member
req = self.client.post( req = self.client.post(
@ -292,7 +248,7 @@ class TestAPI(IhatemoneyTestCase):
# the id of the new member should be returned # the id of the new member should be returned
self.assertStatus(201, req) self.assertStatus(201, req)
assert "1\n" == req.data.decode("utf-8") self.assertEqual("1\n", req.data.decode("utf-8"))
# the list of participants should contain one member # the list of participants should contain one member
req = self.client.get( req = self.client.get(
@ -300,7 +256,7 @@ class TestAPI(IhatemoneyTestCase):
) )
self.assertStatus(200, req) self.assertStatus(200, req)
assert len(json.loads(req.data.decode("utf-8"))) == 1 self.assertEqual(len(json.loads(req.data.decode("utf-8"))), 1)
# Try to add another member with the same name. # Try to add another member with the same name.
req = self.client.post( req = self.client.post(
@ -313,7 +269,7 @@ class TestAPI(IhatemoneyTestCase):
# edit the participant # edit the participant
req = self.client.put( req = self.client.put(
"/api/projects/raclette/members/1", "/api/projects/raclette/members/1",
data={"name": "Jeanne", "weight": 2}, data={"name": "Fred", "weight": 2},
headers=self.get_auth("raclette"), headers=self.get_auth("raclette"),
) )
@ -325,14 +281,14 @@ class TestAPI(IhatemoneyTestCase):
) )
self.assertStatus(200, req) self.assertStatus(200, req)
assert "Jeanne" == json.loads(req.data.decode("utf-8"))["name"] self.assertEqual("Fred", json.loads(req.data.decode("utf-8"))["name"])
assert 2 == json.loads(req.data.decode("utf-8"))["weight"] self.assertEqual(2, json.loads(req.data.decode("utf-8"))["weight"])
# edit this member with same information # edit this member with same information
# (test PUT idempotence) # (test PUT idemopotence)
req = self.client.put( req = self.client.put(
"/api/projects/raclette/members/1", "/api/projects/raclette/members/1",
data={"name": "Jeanne"}, data={"name": "Fred"},
headers=self.get_auth("raclette"), headers=self.get_auth("raclette"),
) )
@ -341,7 +297,7 @@ class TestAPI(IhatemoneyTestCase):
# de-activate the participant # de-activate the participant
req = self.client.put( req = self.client.put(
"/api/projects/raclette/members/1", "/api/projects/raclette/members/1",
data={"name": "Jeanne", "activated": False}, data={"name": "Fred", "activated": False},
headers=self.get_auth("raclette"), headers=self.get_auth("raclette"),
) )
self.assertStatus(200, req) self.assertStatus(200, req)
@ -350,12 +306,12 @@ class TestAPI(IhatemoneyTestCase):
"/api/projects/raclette/members/1", headers=self.get_auth("raclette") "/api/projects/raclette/members/1", headers=self.get_auth("raclette")
) )
self.assertStatus(200, req) self.assertStatus(200, req)
assert not json.loads(req.data.decode("utf-8"))["activated"] self.assertEqual(False, json.loads(req.data.decode("utf-8"))["activated"])
# re-activate the participant # re-activate the participant
req = self.client.put( req = self.client.put(
"/api/projects/raclette/members/1", "/api/projects/raclette/members/1",
data={"name": "Jeanne", "activated": True}, data={"name": "Fred", "activated": True},
headers=self.get_auth("raclette"), headers=self.get_auth("raclette"),
) )
@ -363,7 +319,7 @@ class TestAPI(IhatemoneyTestCase):
"/api/projects/raclette/members/1", headers=self.get_auth("raclette") "/api/projects/raclette/members/1", headers=self.get_auth("raclette")
) )
self.assertStatus(200, req) self.assertStatus(200, req)
assert json.loads(req.data.decode("utf-8"))["activated"] self.assertEqual(True, json.loads(req.data.decode("utf-8"))["activated"])
# delete a member # delete a member
@ -379,7 +335,7 @@ class TestAPI(IhatemoneyTestCase):
) )
self.assertStatus(200, req) self.assertStatus(200, req)
assert "[]\n" == req.data.decode("utf-8") self.assertEqual("[]\n", req.data.decode("utf-8"))
def test_bills(self): def test_bills(self):
# create a project # create a project
@ -387,7 +343,7 @@ class TestAPI(IhatemoneyTestCase):
# add participants # add participants
self.api_add_member("raclette", "zorglub") self.api_add_member("raclette", "zorglub")
self.api_add_member("raclette", "jeanne") self.api_add_member("raclette", "fred")
self.api_add_member("raclette", "quentin") self.api_add_member("raclette", "quentin")
# get the list of bills (should be empty) # get the list of bills (should be empty)
@ -396,7 +352,7 @@ class TestAPI(IhatemoneyTestCase):
) )
self.assertStatus(200, req) self.assertStatus(200, req)
assert "[]\n" == req.data.decode("utf-8") self.assertEqual("[]\n", req.data.decode("utf-8"))
# add a bill # add a bill
req = self.client.post( req = self.client.post(
@ -406,7 +362,6 @@ class TestAPI(IhatemoneyTestCase):
"what": "fromage", "what": "fromage",
"payer": "1", "payer": "1",
"payed_for": ["1", "2"], "payed_for": ["1", "2"],
"bill_type": "Expense",
"amount": "25", "amount": "25",
"external_link": "https://raclette.fr", "external_link": "https://raclette.fr",
}, },
@ -415,7 +370,7 @@ class TestAPI(IhatemoneyTestCase):
# should return the id # should return the id
self.assertStatus(201, req) self.assertStatus(201, req)
assert req.data.decode("utf-8") == "1\n" self.assertEqual(req.data.decode("utf-8"), "1\n")
# get this bill details # get this bill details
req = self.client.get( req = self.client.get(
@ -429,9 +384,8 @@ class TestAPI(IhatemoneyTestCase):
"payer_id": 1, "payer_id": 1,
"owers": [ "owers": [
{"activated": True, "id": 1, "name": "zorglub", "weight": 1}, {"activated": True, "id": 1, "name": "zorglub", "weight": 1},
{"activated": True, "id": 2, "name": "jeanne", "weight": 1}, {"activated": True, "id": 2, "name": "fred", "weight": 1},
], ],
"bill_type": "Expense",
"amount": 25.0, "amount": 25.0,
"date": "2011-08-10", "date": "2011-08-10",
"id": 1, "id": 1,
@ -441,19 +395,19 @@ class TestAPI(IhatemoneyTestCase):
} }
got = json.loads(req.data.decode("utf-8")) got = json.loads(req.data.decode("utf-8"))
assert ( self.assertEqual(
datetime.date.today() datetime.date.today(),
== datetime.datetime.strptime(got["creation_date"], "%Y-%m-%d").date() datetime.datetime.strptime(got["creation_date"], "%Y-%m-%d").date(),
) )
del got["creation_date"] del got["creation_date"]
assert expected == got self.assertDictEqual(expected, got)
# the list of bills should length 1 # the list of bills should length 1
req = self.client.get( req = self.client.get(
"/api/projects/raclette/bills", headers=self.get_auth("raclette") "/api/projects/raclette/bills", headers=self.get_auth("raclette")
) )
self.assertStatus(200, req) self.assertStatus(200, req)
assert 1 == len(json.loads(req.data.decode("utf-8"))) self.assertEqual(1, len(json.loads(req.data.decode("utf-8"))))
# edit with errors should return an error # edit with errors should return an error
req = self.client.put( req = self.client.put(
@ -463,7 +417,6 @@ class TestAPI(IhatemoneyTestCase):
"what": "fromage", "what": "fromage",
"payer": "1", "payer": "1",
"payed_for": ["1", "2"], "payed_for": ["1", "2"],
"bill_type": "Expense",
"amount": "25", "amount": "25",
"external_link": "https://raclette.fr", "external_link": "https://raclette.fr",
}, },
@ -471,7 +424,9 @@ class TestAPI(IhatemoneyTestCase):
) )
self.assertStatus(400, req) self.assertStatus(400, req)
assert '{"date": ["This field is required."]}\n' == req.data.decode("utf-8") self.assertEqual(
'{"date": ["This field is required."]}\n', req.data.decode("utf-8")
)
# edit a bill # edit a bill
req = self.client.put( req = self.client.put(
@ -481,7 +436,6 @@ class TestAPI(IhatemoneyTestCase):
"what": "beer", "what": "beer",
"payer": "2", "payer": "2",
"payed_for": ["1", "2"], "payed_for": ["1", "2"],
"bill_type": "Expense",
"amount": "25", "amount": "25",
"external_link": "https://raclette.fr", "external_link": "https://raclette.fr",
}, },
@ -501,9 +455,8 @@ class TestAPI(IhatemoneyTestCase):
"payer_id": 2, "payer_id": 2,
"owers": [ "owers": [
{"activated": True, "id": 1, "name": "zorglub", "weight": 1}, {"activated": True, "id": 1, "name": "zorglub", "weight": 1},
{"activated": True, "id": 2, "name": "jeanne", "weight": 1}, {"activated": True, "id": 2, "name": "fred", "weight": 1},
], ],
"bill_type": "Expense",
"amount": 25.0, "amount": 25.0,
"date": "2011-09-10", "date": "2011-09-10",
"external_link": "https://raclette.fr", "external_link": "https://raclette.fr",
@ -513,12 +466,12 @@ class TestAPI(IhatemoneyTestCase):
} }
got = json.loads(req.data.decode("utf-8")) got = json.loads(req.data.decode("utf-8"))
assert ( self.assertEqual(
creation_date creation_date,
== datetime.datetime.strptime(got["creation_date"], "%Y-%m-%d").date() datetime.datetime.strptime(got["creation_date"], "%Y-%m-%d").date(),
) )
del got["creation_date"] del got["creation_date"]
assert expected == got self.assertDictEqual(expected, got)
# delete a bill # delete a bill
req = self.client.delete( req = self.client.delete(
@ -538,7 +491,7 @@ class TestAPI(IhatemoneyTestCase):
# add participants # add participants
self.api_add_member("raclette", "zorglub") self.api_add_member("raclette", "zorglub")
self.api_add_member("raclette", "jeanne") self.api_add_member("raclette", "fred")
# valid amounts # valid amounts
input_expected = [ input_expected = [
@ -558,7 +511,6 @@ class TestAPI(IhatemoneyTestCase):
"what": "fromage", "what": "fromage",
"payer": "1", "payer": "1",
"payed_for": ["1", "2"], "payed_for": ["1", "2"],
"bill_type": "Expense",
"amount": input_amount, "amount": input_amount,
}, },
headers=self.get_auth("raclette"), headers=self.get_auth("raclette"),
@ -566,7 +518,7 @@ class TestAPI(IhatemoneyTestCase):
# should return the id # should return the id
self.assertStatus(201, req) self.assertStatus(201, req)
assert req.data.decode("utf-8") == "{}\n".format(id) self.assertEqual(req.data.decode("utf-8"), "{}\n".format(id))
# get this bill's details # get this bill's details
req = self.client.get( req = self.client.get(
@ -581,9 +533,8 @@ class TestAPI(IhatemoneyTestCase):
"payer_id": 1, "payer_id": 1,
"owers": [ "owers": [
{"activated": True, "id": 1, "name": "zorglub", "weight": 1}, {"activated": True, "id": 1, "name": "zorglub", "weight": 1},
{"activated": True, "id": 2, "name": "jeanne", "weight": 1}, {"activated": True, "id": 2, "name": "fred", "weight": 1},
], ],
"bill_type": "Expense",
"amount": expected_amount, "amount": expected_amount,
"date": "2011-08-10", "date": "2011-08-10",
"id": id, "id": id,
@ -593,12 +544,12 @@ class TestAPI(IhatemoneyTestCase):
} }
got = json.loads(req.data.decode("utf-8")) got = json.loads(req.data.decode("utf-8"))
assert ( self.assertEqual(
datetime.date.today() datetime.date.today(),
== datetime.datetime.strptime(got["creation_date"], "%Y-%m-%d").date() datetime.datetime.strptime(got["creation_date"], "%Y-%m-%d").date(),
) )
del got["creation_date"] del got["creation_date"]
assert expected == got self.assertDictEqual(expected, got)
# should raise errors # should raise errors
erroneous_amounts = [ erroneous_amounts = [
@ -617,30 +568,28 @@ class TestAPI(IhatemoneyTestCase):
"what": "fromage", "what": "fromage",
"payer": "1", "payer": "1",
"payed_for": ["1", "2"], "payed_for": ["1", "2"],
"bill_type": "Expense",
"amount": amount, "amount": amount,
}, },
headers=self.get_auth("raclette"), headers=self.get_auth("raclette"),
) )
self.assertStatus(400, req) self.assertStatus(400, req)
@pytest.mark.skip(reason="Currency conversion is broken")
def test_currencies(self): def test_currencies(self):
# check /currencies for list of supported currencies # check /currencies for list of supported currencies
resp = self.client.get("/api/currencies") resp = self.client.get("/api/currencies")
assert 200 == resp.status_code self.assertTrue(201, resp.status_code)
assert "XXX" in json.loads(resp.data.decode("utf-8")) self.assertIn("XXX", json.loads(resp.data.decode("utf-8")))
# create project with a default currency # create project with a default currency
resp = self.api_create("raclette", default_currency="EUR") resp = self.api_create("raclette", default_currency="EUR")
assert 201 == resp.status_code self.assertTrue(201, resp.status_code)
# get information about it # get information about it
resp = self.client.get( resp = self.client.get(
"/api/projects/raclette", headers=self.get_auth("raclette") "/api/projects/raclette", headers=self.get_auth("raclette")
) )
assert 200 == resp.status_code self.assertTrue(200, resp.status_code)
expected = { expected = {
"members": [], "members": [],
"name": "raclette", "name": "raclette",
@ -650,11 +599,11 @@ class TestAPI(IhatemoneyTestCase):
"logging_preference": 1, "logging_preference": 1,
} }
decoded_resp = json.loads(resp.data.decode("utf-8")) decoded_resp = json.loads(resp.data.decode("utf-8"))
assert decoded_resp == expected self.assertDictEqual(decoded_resp, expected)
# Add participants # Add participants
self.api_add_member("raclette", "zorglub") self.api_add_member("raclette", "zorglub")
self.api_add_member("raclette", "jeanne") self.api_add_member("raclette", "fred")
self.api_add_member("raclette", "quentin") self.api_add_member("raclette", "quentin")
# Add a bill without explicit currency # Add a bill without explicit currency
@ -665,7 +614,6 @@ class TestAPI(IhatemoneyTestCase):
"what": "fromage", "what": "fromage",
"payer": "1", "payer": "1",
"payed_for": ["1", "2"], "payed_for": ["1", "2"],
"bill_type": "Expense",
"amount": "25", "amount": "25",
"external_link": "https://raclette.fr", "external_link": "https://raclette.fr",
}, },
@ -674,7 +622,7 @@ class TestAPI(IhatemoneyTestCase):
# should return the id # should return the id
self.assertStatus(201, req) self.assertStatus(201, req)
assert req.data.decode("utf-8") == "1\n" self.assertEqual(req.data.decode("utf-8"), "1\n")
# get this bill details # get this bill details
req = self.client.get( req = self.client.get(
@ -688,9 +636,8 @@ class TestAPI(IhatemoneyTestCase):
"payer_id": 1, "payer_id": 1,
"owers": [ "owers": [
{"activated": True, "id": 1, "name": "zorglub", "weight": 1}, {"activated": True, "id": 1, "name": "zorglub", "weight": 1},
{"activated": True, "id": 2, "name": "jeanne", "weight": 1}, {"activated": True, "id": 2, "name": "fred", "weight": 1},
], ],
"bill_type": "Expense",
"amount": 25.0, "amount": 25.0,
"date": "2011-08-10", "date": "2011-08-10",
"id": 1, "id": 1,
@ -700,12 +647,12 @@ class TestAPI(IhatemoneyTestCase):
} }
got = json.loads(req.data.decode("utf-8")) got = json.loads(req.data.decode("utf-8"))
assert ( self.assertEqual(
datetime.date.today() datetime.date.today(),
== datetime.datetime.strptime(got["creation_date"], "%Y-%m-%d").date() datetime.datetime.strptime(got["creation_date"], "%Y-%m-%d").date(),
) )
del got["creation_date"] del got["creation_date"]
assert expected == got self.assertDictEqual(expected, got)
# Change bill amount and currency # Change bill amount and currency
req = self.client.put( req = self.client.put(
@ -715,7 +662,6 @@ class TestAPI(IhatemoneyTestCase):
"what": "fromage", "what": "fromage",
"payer": "1", "payer": "1",
"payed_for": ["1", "2"], "payed_for": ["1", "2"],
"bill_type": "Expense",
"amount": "30", "amount": "30",
"external_link": "https://raclette.fr", "external_link": "https://raclette.fr",
"original_currency": "CAD", "original_currency": "CAD",
@ -735,9 +681,8 @@ class TestAPI(IhatemoneyTestCase):
"payer_id": 1, "payer_id": 1,
"owers": [ "owers": [
{"activated": True, "id": 1, "name": "zorglub", "weight": 1.0}, {"activated": True, "id": 1, "name": "zorglub", "weight": 1.0},
{"activated": True, "id": 2, "name": "jeanne", "weight": 1.0}, {"activated": True, "id": 2, "name": "fred", "weight": 1.0},
], ],
"bill_type": "Expense",
"amount": 30.0, "amount": 30.0,
"date": "2011-08-10", "date": "2011-08-10",
"id": 1, "id": 1,
@ -748,7 +693,7 @@ class TestAPI(IhatemoneyTestCase):
got = json.loads(req.data.decode("utf-8")) got = json.loads(req.data.decode("utf-8"))
del got["creation_date"] del got["creation_date"]
assert expected == got self.assertDictEqual(expected, got)
# Add a bill with yet another currency # Add a bill with yet another currency
req = self.client.post( req = self.client.post(
@ -758,7 +703,6 @@ class TestAPI(IhatemoneyTestCase):
"what": "Pierogi", "what": "Pierogi",
"payer": "1", "payer": "1",
"payed_for": ["2", "3"], "payed_for": ["2", "3"],
"bill_type": "Expense",
"amount": "80", "amount": "80",
"original_currency": "PLN", "original_currency": "PLN",
}, },
@ -767,7 +711,7 @@ class TestAPI(IhatemoneyTestCase):
# should return the id # should return the id
self.assertStatus(201, req) self.assertStatus(201, req)
assert req.data.decode("utf-8") == "2\n" self.assertEqual(req.data.decode("utf-8"), "2\n")
# Try to remove default project currency, it should fail # Try to remove default project currency, it should fail
req = self.client.put( req = self.client.put(
@ -775,16 +719,15 @@ class TestAPI(IhatemoneyTestCase):
data={ data={
"contact_email": "yeah@notmyidea.org", "contact_email": "yeah@notmyidea.org",
"default_currency": "XXX", "default_currency": "XXX",
"current_password": "raclette",
"password": "raclette", "password": "raclette",
"name": "The raclette party", "name": "The raclette party",
}, },
headers=self.get_auth("raclette"), headers=self.get_auth("raclette"),
) )
self.assertStatus(400, req) self.assertStatus(400, req)
assert "This project cannot be set" in req.data.decode("utf-8") self.assertIn("This project cannot be set", req.data.decode("utf-8"))
assert "because it contains bills in multiple currencies" in req.data.decode( self.assertIn(
"utf-8" "because it contains bills in multiple currencies", req.data.decode("utf-8")
) )
def test_statistics(self): def test_statistics(self):
@ -793,7 +736,7 @@ class TestAPI(IhatemoneyTestCase):
# add participants # add participants
self.api_add_member("raclette", "zorglub") self.api_add_member("raclette", "zorglub")
self.api_add_member("raclette", "jeanne") self.api_add_member("raclette", "fred")
# add a bill # add a bill
req = self.client.post( req = self.client.post(
@ -803,7 +746,6 @@ class TestAPI(IhatemoneyTestCase):
"what": "fromage", "what": "fromage",
"payer": "1", "payer": "1",
"payed_for": ["1", "2"], "payed_for": ["1", "2"],
"bill_type": "Expense",
"amount": "25", "amount": "25",
}, },
headers=self.get_auth("raclette"), headers=self.get_auth("raclette"),
@ -814,30 +756,33 @@ class TestAPI(IhatemoneyTestCase):
"/api/projects/raclette/statistics", headers=self.get_auth("raclette") "/api/projects/raclette/statistics", headers=self.get_auth("raclette")
) )
self.assertStatus(200, req) self.assertStatus(200, req)
assert [ self.assertEqual(
{ [
"balance": 12.5, {
"member": { "balance": 12.5,
"activated": True, "member": {
"id": 1, "activated": True,
"name": "zorglub", "id": 1,
"weight": 1.0, "name": "zorglub",
"weight": 1.0,
},
"paid": 25.0,
"spent": 12.5,
}, },
"paid": 25.0, {
"spent": 12.5, "balance": -12.5,
}, "member": {
{ "activated": True,
"balance": -12.5, "id": 2,
"member": { "name": "fred",
"activated": True, "weight": 1.0,
"id": 2, },
"name": "jeanne", "paid": 0,
"weight": 1.0, "spent": 12.5,
}, },
"paid": 0, ],
"spent": 12.5, json.loads(req.data.decode("utf-8")),
}, )
] == json.loads(req.data.decode("utf-8"))
def test_username_xss(self): def test_username_xss(self):
# create a project # create a project
@ -849,7 +794,7 @@ class TestAPI(IhatemoneyTestCase):
self.api_add_member("raclette", "<script>") self.api_add_member("raclette", "<script>")
result = self.client.get("/raclette/") result = self.client.get("/raclette/")
assert "<script>" not in result.data.decode("utf-8") self.assertNotIn("<script>", result.data.decode("utf-8"))
def test_weighted_bills(self): def test_weighted_bills(self):
# create a project # create a project
@ -857,7 +802,7 @@ class TestAPI(IhatemoneyTestCase):
# add participants # add participants
self.api_add_member("raclette", "zorglub") self.api_add_member("raclette", "zorglub")
self.api_add_member("raclette", "jeannedy familly", 4) self.api_add_member("raclette", "freddy familly", 4)
self.api_add_member("raclette", "quentin") self.api_add_member("raclette", "quentin")
# add a bill # add a bill
@ -868,7 +813,6 @@ class TestAPI(IhatemoneyTestCase):
"what": "fromage", "what": "fromage",
"payer": "1", "payer": "1",
"payed_for": ["1", "2"], "payed_for": ["1", "2"],
"bill_type": "Expense",
"amount": "25", "amount": "25",
}, },
headers=self.get_auth("raclette"), headers=self.get_auth("raclette"),
@ -889,9 +833,8 @@ class TestAPI(IhatemoneyTestCase):
"payer_id": 1, "payer_id": 1,
"owers": [ "owers": [
{"activated": True, "id": 1, "name": "zorglub", "weight": 1}, {"activated": True, "id": 1, "name": "zorglub", "weight": 1},
{"activated": True, "id": 2, "name": "jeannedy familly", "weight": 4}, {"activated": True, "id": 2, "name": "freddy familly", "weight": 4},
], ],
"bill_type": "Expense",
"amount": 25.0, "amount": 25.0,
"date": "2011-08-10", "date": "2011-08-10",
"id": 1, "id": 1,
@ -900,12 +843,12 @@ class TestAPI(IhatemoneyTestCase):
"original_currency": "XXX", "original_currency": "XXX",
} }
got = json.loads(req.data.decode("utf-8")) got = json.loads(req.data.decode("utf-8"))
assert ( self.assertEqual(
creation_date creation_date,
== datetime.datetime.strptime(got["creation_date"], "%Y-%m-%d").date() datetime.datetime.strptime(got["creation_date"], "%Y-%m-%d").date(),
) )
del got["creation_date"] del got["creation_date"]
assert expected == got self.assertDictEqual(expected, got)
# getting it should return a 404 # getting it should return a 404
req = self.client.get( req = self.client.get(
@ -924,7 +867,7 @@ class TestAPI(IhatemoneyTestCase):
{ {
"activated": True, "activated": True,
"id": 2, "id": 2,
"name": "jeannedy familly", "name": "freddy familly",
"weight": 4.0, "weight": 4.0,
"balance": -20.0, "balance": -20.0,
}, },
@ -945,7 +888,7 @@ class TestAPI(IhatemoneyTestCase):
self.assertStatus(200, req) self.assertStatus(200, req)
decoded_req = json.loads(req.data.decode("utf-8")) decoded_req = json.loads(req.data.decode("utf-8"))
assert decoded_req == expected self.assertDictEqual(decoded_req, expected)
def test_log_created_from_api_call(self): def test_log_created_from_api_call(self):
# create a project # create a project
@ -956,13 +899,15 @@ class TestAPI(IhatemoneyTestCase):
self.api_add_member("raclette", "zorglub") self.api_add_member("raclette", "zorglub")
resp = self.client.get("/raclette/history", follow_redirects=True) resp = self.client.get("/raclette/history", follow_redirects=True)
assert resp.status_code == 200 self.assertEqual(resp.status_code, 200)
assert f"Participant {em_surround('zorglub')} added" in resp.data.decode( self.assertIn(
"utf-8" f"Participant {em_surround('zorglub')} added", resp.data.decode("utf-8")
) )
assert f"Project {em_surround('raclette')} added" in resp.data.decode("utf-8") self.assertIn(
assert resp.data.decode("utf-8").count("<td> -- </td>") == 2 f"Project {em_surround('raclette')} added", resp.data.decode("utf-8")
assert "127.0.0.1" not in resp.data.decode("utf-8") )
self.assertEqual(resp.data.decode("utf-8").count("<td> -- </td>"), 2)
self.assertNotIn("127.0.0.1", resp.data.decode("utf-8"))
def test_amount_is_null(self): def test_amount_is_null(self):
self.api_create("raclette") self.api_create("raclette")
@ -977,7 +922,6 @@ class TestAPI(IhatemoneyTestCase):
"what": "fromage", "what": "fromage",
"payer": "1", "payer": "1",
"payed_for": ["1"], "payed_for": ["1"],
"bill_type": "Expense",
"amount": "0", "amount": "0",
}, },
headers=self.get_auth("raclette"), headers=self.get_auth("raclette"),
@ -1006,71 +950,12 @@ class TestAPI(IhatemoneyTestCase):
"what": "fromage", "what": "fromage",
"payer": "1", "payer": "1",
"payed_for": ["1"], "payed_for": ["1"],
"bill_type": "Expense",
"amount": "9347242149381274732472348728748723473278472843.12", "amount": "9347242149381274732472348728748723473278472843.12",
}, },
headers=self.get_auth("raclette"), headers=self.get_auth("raclette"),
) )
self.assertStatus(400, req) self.assertStatus(400, req)
def test_validate_bill_type(self):
self.api_create("raclette")
self.api_add_member("raclette", "zorglub")
req = self.client.post( if __name__ == "__main__":
"/api/projects/raclette/bills", unittest.main()
data={
"date": "2011-08-10",
"what": "fromage",
"payer": "1",
"payed_for": ["1"],
"bill_type": "wrong_bill_type",
"amount": "50",
},
headers=self.get_auth("raclette"),
)
self.assertStatus(400, req)
req = self.client.post(
"/api/projects/raclette/bills",
data={
"date": "2011-08-10",
"what": "fromage",
"payer": "1",
"payed_for": ["1"],
"bill_type": "Expense",
"amount": "50",
},
headers=self.get_auth("raclette"),
)
self.assertStatus(201, req)
def test_default_bill_type(self):
self.api_create("raclette")
self.api_add_member("raclette", "zorglub")
# Post a bill without adding a bill type
req = self.client.post(
"/api/projects/raclette/bills",
data={
"date": "2011-08-10",
"what": "fromage",
"payer": "1",
"payed_for": ["1"],
"amount": "50",
},
headers=self.get_auth("raclette"),
)
self.assertStatus(201, req)
req = self.client.get(
"/api/projects/raclette/bills/1", headers=self.get_auth("raclette")
)
self.assertStatus(200, req)
# Bill type should now be "Expense"
got = json.loads(req.data.decode("utf-8"))
assert got["bill_type"] == "Expense"

File diff suppressed because it is too large Load diff

View file

@ -1,20 +1,44 @@
import os import os
from unittest.mock import MagicMock
import pytest from flask_testing import TestCase
from werkzeug.security import generate_password_hash
from ihatemoney import models from ihatemoney import models
from ihatemoney.utils import generate_password_hash from ihatemoney.currency_convertor import CurrencyConverter
from ihatemoney.run import create_app, db
@pytest.mark.usefixtures("client", "converter") class BaseTestCase(TestCase):
class BaseTestCase:
SECRET_KEY = "TEST SESSION" SECRET_KEY = "TEST SESSION"
SQLALCHEMY_DATABASE_URI = os.environ.get( SQLALCHEMY_DATABASE_URI = os.environ.get(
"TESTING_SQLALCHEMY_DATABASE_URI", "sqlite://" "TESTING_SQLALCHEMY_DATABASE_URI", "sqlite://"
) )
ENABLE_CAPTCHA = False ENABLE_CAPTCHA = False
PASSWORD_HASH_METHOD = "pbkdf2:sha1:1"
PASSWORD_HASH_SALT_LENGTH = 1 def create_app(self):
# Pass the test object as a configuration.
return create_app(self)
def setUp(self):
db.create_all()
# Add dummy data to CurrencyConverter for all tests (since it's a singleton)
mock_data = {
"USD": 1,
"EUR": 0.8,
"CAD": 1.2,
"PLN": 4,
CurrencyConverter.no_currency: 1,
}
converter = CurrencyConverter()
converter.get_rates = MagicMock(return_value=mock_data)
# Also add it to an attribute to make tests clearer
self.converter = converter
def tearDown(self):
# clean after testing
db.session.remove()
db.drop_all()
def login(self, project, password=None, test_client=None): def login(self, project, password=None, test_client=None):
password = password or project password = password or project
@ -32,7 +56,6 @@ class BaseTestCase:
default_currency="XXX", default_currency="XXX",
name=None, name=None,
password=None, password=None,
project_history=True,
): ):
"""Create a fake project""" """Create a fake project"""
name = name or id name = name or id
@ -46,7 +69,6 @@ class BaseTestCase:
"password": password, "password": password,
"contact_email": f"{id}@notmyidea.org", "contact_email": f"{id}@notmyidea.org",
"default_currency": default_currency, "default_currency": default_currency,
"project_history": project_history,
}, },
follow_redirects=follow_redirects, follow_redirects=follow_redirects,
) )
@ -57,7 +79,7 @@ class BaseTestCase:
data=data, data=data,
# follow_redirects=True, # follow_redirects=True,
) )
assert ("/{id}/edit" in str(resp.response)) == (not success) self.assertEqual("/{id}/edit" in str(resp.response), not success)
def create_project(self, id, default_currency="XXX", name=None, password=None): def create_project(self, id, default_currency="XXX", name=None, password=None):
name = name or str(id) name = name or str(id)
@ -83,9 +105,11 @@ class IhatemoneyTestCase(BaseTestCase):
def assertStatus(self, expected, resp, url=None): def assertStatus(self, expected, resp, url=None):
if url is None: if url is None:
url = resp.request.path url = resp.request.path
assert ( return self.assertEqual(
expected == resp.status_code expected,
), f"{url} expected {expected}, got {resp.status_code}" resp.status_code,
f"{url} expected {expected}, got {resp.status_code}",
)
def enable_admin(self, password="adminpass"): def enable_admin(self, password="adminpass"):
self.app.config["ACTIVATE_ADMIN_DASHBOARD"] = True self.app.config["ACTIVATE_ADMIN_DASHBOARD"] = True

View file

@ -1,64 +0,0 @@
from unittest.mock import MagicMock
from flask import Flask
from jinja2 import FileSystemBytecodeCache
import pytest
from ihatemoney.babel_utils import compile_catalogs
from ihatemoney.currency_convertor import CurrencyConverter
from ihatemoney.run import create_app, db
@pytest.fixture(autouse=True, scope="session")
def babel_catalogs():
compile_catalogs()
@pytest.fixture(scope="session")
def jinja_cache_directory(tmp_path_factory):
return tmp_path_factory.mktemp("cache")
@pytest.fixture
def app(request: pytest.FixtureRequest, jinja_cache_directory):
"""Create the Flask app with database"""
app = create_app(request.cls)
# Caches the jinja templates so they are compiled only once per test session
app.jinja_env.bytecode_cache = FileSystemBytecodeCache(jinja_cache_directory)
with app.app_context():
db.create_all()
request.cls.app = app
yield app
# clean after testing
db.session.remove()
db.drop_all()
@pytest.fixture
def client(app: Flask, request: pytest.FixtureRequest):
client = app.test_client()
request.cls.client = client
yield client
@pytest.fixture
def converter(request: pytest.FixtureRequest):
# Add dummy data to CurrencyConverter for all tests (since it's a singleton)
mock_data = {
"USD": 1,
"EUR": 0.8,
"CAD": 1.2,
"PLN": 4,
CurrencyConverter.no_currency: 1,
}
converter = CurrencyConverter()
converter.get_rates = MagicMock(return_value=mock_data)
# Also add it to an attribute to make tests clearer
request.cls.converter = converter
yield converter

View file

@ -1,6 +1,4 @@
import re import unittest
import pytest
from ihatemoney import history, models from ihatemoney import history, models
from ihatemoney.tests.common.help_functions import em_surround from ihatemoney.tests.common.help_functions import em_surround
@ -8,40 +6,24 @@ from ihatemoney.tests.common.ihatemoney_testcase import IhatemoneyTestCase
from ihatemoney.versioning import LoggingMode from ihatemoney.versioning import LoggingMode
@pytest.fixture class HistoryTestCase(IhatemoneyTestCase):
def demo(client): def setUp(self):
client.post( super().setUp()
"/create", self.post_project("demo")
data={ self.login("demo")
"name": "demo",
"id": "demo",
"password": "demo",
"contact_email": "demo@notmyidea.org",
"default_currency": "XXX",
"project_history": True,
},
)
client.post(
"/authenticate",
data=dict(id="demo", password="demo"),
)
@pytest.mark.usefixtures("demo")
class TestHistory(IhatemoneyTestCase):
def test_simple_create_logentry_no_ip(self): def test_simple_create_logentry_no_ip(self):
resp = self.client.get("/demo/history") resp = self.client.get("/demo/history")
assert resp.status_code == 200 self.assertEqual(resp.status_code, 200)
assert f"Project {em_surround('demo')} added" in resp.data.decode("utf-8") self.assertIn(f"Project {em_surround('demo')} added", resp.data.decode("utf-8"))
assert resp.data.decode("utf-8").count("<td> -- </td>") == 1 self.assertEqual(resp.data.decode("utf-8").count("<td> -- </td>"), 1)
assert "127.0.0.1" not in resp.data.decode("utf-8") self.assertNotIn("127.0.0.1", resp.data.decode("utf-8"))
def change_privacy_to(self, current_password, logging_preference): def change_privacy_to(self, logging_preference):
# Change only logging_preferences # Change only logging_preferences
new_data = { new_data = {
"name": "demo", "name": "demo",
"contact_email": "demo@notmyidea.org", "contact_email": "demo@notmyidea.org",
"current_password": current_password,
"password": "demo", "password": "demo",
"default_currency": "XXX", "default_currency": "XXX",
} }
@ -53,136 +35,142 @@ class TestHistory(IhatemoneyTestCase):
# Disable History # Disable History
resp = self.client.post("/demo/edit", data=new_data, follow_redirects=True) resp = self.client.post("/demo/edit", data=new_data, follow_redirects=True)
assert resp.status_code == 200 self.assertEqual(resp.status_code, 200)
assert "alert-danger" not in resp.data.decode("utf-8") self.assertNotIn("alert-danger", resp.data.decode("utf-8"))
resp = self.client.get("/demo/edit") resp = self.client.get("/demo/edit")
assert resp.status_code == 200 self.assertEqual(resp.status_code, 200)
if logging_preference == LoggingMode.DISABLED: if logging_preference == LoggingMode.DISABLED:
assert '<input id="project_history"' in resp.data.decode("utf-8") self.assertIn('<input id="project_history"', resp.data.decode("utf-8"))
else: else:
assert '<input checked id="project_history"' in resp.data.decode("utf-8") self.assertIn(
'<input checked id="project_history"', resp.data.decode("utf-8")
)
if logging_preference == LoggingMode.RECORD_IP: if logging_preference == LoggingMode.RECORD_IP:
assert '<input checked id="ip_recording"' in resp.data.decode("utf-8") self.assertIn('<input checked id="ip_recording"', resp.data.decode("utf-8"))
else: else:
assert '<input id="ip_recording"' in resp.data.decode("utf-8") self.assertIn('<input id="ip_recording"', resp.data.decode("utf-8"))
def assert_empty_history_logging_disabled(self): def assert_empty_history_logging_disabled(self):
resp = self.client.get("/demo/history") resp = self.client.get("/demo/history")
assert ( self.assertIn(
"This project has history disabled. New actions won't appear below." "This project has history disabled. New actions won't appear below.",
in resp.data.decode("utf-8") resp.data.decode("utf-8"),
) )
assert "Nothing to list" in resp.data.decode("utf-8") self.assertIn("Nothing to list", resp.data.decode("utf-8"))
assert ( self.assertNotIn(
"The table below reflects actions recorded prior to disabling project history." "The table below reflects actions recorded prior to disabling project history.",
not in resp.data.decode("utf-8") resp.data.decode("utf-8"),
) )
assert "Some entries below contain IP addresses," not in resp.data.decode( self.assertNotIn(
"utf-8" "Some entries below contain IP addresses,", resp.data.decode("utf-8")
)
self.assertNotIn("127.0.0.1", resp.data.decode("utf-8"))
self.assertNotIn("<td> -- </td>", resp.data.decode("utf-8"))
self.assertNotIn(
f"Project {em_surround('demo')} added", resp.data.decode("utf-8")
) )
assert "127.0.0.1" not in resp.data.decode("utf-8")
assert "<td> -- </td>" not in resp.data.decode("utf-8")
assert f"Project {em_surround('demo')} added" not in resp.data.decode("utf-8")
def test_project_edit(self): def test_project_edit(self):
new_data = { new_data = {
"name": "demo2", "name": "demo2",
"contact_email": "demo2@notmyidea.org", "contact_email": "demo2@notmyidea.org",
"current_password": "demo",
"password": "123456", "password": "123456",
"project_history": "y", "project_history": "y",
"default_currency": "USD", # Currency changed from default "default_currency": "USD", # Currency changed from default
} }
resp = self.client.post("/demo/edit", data=new_data, follow_redirects=True) resp = self.client.post("/demo/edit", data=new_data, follow_redirects=True)
assert resp.status_code == 200 self.assertEqual(resp.status_code, 200)
resp = self.client.get("/demo/history") resp = self.client.get("/demo/history")
assert resp.status_code == 200 self.assertEqual(resp.status_code, 200)
assert f"Project {em_surround('demo')} added" in resp.data.decode("utf-8") self.assertIn(f"Project {em_surround('demo')} added", resp.data.decode("utf-8"))
assert ( self.assertIn(
f"Project contact email changed to {em_surround('demo2@notmyidea.org')}" f"Project contact email changed to {em_surround('demo2@notmyidea.org')}",
in resp.data.decode("utf-8") resp.data.decode("utf-8"),
) )
assert "Project private code changed" in resp.data.decode("utf-8") self.assertIn("Project private code changed", resp.data.decode("utf-8"))
assert f"Project renamed to {em_surround('demo2')}" in resp.data.decode("utf-8") self.assertIn(
assert resp.data.decode("utf-8").index("Project renamed ") < resp.data.decode( f"Project renamed to {em_surround('demo2')}", resp.data.decode("utf-8")
"utf-8" )
).index("Project contact email changed to ") self.assertLess(
assert resp.data.decode("utf-8").index("Project renamed ") < resp.data.decode( resp.data.decode("utf-8").index("Project renamed "),
"utf-8" resp.data.decode("utf-8").index("Project contact email changed to "),
).index("Project private code changed") )
assert resp.data.decode("utf-8").count("<td> -- </td>") == 5 self.assertLess(
assert "127.0.0.1" not in resp.data.decode("utf-8") resp.data.decode("utf-8").index("Project renamed "),
resp.data.decode("utf-8").index("Project private code changed"),
)
self.assertEqual(resp.data.decode("utf-8").count("<td> -- </td>"), 5)
self.assertNotIn("127.0.0.1", resp.data.decode("utf-8"))
def test_project_privacy_edit(self): def test_project_privacy_edit(self):
resp = self.client.get("/demo/edit") resp = self.client.get("/demo/edit")
assert resp.status_code == 200 self.assertEqual(resp.status_code, 200)
assert ( self.assertIn(
'<input checked id="project_history" name="project_history" type="checkbox" value="y">' '<input checked id="project_history" name="project_history" type="checkbox" value="y">',
in resp.data.decode("utf-8") resp.data.decode("utf-8"),
) )
self.change_privacy_to("demo", LoggingMode.DISABLED) self.change_privacy_to(LoggingMode.DISABLED)
resp = self.client.get("/demo/history") resp = self.client.get("/demo/history")
assert resp.status_code == 200 self.assertEqual(resp.status_code, 200)
assert "Disabled Project History\n" in resp.data.decode("utf-8") self.assertIn("Disabled Project History\n", resp.data.decode("utf-8"))
assert resp.data.decode("utf-8").count("<td> -- </td>") == 2 self.assertEqual(resp.data.decode("utf-8").count("<td> -- </td>"), 2)
assert "127.0.0.1" not in resp.data.decode("utf-8") self.assertNotIn("127.0.0.1", resp.data.decode("utf-8"))
self.change_privacy_to("demo", LoggingMode.RECORD_IP) self.change_privacy_to(LoggingMode.RECORD_IP)
resp = self.client.get("/demo/history") resp = self.client.get("/demo/history")
assert resp.status_code == 200 self.assertEqual(resp.status_code, 200)
assert "Enabled Project History & IP Address Recording" in resp.data.decode( self.assertIn(
"utf-8" "Enabled Project History & IP Address Recording", resp.data.decode("utf-8")
) )
assert resp.data.decode("utf-8").count("<td> -- </td>") == 2 self.assertEqual(resp.data.decode("utf-8").count("<td> -- </td>"), 2)
assert resp.data.decode("utf-8").count("127.0.0.1") == 1 self.assertEqual(resp.data.decode("utf-8").count("127.0.0.1"), 1)
self.change_privacy_to("demo", LoggingMode.ENABLED) self.change_privacy_to(LoggingMode.ENABLED)
resp = self.client.get("/demo/history") resp = self.client.get("/demo/history")
assert resp.status_code == 200 self.assertEqual(resp.status_code, 200)
assert "Disabled IP Address Recording\n" in resp.data.decode("utf-8") self.assertIn("Disabled IP Address Recording\n", resp.data.decode("utf-8"))
assert resp.data.decode("utf-8").count("<td> -- </td>") == 2 self.assertEqual(resp.data.decode("utf-8").count("<td> -- </td>"), 2)
assert resp.data.decode("utf-8").count("127.0.0.1") == 2 self.assertEqual(resp.data.decode("utf-8").count("127.0.0.1"), 2)
def test_project_privacy_edit2(self): def test_project_privacy_edit2(self):
self.change_privacy_to("demo", LoggingMode.RECORD_IP) self.change_privacy_to(LoggingMode.RECORD_IP)
resp = self.client.get("/demo/history") resp = self.client.get("/demo/history")
assert resp.status_code == 200 self.assertEqual(resp.status_code, 200)
assert "Enabled IP Address Recording\n" in resp.data.decode("utf-8") self.assertIn("Enabled IP Address Recording\n", resp.data.decode("utf-8"))
assert resp.data.decode("utf-8").count("<td> -- </td>") == 1 self.assertEqual(resp.data.decode("utf-8").count("<td> -- </td>"), 1)
assert resp.data.decode("utf-8").count("127.0.0.1") == 1 self.assertEqual(resp.data.decode("utf-8").count("127.0.0.1"), 1)
self.change_privacy_to("demo", LoggingMode.DISABLED) self.change_privacy_to(LoggingMode.DISABLED)
resp = self.client.get("/demo/history") resp = self.client.get("/demo/history")
assert resp.status_code == 200 self.assertEqual(resp.status_code, 200)
assert "Disabled Project History & IP Address Recording" in resp.data.decode( self.assertIn(
"utf-8" "Disabled Project History & IP Address Recording", resp.data.decode("utf-8")
) )
assert resp.data.decode("utf-8").count("<td> -- </td>") == 1 self.assertEqual(resp.data.decode("utf-8").count("<td> -- </td>"), 1)
assert resp.data.decode("utf-8").count("127.0.0.1") == 2 self.assertEqual(resp.data.decode("utf-8").count("127.0.0.1"), 2)
self.change_privacy_to("demo", LoggingMode.ENABLED) self.change_privacy_to(LoggingMode.ENABLED)
resp = self.client.get("/demo/history") resp = self.client.get("/demo/history")
assert resp.status_code == 200 self.assertEqual(resp.status_code, 200)
assert "Enabled Project History\n" in resp.data.decode("utf-8") self.assertIn("Enabled Project History\n", resp.data.decode("utf-8"))
assert resp.data.decode("utf-8").count("<td> -- </td>") == 2 self.assertEqual(resp.data.decode("utf-8").count("<td> -- </td>"), 2)
assert resp.data.decode("utf-8").count("127.0.0.1") == 2 self.assertEqual(resp.data.decode("utf-8").count("127.0.0.1"), 2)
def do_misc_database_operations(self, logging_mode): def do_misc_database_operations(self, logging_mode):
new_data = { new_data = {
"name": "demo2", "name": "demo2",
"contact_email": "demo2@notmyidea.org", "contact_email": "demo2@notmyidea.org",
"current_password": "demo",
"password": "123456", "password": "123456",
"default_currency": "USD", "default_currency": "USD",
} }
@ -194,13 +182,13 @@ class TestHistory(IhatemoneyTestCase):
new_data["ip_recording"] = "y" new_data["ip_recording"] = "y"
resp = self.client.post("/demo/edit", data=new_data, follow_redirects=True) resp = self.client.post("/demo/edit", data=new_data, follow_redirects=True)
assert resp.status_code == 200 self.assertEqual(resp.status_code, 200)
# adds a member to this project # adds a member to this project
resp = self.client.post( resp = self.client.post(
"/demo/members/add", data={"name": "zorglub"}, follow_redirects=True "/demo/members/add", data={"name": "zorglub"}, follow_redirects=True
) )
assert resp.status_code == 200 self.assertEqual(resp.status_code, 200)
user_id = models.Person.query.one().id user_id = models.Person.query.one().id
@ -212,12 +200,11 @@ class TestHistory(IhatemoneyTestCase):
"what": "fromage à raclette", "what": "fromage à raclette",
"payer": user_id, "payer": user_id,
"payed_for": [user_id], "payed_for": [user_id],
"bill_type": "Expense",
"amount": "25", "amount": "25",
}, },
follow_redirects=True, follow_redirects=True,
) )
assert resp.status_code == 200 self.assertEqual(resp.status_code, 200)
bill_id = models.Bill.query.one().id bill_id = models.Bill.query.one().id
@ -229,46 +216,48 @@ class TestHistory(IhatemoneyTestCase):
"what": "fromage à raclette", "what": "fromage à raclette",
"payer": user_id, "payer": user_id,
"payed_for": [user_id], "payed_for": [user_id],
"bill_type": "Expense",
"amount": "10", "amount": "10",
}, },
follow_redirects=True, follow_redirects=True,
) )
assert resp.status_code == 200 self.assertEqual(resp.status_code, 200)
# delete the bill # delete the bill
resp = self.client.post(f"/demo/delete/{bill_id}", follow_redirects=True) resp = self.client.post(f"/demo/delete/{bill_id}", follow_redirects=True)
assert resp.status_code == 200 self.assertEqual(resp.status_code, 200)
# delete user using POST method # delete user using POST method
resp = self.client.post( resp = self.client.post(
f"/demo/members/{user_id}/delete", follow_redirects=True f"/demo/members/{user_id}/delete", follow_redirects=True
) )
assert resp.status_code == 200 self.assertEqual(resp.status_code, 200)
def test_disable_clear_no_new_records(self): def test_disable_clear_no_new_records(self):
# Disable logging # Disable logging
self.change_privacy_to("demo", LoggingMode.DISABLED) self.change_privacy_to(LoggingMode.DISABLED)
# Ensure we can't clear history with a GET or with a password-less POST # Ensure we can't clear history with a GET or with a password-less POST
resp = self.client.get("/demo/erase_history") resp = self.client.get("/demo/erase_history")
assert resp.status_code == 405 self.assertEqual(resp.status_code, 405)
resp = self.client.post("/demo/erase_history", follow_redirects=True) resp = self.client.post("/demo/erase_history", follow_redirects=True)
assert "Error deleting project history" in resp.data.decode("utf-8") self.assertIn(
"Error deleting project history",
resp.data.decode("utf-8"),
)
# List history # List history
resp = self.client.get("/demo/history") resp = self.client.get("/demo/history")
assert resp.status_code == 200 self.assertEqual(resp.status_code, 200)
assert ( self.assertIn(
"This project has history disabled. New actions won't appear below." "This project has history disabled. New actions won't appear below.",
in resp.data.decode("utf-8") resp.data.decode("utf-8"),
) )
assert ( self.assertIn(
"The table below reflects actions recorded prior to disabling project history." "The table below reflects actions recorded prior to disabling project history.",
in resp.data.decode("utf-8") resp.data.decode("utf-8"),
) )
assert "Nothing to list" not in resp.data.decode("utf-8") self.assertNotIn("Nothing to list", resp.data.decode("utf-8"))
assert "Some entries below contain IP addresses," not in resp.data.decode( self.assertNotIn(
"utf-8" "Some entries below contain IP addresses,", resp.data.decode("utf-8")
) )
# Clear Existing Entries # Clear Existing Entries
@ -277,7 +266,7 @@ class TestHistory(IhatemoneyTestCase):
data={"password": "demo"}, data={"password": "demo"},
follow_redirects=True, follow_redirects=True,
) )
assert resp.status_code == 200 self.assertEqual(resp.status_code, 200)
self.assert_empty_history_logging_disabled() self.assert_empty_history_logging_disabled()
# Do lots of database operations & check that there's still no history # Do lots of database operations & check that there's still no history
@ -287,47 +276,52 @@ class TestHistory(IhatemoneyTestCase):
def test_clear_ip_records(self): def test_clear_ip_records(self):
# Enable IP Recording # Enable IP Recording
self.change_privacy_to("demo", LoggingMode.RECORD_IP) self.change_privacy_to(LoggingMode.RECORD_IP)
# Do lots of database operations to generate IP address entries # Do lots of database operations to generate IP address entries
self.do_misc_database_operations(LoggingMode.RECORD_IP) self.do_misc_database_operations(LoggingMode.RECORD_IP)
# Disable IP Recording # Disable IP Recording
self.change_privacy_to("123456", LoggingMode.ENABLED) self.change_privacy_to(LoggingMode.ENABLED)
resp = self.client.get("/demo/history") resp = self.client.get("/demo/history")
assert resp.status_code == 200 self.assertEqual(resp.status_code, 200)
assert ( self.assertNotIn(
"This project has history disabled. New actions won't appear below." "This project has history disabled. New actions won't appear below.",
not in resp.data.decode("utf-8") resp.data.decode("utf-8"),
) )
assert ( self.assertNotIn(
"The table below reflects actions recorded prior to disabling project history." "The table below reflects actions recorded prior to disabling project history.",
not in resp.data.decode("utf-8") resp.data.decode("utf-8"),
) )
assert "Nothing to list" not in resp.data.decode("utf-8") self.assertNotIn("Nothing to list", resp.data.decode("utf-8"))
assert "Some entries below contain IP addresses," in resp.data.decode("utf-8") self.assertIn(
assert resp.data.decode("utf-8").count("127.0.0.1") == 12 "Some entries below contain IP addresses,", resp.data.decode("utf-8")
assert resp.data.decode("utf-8").count("<td> -- </td>") == 1 )
self.assertEqual(resp.data.decode("utf-8").count("127.0.0.1"), 12)
self.assertEqual(resp.data.decode("utf-8").count("<td> -- </td>"), 1)
# Generate more operations to confirm additional IP info isn't recorded # Generate more operations to confirm additional IP info isn't recorded
self.do_misc_database_operations(LoggingMode.ENABLED) self.do_misc_database_operations(LoggingMode.ENABLED)
resp = self.client.get("/demo/history") resp = self.client.get("/demo/history")
assert resp.status_code == 200 self.assertEqual(resp.status_code, 200)
assert resp.data.decode("utf-8").count("127.0.0.1") == 12 self.assertEqual(resp.data.decode("utf-8").count("127.0.0.1"), 12)
assert resp.data.decode("utf-8").count("<td> -- </td>") == 7 self.assertEqual(resp.data.decode("utf-8").count("<td> -- </td>"), 7)
# Ensure we can't clear IP data with a GET or with a password-less POST # Ensure we can't clear IP data with a GET or with a password-less POST
resp = self.client.get("/demo/strip_ip_addresses") resp = self.client.get("/demo/strip_ip_addresses")
assert resp.status_code == 405 self.assertEqual(resp.status_code, 405)
resp = self.client.post("/demo/strip_ip_addresses", follow_redirects=True) resp = self.client.post("/demo/strip_ip_addresses", follow_redirects=True)
assert "Error deleting recorded IP addresses" in resp.data.decode("utf-8") self.assertIn(
"Error deleting recorded IP addresses",
resp.data.decode("utf-8"),
)
resp = self.client.get("/demo/history") resp = self.client.get("/demo/history")
assert resp.status_code == 200 self.assertEqual(resp.status_code, 200)
assert resp.data.decode("utf-8").count("127.0.0.1") == 12 self.assertEqual(resp.data.decode("utf-8").count("127.0.0.1"), 12)
assert resp.data.decode("utf-8").count("<td> -- </td>") == 7 self.assertEqual(resp.data.decode("utf-8").count("<td> -- </td>"), 7)
# Clear IP Data # Clear IP Data
resp = self.client.post( resp = self.client.post(
@ -336,33 +330,33 @@ class TestHistory(IhatemoneyTestCase):
follow_redirects=True, follow_redirects=True,
) )
assert resp.status_code == 200 self.assertEqual(resp.status_code, 200)
assert ( self.assertNotIn(
"This project has history disabled. New actions won't appear below." "This project has history disabled. New actions won't appear below.",
not in resp.data.decode("utf-8") resp.data.decode("utf-8"),
) )
assert ( self.assertNotIn(
"The table below reflects actions recorded prior to disabling project history." "The table below reflects actions recorded prior to disabling project history.",
not in resp.data.decode("utf-8") resp.data.decode("utf-8"),
) )
assert "Nothing to list" not in resp.data.decode("utf-8") self.assertNotIn("Nothing to list", resp.data.decode("utf-8"))
assert "Some entries below contain IP addresses," not in resp.data.decode( self.assertNotIn(
"utf-8" "Some entries below contain IP addresses,", resp.data.decode("utf-8")
) )
assert resp.data.decode("utf-8").count("127.0.0.1") == 0 self.assertEqual(resp.data.decode("utf-8").count("127.0.0.1"), 0)
assert resp.data.decode("utf-8").count("<td> -- </td>") == 19 self.assertEqual(resp.data.decode("utf-8").count("<td> -- </td>"), 19)
def test_logs_for_common_actions(self): def test_logs_for_common_actions(self):
# adds a member to this project # adds a member to this project
resp = self.client.post( resp = self.client.post(
"/demo/members/add", data={"name": "zorglub"}, follow_redirects=True "/demo/members/add", data={"name": "zorglub"}, follow_redirects=True
) )
assert resp.status_code == 200 self.assertEqual(resp.status_code, 200)
resp = self.client.get("/demo/history") resp = self.client.get("/demo/history")
assert resp.status_code == 200 self.assertEqual(resp.status_code, 200)
assert f"Participant {em_surround('zorglub')} added" in resp.data.decode( self.assertIn(
"utf-8" f"Participant {em_surround('zorglub')} added", resp.data.decode("utf-8")
) )
# create a bill # create a bill
@ -373,17 +367,17 @@ class TestHistory(IhatemoneyTestCase):
"what": "fromage à raclette", "what": "fromage à raclette",
"payer": 1, "payer": 1,
"payed_for": [1], "payed_for": [1],
"bill_type": "Expense",
"amount": "25", "amount": "25",
}, },
follow_redirects=True, follow_redirects=True,
) )
assert resp.status_code == 200 self.assertEqual(resp.status_code, 200)
resp = self.client.get("/demo/history") resp = self.client.get("/demo/history")
assert resp.status_code == 200 self.assertEqual(resp.status_code, 200)
assert f"Bill {em_surround('fromage à raclette')} added" in resp.data.decode( self.assertIn(
"utf-8" f"Bill {em_surround('fromage à raclette')} added",
resp.data.decode("utf-8"),
) )
# edit the bill # edit the bill
@ -394,42 +388,48 @@ class TestHistory(IhatemoneyTestCase):
"what": "new thing", "what": "new thing",
"payer": 1, "payer": 1,
"payed_for": [1], "payed_for": [1],
"bill_type": "Expense",
"amount": "10", "amount": "10",
}, },
follow_redirects=True, follow_redirects=True,
) )
assert resp.status_code == 200 self.assertEqual(resp.status_code, 200)
resp = self.client.get("/demo/history") resp = self.client.get("/demo/history")
assert resp.status_code == 200 self.assertEqual(resp.status_code, 200)
assert f"Bill {em_surround('fromage à raclette')} added" in resp.data.decode( self.assertIn(
"utf-8" f"Bill {em_surround('fromage à raclette')} added",
resp.data.decode("utf-8"),
) )
assert re.search( self.assertRegex(
resp.data.decode("utf-8"),
r"Bill %s:\s* Amount changed\s* from %s\s* to %s" r"Bill %s:\s* Amount changed\s* from %s\s* to %s"
% ( % (
em_surround("fromage à raclette", regex_escape=True), em_surround("fromage à raclette", regex_escape=True),
em_surround("25.0", regex_escape=True), em_surround("25.0", regex_escape=True),
em_surround("10.0", regex_escape=True), em_surround("10.0", regex_escape=True),
), ),
)
self.assertIn(
"Bill %s renamed to %s"
% (em_surround("fromage à raclette"), em_surround("new thing")),
resp.data.decode("utf-8"), resp.data.decode("utf-8"),
) )
assert "Bill %s renamed to %s" % ( self.assertLess(
em_surround("fromage à raclette"), resp.data.decode("utf-8").index(
em_surround("new thing"), f"Bill {em_surround('fromage à raclette')} renamed to"
) in resp.data.decode("utf-8") ),
assert resp.data.decode("utf-8").index( resp.data.decode("utf-8").index("Amount changed"),
f"Bill {em_surround('fromage à raclette')} renamed to" )
) < resp.data.decode("utf-8").index("Amount changed")
# delete the bill # delete the bill
resp = self.client.post("/demo/delete/1", follow_redirects=True) resp = self.client.post("/demo/delete/1", follow_redirects=True)
assert resp.status_code == 200 self.assertEqual(resp.status_code, 200)
resp = self.client.get("/demo/history") resp = self.client.get("/demo/history")
assert resp.status_code == 200 self.assertEqual(resp.status_code, 200)
assert f"Bill {em_surround('new thing')} removed" in resp.data.decode("utf-8") self.assertIn(
f"Bill {em_surround('new thing')} removed", resp.data.decode("utf-8")
)
# edit user # edit user
resp = self.client.post( resp = self.client.post(
@ -437,35 +437,39 @@ class TestHistory(IhatemoneyTestCase):
data={"weight": 2, "name": "new name"}, data={"weight": 2, "name": "new name"},
follow_redirects=True, follow_redirects=True,
) )
assert resp.status_code == 200 self.assertEqual(resp.status_code, 200)
resp = self.client.get("/demo/history") resp = self.client.get("/demo/history")
assert resp.status_code == 200 self.assertEqual(resp.status_code, 200)
assert re.search( self.assertRegex(
resp.data.decode("utf-8"),
r"Participant %s:\s* weight changed\s* from %s\s* to %s" r"Participant %s:\s* weight changed\s* from %s\s* to %s"
% ( % (
em_surround("zorglub", regex_escape=True), em_surround("zorglub", regex_escape=True),
em_surround("1.0", regex_escape=True), em_surround("1.0", regex_escape=True),
em_surround("2.0", regex_escape=True), em_surround("2.0", regex_escape=True),
), ),
)
self.assertIn(
"Participant %s renamed to %s"
% (em_surround("zorglub"), em_surround("new name")),
resp.data.decode("utf-8"), resp.data.decode("utf-8"),
) )
assert "Participant %s renamed to %s" % ( self.assertLess(
em_surround("zorglub"), resp.data.decode("utf-8").index(
em_surround("new name"), f"Participant {em_surround('zorglub')} renamed"
) in resp.data.decode("utf-8") ),
assert resp.data.decode("utf-8").index( resp.data.decode("utf-8").index("weight changed"),
f"Participant {em_surround('zorglub')} renamed" )
) < resp.data.decode("utf-8").index("weight changed")
# delete user using POST method # delete user using POST method
resp = self.client.post("/demo/members/1/delete", follow_redirects=True) resp = self.client.post("/demo/members/1/delete", follow_redirects=True)
assert resp.status_code == 200 self.assertEqual(resp.status_code, 200)
resp = self.client.get("/demo/history") resp = self.client.get("/demo/history")
assert resp.status_code == 200 self.assertEqual(resp.status_code, 200)
assert f"Participant {em_surround('new name')} removed" in resp.data.decode( self.assertIn(
"utf-8" f"Participant {em_surround('new name')} removed", resp.data.decode("utf-8")
) )
def test_double_bill_double_person_edit_second(self): def test_double_bill_double_person_edit_second(self):
@ -481,7 +485,6 @@ class TestHistory(IhatemoneyTestCase):
"what": "Bill 1", "what": "Bill 1",
"payer": 1, "payer": 1,
"payed_for": [1, 2], "payed_for": [1, 2],
"bill_type": "Expense",
"amount": "25", "amount": "25",
}, },
) )
@ -492,16 +495,15 @@ class TestHistory(IhatemoneyTestCase):
"what": "Bill 2", "what": "Bill 2",
"payer": 1, "payer": 1,
"payed_for": [1, 2], "payed_for": [1, 2],
"bill_type": "Expense",
"amount": "20", "amount": "20",
}, },
) )
# Should be 5 history entries at this point # Should be 5 history entries at this point
resp = self.client.get("/demo/history") resp = self.client.get("/demo/history")
assert resp.status_code == 200 self.assertEqual(resp.status_code, 200)
assert resp.data.decode("utf-8").count("<td> -- </td>") == 5 self.assertEqual(resp.data.decode("utf-8").count("<td> -- </td>"), 5)
assert "127.0.0.1" not in resp.data.decode("utf-8") self.assertNotIn("127.0.0.1", resp.data.decode("utf-8"))
# Edit ONLY the amount on the first bill # Edit ONLY the amount on the first bill
self.client.post( self.client.post(
@ -511,33 +513,33 @@ class TestHistory(IhatemoneyTestCase):
"what": "Bill 1", "what": "Bill 1",
"payer": 1, "payer": 1,
"payed_for": [1, 2], "payed_for": [1, 2],
"bill_type": "Expense",
"amount": "88", "amount": "88",
}, },
) )
resp = self.client.get("/demo/history") resp = self.client.get("/demo/history")
assert resp.status_code == 200 self.assertEqual(resp.status_code, 200)
assert re.search( self.assertRegex(
resp.data.decode("utf-8"),
r"Bill {}:\s* Amount changed\s* from {}\s* to {}".format( r"Bill {}:\s* Amount changed\s* from {}\s* to {}".format(
em_surround("Bill 1", regex_escape=True), em_surround("Bill 1", regex_escape=True),
em_surround("25.0", regex_escape=True), em_surround("25.0", regex_escape=True),
em_surround("88.0", regex_escape=True), em_surround("88.0", regex_escape=True),
), ),
resp.data.decode("utf-8"),
) )
assert not re.search( self.assertNotRegex(
resp.data.decode("utf-8"),
r"Removed\s* {}\s* and\s* {}\s* from\s* owers list".format( r"Removed\s* {}\s* and\s* {}\s* from\s* owers list".format(
em_surround("User 1", regex_escape=True), em_surround("User 1", regex_escape=True),
em_surround("User 2", regex_escape=True), em_surround("User 2", regex_escape=True),
), ),
resp.data.decode("utf-8"), resp.data.decode("utf-8"),
), resp.data.decode("utf-8") )
# Should be 6 history entries at this point # Should be 6 history entries at this point
assert resp.data.decode("utf-8").count("<td> -- </td>") == 6 self.assertEqual(resp.data.decode("utf-8").count("<td> -- </td>"), 6)
assert "127.0.0.1" not in resp.data.decode("utf-8") self.assertNotIn("127.0.0.1", resp.data.decode("utf-8"))
def test_bill_add_remove_add(self): def test_bill_add_remove_add(self):
# add two members # add two members
@ -552,7 +554,6 @@ class TestHistory(IhatemoneyTestCase):
"what": "Bill 1", "what": "Bill 1",
"payer": 1, "payer": 1,
"payed_for": [1, 2], "payed_for": [1, 2],
"bill_type": "Expense",
"amount": "25", "amount": "25",
}, },
) )
@ -561,11 +562,13 @@ class TestHistory(IhatemoneyTestCase):
self.client.post("/demo/delete/1", follow_redirects=True) self.client.post("/demo/delete/1", follow_redirects=True)
resp = self.client.get("/demo/history") resp = self.client.get("/demo/history")
assert resp.status_code == 200 self.assertEqual(resp.status_code, 200)
assert resp.data.decode("utf-8").count("<td> -- </td>") == 5 self.assertEqual(resp.data.decode("utf-8").count("<td> -- </td>"), 5)
assert "127.0.0.1" not in resp.data.decode("utf-8") self.assertNotIn("127.0.0.1", resp.data.decode("utf-8"))
assert f"Bill {em_surround('Bill 1')} added" in resp.data.decode("utf-8") self.assertIn(f"Bill {em_surround('Bill 1')} added", resp.data.decode("utf-8"))
assert f"Bill {em_surround('Bill 1')} removed" in resp.data.decode("utf-8") self.assertIn(
f"Bill {em_surround('Bill 1')} removed", resp.data.decode("utf-8")
)
# Add a new bill # Add a new bill
self.client.post( self.client.post(
@ -575,21 +578,22 @@ class TestHistory(IhatemoneyTestCase):
"what": "Bill 2", "what": "Bill 2",
"payer": 1, "payer": 1,
"payed_for": [1, 2], "payed_for": [1, 2],
"bill_type": "Expense",
"amount": "20", "amount": "20",
}, },
) )
resp = self.client.get("/demo/history") resp = self.client.get("/demo/history")
assert resp.status_code == 200 self.assertEqual(resp.status_code, 200)
assert resp.data.decode("utf-8").count("<td> -- </td>") == 6 self.assertEqual(resp.data.decode("utf-8").count("<td> -- </td>"), 6)
assert "127.0.0.1" not in resp.data.decode("utf-8") self.assertNotIn("127.0.0.1", resp.data.decode("utf-8"))
assert f"Bill {em_surround('Bill 1')} added" in resp.data.decode("utf-8") self.assertIn(f"Bill {em_surround('Bill 1')} added", resp.data.decode("utf-8"))
assert ( self.assertEqual(
resp.data.decode("utf-8").count(f"Bill {em_surround('Bill 1')} added") == 1 resp.data.decode("utf-8").count(f"Bill {em_surround('Bill 1')} added"), 1
)
self.assertIn(f"Bill {em_surround('Bill 2')} added", resp.data.decode("utf-8"))
self.assertIn(
f"Bill {em_surround('Bill 1')} removed", resp.data.decode("utf-8")
) )
assert f"Bill {em_surround('Bill 2')} added" in resp.data.decode("utf-8")
assert f"Bill {em_surround('Bill 1')} removed" in resp.data.decode("utf-8")
def test_double_bill_double_person_edit_second_no_web(self): def test_double_bill_double_person_edit_second_no_web(self):
u1 = models.Person(project_id="demo", name="User 1") u1 = models.Person(project_id="demo", name="User 1")
@ -610,7 +614,7 @@ class TestHistory(IhatemoneyTestCase):
models.db.session.commit() models.db.session.commit()
history_list = history.get_history(self.get_project("demo")) history_list = history.get_history(self.get_project("demo"))
assert len(history_list) == 5 self.assertEqual(len(history_list), 5)
# Change just the amount # Change just the amount
b1.amount = 5 b1.amount = 5
@ -619,8 +623,8 @@ class TestHistory(IhatemoneyTestCase):
history_list = history.get_history(self.get_project("demo")) history_list = history.get_history(self.get_project("demo"))
for entry in history_list: for entry in history_list:
if "prop_changed" in entry: if "prop_changed" in entry:
assert "owers" not in entry["prop_changed"] self.assertNotIn("owers", entry["prop_changed"])
assert len(history_list) == 6 self.assertEqual(len(history_list), 6)
def test_delete_history_with_project(self): def test_delete_history_with_project(self):
self.post_project("raclette", password="party") self.post_project("raclette", password="party")
@ -636,7 +640,6 @@ class TestHistory(IhatemoneyTestCase):
"what": "fromage à raclette", "what": "fromage à raclette",
"payer": 1, "payer": 1,
"payed_for": [1], "payed_for": [1],
"bill_type": "Expense",
"amount": "10", "amount": "10",
"original_currency": "EUR", "original_currency": "EUR",
}, },
@ -653,4 +656,8 @@ class TestHistory(IhatemoneyTestCase):
# History should be equal to project creation # History should be equal to project creation
history_list = history.get_history(self.get_project("raclette")) history_list = history.get_history(self.get_project("raclette"))
assert len(history_list) == 1 self.assertEqual(len(history_list), 1)
if __name__ == "__main__":
unittest.main()

View file

@ -3,11 +3,7 @@
DEBUG = False DEBUG = False
SQLALCHEMY_DATABASE_URI = 'sqlite:///budget.db' SQLALCHEMY_DATABASE_URI = 'sqlite:///budget.db'
SQLACHEMY_ECHO = DEBUG SQLACHEMY_ECHO = DEBUG
SITE_NAME = "I Hate Money"
SECRET_KEY = "supersecret" SECRET_KEY = "supersecret"
MAIL_DEFAULT_SENDER = "Budget manager <admin@example.com>" MAIL_DEFAULT_SENDER = "Budget manager <admin@example.com>"
PASSWORD_HASH_METHOD = "pbkdf2:sha1:1"
PASSWORD_HASH_SALT_LENGTH = 1

View file

@ -7,6 +7,3 @@ SQLACHEMY_ECHO = DEBUG
SECRET_KEY = "lalatra" SECRET_KEY = "lalatra"
MAIL_DEFAULT_SENDER = "Budget manager <admin@example.com>" MAIL_DEFAULT_SENDER = "Budget manager <admin@example.com>"
PASSWORD_HASH_METHOD = "pbkdf2:sha1:1"
PASSWORD_HASH_SALT_LENGTH = 1

View file

@ -1,49 +1,12 @@
import copy import copy
import json import json
import unittest
import pytest
from ihatemoney.tests.common.ihatemoney_testcase import IhatemoneyTestCase from ihatemoney.tests.common.ihatemoney_testcase import IhatemoneyTestCase
from ihatemoney.utils import list_of_dicts2csv, list_of_dicts2json from ihatemoney.utils import list_of_dicts2csv, list_of_dicts2json
@pytest.fixture
def import_data(request: pytest.FixtureRequest):
data = [
{
"date": "2017-01-01",
"what": "refund",
"amount": 13.33,
"payer_name": "tata",
"payer_weight": 1.0,
"bill_type": "Expense",
"owers": ["jeanne"],
},
{
"date": "2016-12-31",
"what": "red wine",
"bill_type": "Expense",
"amount": 200.0,
"payer_name": "jeanne",
"payer_weight": 1.0,
"owers": ["zorglub", "tata"],
},
{
"date": "2016-12-31",
"bill_type": "Expense",
"what": "fromage a raclette",
"amount": 10.0,
"payer_name": "zorglub",
"payer_weight": 2.0,
"owers": ["zorglub", "jeanne", "tata", "pepe"],
},
]
request.cls.data = data
yield data
class CommonTestCase(object): class CommonTestCase(object):
@pytest.mark.usefixtures("import_data")
class Import(IhatemoneyTestCase): class Import(IhatemoneyTestCase):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
@ -51,29 +14,26 @@ class CommonTestCase(object):
{ {
"date": "2017-01-01", "date": "2017-01-01",
"what": "refund", "what": "refund",
"bill_type": "Expense",
"amount": 13.33, "amount": 13.33,
"payer_name": "tata", "payer_name": "tata",
"payer_weight": 1.0, "payer_weight": 1.0,
"owers": ["jeanne"], "owers": ["fred"],
}, },
{ {
"date": "2016-12-31", "date": "2016-12-31",
"what": "red wine", "what": "red wine",
"bill_type": "Expense",
"amount": 200.0, "amount": 200.0,
"payer_name": "jeanne", "payer_name": "fred",
"payer_weight": 1.0, "payer_weight": 1.0,
"owers": ["zorglub", "tata"], "owers": ["zorglub", "tata"],
}, },
{ {
"date": "2016-12-31", "date": "2016-12-31",
"what": "a raclette", "what": "fromage a raclette",
"bill_type": "Expense",
"amount": 10.0, "amount": 10.0,
"payer_name": "zorglub", "payer_name": "zorglub",
"payer_weight": 2.0, "payer_weight": 2.0,
"owers": ["zorglub", "jeanne", "tata", "pepe"], "owers": ["zorglub", "fred", "tata", "pepe"],
}, },
] ]
@ -95,7 +55,7 @@ class CommonTestCase(object):
bills = project.get_pretty_bills() bills = project.get_pretty_bills()
# Check if all bills have been added # Check if all bills have been added
assert len(bills) == len(self.data) self.assertEqual(len(bills), len(self.data))
# Check if name of bills are ok # Check if name of bills are ok
b = [e["what"] for e in bills] b = [e["what"] for e in bills]
@ -103,23 +63,22 @@ class CommonTestCase(object):
ref = [e["what"] for e in self.data] ref = [e["what"] for e in self.data]
ref.sort() ref.sort()
assert b == ref self.assertEqual(b, ref)
# Check if other informations in bill are ok # Check if other informations in bill are ok
for d in self.data: for d in self.data:
for b in bills: for b in bills:
if b["what"] == d["what"]: if b["what"] == d["what"]:
assert b["payer_name"] == d["payer_name"] self.assertEqual(b["payer_name"], d["payer_name"])
assert b["amount"] == d["amount"] self.assertEqual(b["amount"], d["amount"])
assert b["currency"] == d["currency"] self.assertEqual(b["currency"], d["currency"])
assert b["payer_weight"] == d["payer_weight"] self.assertEqual(b["payer_weight"], d["payer_weight"])
assert b["date"] == d["date"] self.assertEqual(b["date"], d["date"])
assert b["bill_type"] == d["bill_type"]
list_project = [ower for ower in b["owers"]] list_project = [ower for ower in b["owers"]]
list_project.sort() list_project.sort()
list_json = [ower for ower in d["owers"]] list_json = [ower for ower in d["owers"]]
list_json.sort() list_json.sort()
assert list_project == list_json self.assertEqual(list_project, list_json)
def test_import_single_currency_in_empty_project_without_currency(self): def test_import_single_currency_in_empty_project_without_currency(self):
# Import JSON with a single currency in an empty project with no # Import JSON with a single currency in an empty project with no
@ -137,7 +96,7 @@ class CommonTestCase(object):
bills = project.get_pretty_bills() bills = project.get_pretty_bills()
# Check if all bills have been added # Check if all bills have been added
assert len(bills) == len(self.data) self.assertEqual(len(bills), len(self.data))
# Check if name of bills are ok # Check if name of bills are ok
b = [e["what"] for e in bills] b = [e["what"] for e in bills]
@ -145,24 +104,23 @@ class CommonTestCase(object):
ref = [e["what"] for e in self.data] ref = [e["what"] for e in self.data]
ref.sort() ref.sort()
assert b == ref self.assertEqual(b, ref)
# Check if other informations in bill are ok # Check if other informations in bill are ok
for d in self.data: for d in self.data:
for b in bills: for b in bills:
if b["what"] == d["what"]: if b["what"] == d["what"]:
assert b["payer_name"] == d["payer_name"] self.assertEqual(b["payer_name"], d["payer_name"])
assert b["amount"] == d["amount"] self.assertEqual(b["amount"], d["amount"])
# Currency should have been stripped # Currency should have been stripped
assert b["currency"] == "XXX" self.assertEqual(b["currency"], "XXX")
assert b["payer_weight"] == d["payer_weight"] self.assertEqual(b["payer_weight"], d["payer_weight"])
assert b["date"] == d["date"] self.assertEqual(b["date"], d["date"])
assert b["bill_type"] == d["bill_type"]
list_project = [ower for ower in b["owers"]] list_project = [ower for ower in b["owers"]]
list_project.sort() list_project.sort()
list_json = [ower for ower in d["owers"]] list_json = [ower for ower in d["owers"]]
list_json.sort() list_json.sort()
assert list_project == list_json self.assertEqual(list_project, list_json)
def test_import_multiple_currencies_in_empty_project_without_currency(self): def test_import_multiple_currencies_in_empty_project_without_currency(self):
# Import JSON with multiple currencies in an empty project with no # Import JSON with multiple currencies in an empty project with no
@ -180,7 +138,7 @@ class CommonTestCase(object):
bills = project.get_pretty_bills() bills = project.get_pretty_bills()
# Check that there are no bills # Check that there are no bills
assert len(bills) == 0 self.assertEqual(len(bills), 0)
def test_import_no_currency_in_empty_project_with_currency(self): def test_import_no_currency_in_empty_project_with_currency(self):
# Import JSON without currencies (from ihatemoney < 5) in an empty # Import JSON without currencies (from ihatemoney < 5) in an empty
@ -196,7 +154,7 @@ class CommonTestCase(object):
bills = project.get_pretty_bills() bills = project.get_pretty_bills()
# Check if all bills have been added # Check if all bills have been added
assert len(bills) == len(self.data) self.assertEqual(len(bills), len(self.data))
# Check if name of bills are ok # Check if name of bills are ok
b = [e["what"] for e in bills] b = [e["what"] for e in bills]
@ -204,24 +162,23 @@ class CommonTestCase(object):
ref = [e["what"] for e in self.data] ref = [e["what"] for e in self.data]
ref.sort() ref.sort()
assert b == ref self.assertEqual(b, ref)
# Check if other informations in bill are ok # Check if other informations in bill are ok
for d in self.data: for d in self.data:
for b in bills: for b in bills:
if b["what"] == d["what"]: if b["what"] == d["what"]:
assert b["payer_name"] == d["payer_name"] self.assertEqual(b["payer_name"], d["payer_name"])
assert b["amount"] == d["amount"] self.assertEqual(b["amount"], d["amount"])
# All bills are converted to default project currency # All bills are converted to default project currency
assert b["currency"] == "EUR" self.assertEqual(b["currency"], "EUR")
assert b["payer_weight"] == d["payer_weight"] self.assertEqual(b["payer_weight"], d["payer_weight"])
assert b["date"] == d["date"] self.assertEqual(b["date"], d["date"])
assert b["bill_type"] == d["bill_type"]
list_project = [ower for ower in b["owers"]] list_project = [ower for ower in b["owers"]]
list_project.sort() list_project.sort()
list_json = [ower for ower in d["owers"]] list_json = [ower for ower in d["owers"]]
list_json.sort() list_json.sort()
assert list_project == list_json self.assertEqual(list_project, list_json)
def test_import_no_currency_in_empty_project_without_currency(self): def test_import_no_currency_in_empty_project_without_currency(self):
# Import JSON without currencies (from ihatemoney < 5) in an empty # Import JSON without currencies (from ihatemoney < 5) in an empty
@ -237,7 +194,7 @@ class CommonTestCase(object):
bills = project.get_pretty_bills() bills = project.get_pretty_bills()
# Check if all bills have been added # Check if all bills have been added
assert len(bills) == len(self.data) self.assertEqual(len(bills), len(self.data))
# Check if name of bills are ok # Check if name of bills are ok
b = [e["what"] for e in bills] b = [e["what"] for e in bills]
@ -245,23 +202,22 @@ class CommonTestCase(object):
ref = [e["what"] for e in self.data] ref = [e["what"] for e in self.data]
ref.sort() ref.sort()
assert b == ref self.assertEqual(b, ref)
# Check if other informations in bill are ok # Check if other informations in bill are ok
for d in self.data: for d in self.data:
for b in bills: for b in bills:
if b["what"] == d["what"]: if b["what"] == d["what"]:
assert b["payer_name"] == d["payer_name"] self.assertEqual(b["payer_name"], d["payer_name"])
assert b["amount"] == d["amount"] self.assertEqual(b["amount"], d["amount"])
assert b["currency"] == "XXX" self.assertEqual(b["currency"], "XXX")
assert b["payer_weight"] == d["payer_weight"] self.assertEqual(b["payer_weight"], d["payer_weight"])
assert b["date"] == d["date"] self.assertEqual(b["date"], d["date"])
assert b["bill_type"] == d["bill_type"]
list_project = [ower for ower in b["owers"]] list_project = [ower for ower in b["owers"]]
list_project.sort() list_project.sort()
list_json = [ower for ower in d["owers"]] list_json = [ower for ower in d["owers"]]
list_json.sort() list_json.sort()
assert list_project == list_json self.assertEqual(list_project, list_json)
def test_import_partial_project(self): def test_import_partial_project(self):
# Import a JSON in a project with already existing data # Import a JSON in a project with already existing data
@ -274,14 +230,13 @@ class CommonTestCase(object):
self.client.post( self.client.post(
"/raclette/members/add", data={"name": "zorglub", "weight": 2} "/raclette/members/add", data={"name": "zorglub", "weight": 2}
) )
self.client.post("/raclette/members/add", data={"name": "jeanne"}) self.client.post("/raclette/members/add", data={"name": "fred"})
self.client.post("/raclette/members/add", data={"name": "tata"}) self.client.post("/raclette/members/add", data={"name": "tata"})
self.client.post( self.client.post(
"/raclette/add", "/raclette/add",
data={ data={
"date": "2016-12-31", "date": "2016-12-31",
"what": "red wine", "what": "red wine",
"bill_type": "Expense",
"payer": 2, "payer": 2,
"payed_for": [1, 3], "payed_for": [1, 3],
"amount": "200", "amount": "200",
@ -295,7 +250,7 @@ class CommonTestCase(object):
bills = project.get_pretty_bills() bills = project.get_pretty_bills()
# Check if all bills have been added # Check if all bills have been added
assert len(bills) == len(self.data) self.assertEqual(len(bills), len(self.data))
# Check if name of bills are ok # Check if name of bills are ok
b = [e["what"] for e in bills] b = [e["what"] for e in bills]
@ -303,23 +258,22 @@ class CommonTestCase(object):
ref = [e["what"] for e in self.data] ref = [e["what"] for e in self.data]
ref.sort() ref.sort()
assert b == ref self.assertEqual(b, ref)
# Check if other informations in bill are ok # Check if other informations in bill are ok
for d in self.data: for d in self.data:
for b in bills: for b in bills:
if b["what"] == d["what"]: if b["what"] == d["what"]:
assert b["payer_name"] == d["payer_name"] self.assertEqual(b["payer_name"], d["payer_name"])
assert b["amount"] == d["amount"] self.assertEqual(b["amount"], d["amount"])
assert b["currency"] == d["currency"] self.assertEqual(b["currency"], d["currency"])
assert b["payer_weight"] == d["payer_weight"] self.assertEqual(b["payer_weight"], d["payer_weight"])
assert b["date"] == d["date"] self.assertEqual(b["date"], d["date"])
assert b["bill_type"] == d["bill_type"]
list_project = [ower for ower in b["owers"]] list_project = [ower for ower in b["owers"]]
list_project.sort() list_project.sort()
list_json = [ower for ower in d["owers"]] list_json = [ower for ower in d["owers"]]
list_json.sort() list_json.sort()
assert list_project == list_json self.assertEqual(list_project, list_json)
def test_import_wrong_data(self): def test_import_wrong_data(self):
self.post_project("raclette") self.post_project("raclette")
@ -338,10 +292,9 @@ class CommonTestCase(object):
{ {
"date": "2017-01-01", "date": "2017-01-01",
"what": "refund", "what": "refund",
"bill_type": "Reimbursement",
"payer_name": "tata", "payer_name": "tata",
"payer_weight": 1.0, "payer_weight": 1.0,
"owers": ["jeanne"], "owers": ["fred"],
} }
] ]
for data in [data_wrong_keys, data_amount_missing]: for data in [data_wrong_keys, data_amount_missing]:
@ -349,7 +302,7 @@ class CommonTestCase(object):
self.import_project("raclette", self.generate_form_data(data), 400) self.import_project("raclette", self.generate_form_data(data), 400)
class TestExport(IhatemoneyTestCase): class ExportTestCase(IhatemoneyTestCase):
def test_export(self): def test_export(self):
# Export a simple project without currencies # Export a simple project without currencies
@ -357,7 +310,7 @@ class TestExport(IhatemoneyTestCase):
# add participants # add participants
self.client.post("/raclette/members/add", data={"name": "zorglub", "weight": 2}) self.client.post("/raclette/members/add", data={"name": "zorglub", "weight": 2})
self.client.post("/raclette/members/add", data={"name": "jeanne"}) self.client.post("/raclette/members/add", data={"name": "fred"})
self.client.post("/raclette/members/add", data={"name": "tata"}) self.client.post("/raclette/members/add", data={"name": "tata"})
self.client.post("/raclette/members/add", data={"name": "pépé"}) self.client.post("/raclette/members/add", data={"name": "pépé"})
@ -366,8 +319,7 @@ class TestExport(IhatemoneyTestCase):
"/raclette/add", "/raclette/add",
data={ data={
"date": "2016-12-31", "date": "2016-12-31",
"bill_type": "Expense", "what": "fromage à raclette",
"what": "à raclette",
"payer": 1, "payer": 1,
"payed_for": [1, 2, 3, 4], "payed_for": [1, 2, 3, 4],
"amount": "10.0", "amount": "10.0",
@ -378,7 +330,6 @@ class TestExport(IhatemoneyTestCase):
"/raclette/add", "/raclette/add",
data={ data={
"date": "2016-12-31", "date": "2016-12-31",
"bill_type": "Expense",
"what": "red wine", "what": "red wine",
"payer": 2, "payer": 2,
"payed_for": [1, 3], "payed_for": [1, 3],
@ -390,7 +341,6 @@ class TestExport(IhatemoneyTestCase):
"/raclette/add", "/raclette/add",
data={ data={
"date": "2017-01-01", "date": "2017-01-01",
"bill_type": "Reimbursement",
"what": "refund", "what": "refund",
"payer": 3, "payer": 3,
"payed_for": [2], "payed_for": [2],
@ -403,49 +353,48 @@ class TestExport(IhatemoneyTestCase):
expected = [ expected = [
{ {
"date": "2017-01-01", "date": "2017-01-01",
"bill_type": "Reimbursement",
"what": "refund", "what": "refund",
"amount": 13.33, "amount": 13.33,
"currency": "XXX", "currency": "XXX",
"payer_name": "tata", "payer_name": "tata",
"payer_weight": 1.0, "payer_weight": 1.0,
"owers": ["jeanne"], "owers": ["fred"],
}, },
{ {
"date": "2016-12-31", "date": "2016-12-31",
"bill_type": "Expense",
"what": "red wine", "what": "red wine",
"amount": 200.0, "amount": 200.0,
"currency": "XXX", "currency": "XXX",
"payer_name": "jeanne", "payer_name": "fred",
"payer_weight": 1.0, "payer_weight": 1.0,
"owers": ["zorglub", "tata"], "owers": ["zorglub", "tata"],
}, },
{ {
"date": "2016-12-31", "date": "2016-12-31",
"bill_type": "Expense", "what": "fromage \xe0 raclette",
"what": "\xe0 raclette",
"amount": 10.0, "amount": 10.0,
"currency": "XXX", "currency": "XXX",
"payer_name": "zorglub", "payer_name": "zorglub",
"payer_weight": 2.0, "payer_weight": 2.0,
"owers": ["zorglub", "jeanne", "tata", "p\xe9p\xe9"], "owers": ["zorglub", "fred", "tata", "p\xe9p\xe9"],
}, },
] ]
assert json.loads(resp.data.decode("utf-8")) == expected self.assertEqual(json.loads(resp.data.decode("utf-8")), expected)
# generate csv export of bills # generate csv export of bills
resp = self.client.get("/raclette/export/bills.csv") resp = self.client.get("/raclette/export/bills.csv")
expected = [ expected = [
"date,what,bill_type,amount,currency,payer_name,payer_weight,owers", "date,what,amount,currency,payer_name,payer_weight,owers",
"2017-01-01,refund,Reimbursement,XXX,13.33,tata,1.0,jeanne", "2017-01-01,refund,XXX,13.33,tata,1.0,fred",
'2016-12-31,red wine,Expense,XXX,200.0,jeanne,1.0,"zorglub, tata"', '2016-12-31,red wine,XXX,200.0,fred,1.0,"zorglub, tata"',
'2016-12-31,à raclette,Expense,10.0,XXX,zorglub,2.0,"zorglub, jeanne, tata, pépé"', '2016-12-31,fromage à raclette,10.0,XXX,zorglub,2.0,"zorglub, fred, tata, pépé"',
] ]
received_lines = resp.data.decode("utf-8").split("\n") received_lines = resp.data.decode("utf-8").split("\n")
for i, line in enumerate(expected): for i, line in enumerate(expected):
assert set(line.split(",")) == set(received_lines[i].strip("\r").split(",")) self.assertEqual(
set(line.split(",")), set(received_lines[i].strip("\r").split(","))
)
# generate json export of transactions # generate json export of transactions
resp = self.client.get("/raclette/export/transactions.json") resp = self.client.get("/raclette/export/transactions.json")
@ -453,45 +402,46 @@ class TestExport(IhatemoneyTestCase):
{ {
"amount": 2.00, "amount": 2.00,
"currency": "XXX", "currency": "XXX",
"receiver": "jeanne", "receiver": "fred",
"ower": "p\xe9p\xe9", "ower": "p\xe9p\xe9",
}, },
{"amount": 55.34, "currency": "XXX", "receiver": "jeanne", "ower": "tata"}, {"amount": 55.34, "currency": "XXX", "receiver": "fred", "ower": "tata"},
{ {
"amount": 127.33, "amount": 127.33,
"currency": "XXX", "currency": "XXX",
"receiver": "jeanne", "receiver": "fred",
"ower": "zorglub", "ower": "zorglub",
}, },
] ]
assert json.loads(resp.data.decode("utf-8")) == expected self.assertEqual(json.loads(resp.data.decode("utf-8")), expected)
# generate csv export of transactions # generate csv export of transactions
resp = self.client.get("/raclette/export/transactions.csv") resp = self.client.get("/raclette/export/transactions.csv")
expected = [ expected = [
"amount,currency,receiver,ower", "amount,currency,receiver,ower",
"2.0,XXX,jeanne,pépé", "2.0,XXX,fred,pépé",
"55.34,XXX,jeanne,tata", "55.34,XXX,fred,tata",
"127.33,XXX,jeanne,zorglub", "127.33,XXX,fred,zorglub",
] ]
received_lines = resp.data.decode("utf-8").split("\n") received_lines = resp.data.decode("utf-8").split("\n")
for i, line in enumerate(expected): for i, line in enumerate(expected):
assert set(line.split(",")) == set(received_lines[i].strip("\r").split(",")) self.assertEqual(
set(line.split(",")), set(received_lines[i].strip("\r").split(","))
)
# wrong export_format should return a 404 # wrong export_format should return a 404
resp = self.client.get("/raclette/export/transactions.wrong") resp = self.client.get("/raclette/export/transactions.wrong")
assert resp.status_code == 404 self.assertEqual(resp.status_code, 404)
@pytest.mark.skip(reason="Currency conversion is broken")
def test_export_with_currencies(self): def test_export_with_currencies(self):
self.post_project("raclette", default_currency="EUR") self.post_project("raclette", default_currency="EUR")
# add participants # add participants
self.client.post("/raclette/members/add", data={"name": "zorglub", "weight": 2}) self.client.post("/raclette/members/add", data={"name": "zorglub", "weight": 2})
self.client.post("/raclette/members/add", data={"name": "jeanne"}) self.client.post("/raclette/members/add", data={"name": "fred"})
self.client.post("/raclette/members/add", data={"name": "tata"}) self.client.post("/raclette/members/add", data={"name": "tata"})
self.client.post("/raclette/members/add", data={"name": "pépé"}) self.client.post("/raclette/members/add", data={"name": "pépé"})
@ -500,8 +450,7 @@ class TestExport(IhatemoneyTestCase):
"/raclette/add", "/raclette/add",
data={ data={
"date": "2016-12-31", "date": "2016-12-31",
"what": "à raclette", "what": "fromage à raclette",
"bill_type": "Expense",
"payer": 1, "payer": 1,
"payed_for": [1, 2, 3, 4], "payed_for": [1, 2, 3, 4],
"amount": "10.0", "amount": "10.0",
@ -514,7 +463,6 @@ class TestExport(IhatemoneyTestCase):
data={ data={
"date": "2016-12-31", "date": "2016-12-31",
"what": "poutine from Québec", "what": "poutine from Québec",
"bill_type": "Expense",
"payer": 2, "payer": 2,
"payed_for": [1, 3], "payed_for": [1, 3],
"amount": "100", "amount": "100",
@ -527,7 +475,6 @@ class TestExport(IhatemoneyTestCase):
data={ data={
"date": "2017-01-01", "date": "2017-01-01",
"what": "refund", "what": "refund",
"bill_type": "Reimbursement",
"payer": 3, "payer": 3,
"payed_for": [2], "payed_for": [2],
"amount": "13.33", "amount": "13.33",
@ -541,48 +488,47 @@ class TestExport(IhatemoneyTestCase):
{ {
"date": "2017-01-01", "date": "2017-01-01",
"what": "refund", "what": "refund",
"bill_type": "Reimbursement",
"amount": 13.33, "amount": 13.33,
"currency": "EUR", "currency": "EUR",
"payer_name": "tata", "payer_name": "tata",
"payer_weight": 1.0, "payer_weight": 1.0,
"owers": ["jeanne"], "owers": ["fred"],
}, },
{ {
"date": "2016-12-31", "date": "2016-12-31",
"what": "poutine from Qu\xe9bec", "what": "poutine from Qu\xe9bec",
"bill_type": "Expense",
"amount": 100.0, "amount": 100.0,
"currency": "CAD", "currency": "CAD",
"payer_name": "jeanne", "payer_name": "fred",
"payer_weight": 1.0, "payer_weight": 1.0,
"owers": ["zorglub", "tata"], "owers": ["zorglub", "tata"],
}, },
{ {
"date": "2016-12-31", "date": "2016-12-31",
"what": "fromage \xe0 raclette", "what": "fromage \xe0 raclette",
"bill_type": "Expense",
"amount": 10.0, "amount": 10.0,
"currency": "EUR", "currency": "EUR",
"payer_name": "zorglub", "payer_name": "zorglub",
"payer_weight": 2.0, "payer_weight": 2.0,
"owers": ["zorglub", "jeanne", "tata", "p\xe9p\xe9"], "owers": ["zorglub", "fred", "tata", "p\xe9p\xe9"],
}, },
] ]
assert json.loads(resp.data.decode("utf-8")) == expected self.assertEqual(json.loads(resp.data.decode("utf-8")), expected)
# generate csv export of bills # generate csv export of bills
resp = self.client.get("/raclette/export/bills.csv") resp = self.client.get("/raclette/export/bills.csv")
expected = [ expected = [
"date,what,bill_type,amount,currency,payer_name,payer_weight,owers", "date,what,amount,currency,payer_name,payer_weight,owers",
"2017-01-01,refund,Reimbursement,13.33,EUR,tata,1.0,jeanne", "2017-01-01,refund,13.33,EUR,tata,1.0,fred",
'2016-12-31,poutine from Québec,Expense,100.0,CAD,jeanne,1.0,"zorglub, tata"', '2016-12-31,poutine from Québec,100.0,CAD,fred,1.0,"zorglub, tata"',
'2016-12-31,à raclette,Expense,10.0,EUR,zorglub,2.0,"zorglub, jeanne, tata, pépé"', '2016-12-31,fromage à raclette,10.0,EUR,zorglub,2.0,"zorglub, fred, tata, pépé"',
] ]
received_lines = resp.data.decode("utf-8").split("\n") received_lines = resp.data.decode("utf-8").split("\n")
for i, line in enumerate(expected): for i, line in enumerate(expected):
assert set(line.split(",")) == set(received_lines[i].strip("\r").split(",")) self.assertEqual(
set(line.split(",")), set(received_lines[i].strip("\r").split(","))
)
# generate json export of transactions (in EUR!) # generate json export of transactions (in EUR!)
resp = self.client.get("/raclette/export/transactions.json") resp = self.client.get("/raclette/export/transactions.json")
@ -590,33 +536,30 @@ class TestExport(IhatemoneyTestCase):
{ {
"amount": 2.00, "amount": 2.00,
"currency": "EUR", "currency": "EUR",
"receiver": "jeanne", "receiver": "fred",
"ower": "p\xe9p\xe9", "ower": "p\xe9p\xe9",
}, },
{"amount": 10.89, "currency": "EUR", "receiver": "jeanne", "ower": "tata"}, {"amount": 10.89, "currency": "EUR", "receiver": "fred", "ower": "tata"},
{ {"amount": 38.45, "currency": "EUR", "receiver": "fred", "ower": "zorglub"},
"amount": 38.45,
"currency": "EUR",
"receiver": "jeanne",
"ower": "zorglub",
},
] ]
assert json.loads(resp.data.decode("utf-8")) == expected self.assertEqual(json.loads(resp.data.decode("utf-8")), expected)
# generate csv export of transactions # generate csv export of transactions
resp = self.client.get("/raclette/export/transactions.csv") resp = self.client.get("/raclette/export/transactions.csv")
expected = [ expected = [
"amount,currency,receiver,ower", "amount,currency,receiver,ower",
"2.0,EUR,jeanne,pépé", "2.0,EUR,fred,pépé",
"10.89,EUR,jeanne,tata", "10.89,EUR,fred,tata",
"38.45,EUR,jeanne,zorglub", "38.45,EUR,fred,zorglub",
] ]
received_lines = resp.data.decode("utf-8").split("\n") received_lines = resp.data.decode("utf-8").split("\n")
for i, line in enumerate(expected): for i, line in enumerate(expected):
assert set(line.split(",")) == set(received_lines[i].strip("\r").split(",")) self.assertEqual(
set(line.split(",")), set(received_lines[i].strip("\r").split(","))
)
# Change project currency to CAD # Change project currency to CAD
project = self.get_project("raclette") project = self.get_project("raclette")
@ -628,33 +571,30 @@ class TestExport(IhatemoneyTestCase):
{ {
"amount": 3.00, "amount": 3.00,
"currency": "CAD", "currency": "CAD",
"receiver": "jeanne", "receiver": "fred",
"ower": "p\xe9p\xe9", "ower": "p\xe9p\xe9",
}, },
{"amount": 16.34, "currency": "CAD", "receiver": "jeanne", "ower": "tata"}, {"amount": 16.34, "currency": "CAD", "receiver": "fred", "ower": "tata"},
{ {"amount": 57.67, "currency": "CAD", "receiver": "fred", "ower": "zorglub"},
"amount": 57.67,
"currency": "CAD",
"receiver": "jeanne",
"ower": "zorglub",
},
] ]
assert json.loads(resp.data.decode("utf-8")) == expected self.assertEqual(json.loads(resp.data.decode("utf-8")), expected)
# generate csv export of transactions # generate csv export of transactions
resp = self.client.get("/raclette/export/transactions.csv") resp = self.client.get("/raclette/export/transactions.csv")
expected = [ expected = [
"amount,currency,receiver,ower", "amount,currency,receiver,ower",
"3.0,CAD,jeanne,pépé", "3.0,CAD,fred,pépé",
"16.34,CAD,jeanne,tata", "16.34,CAD,fred,tata",
"57.67,CAD,jeanne,zorglub", "57.67,CAD,fred,zorglub",
] ]
received_lines = resp.data.decode("utf-8").split("\n") received_lines = resp.data.decode("utf-8").split("\n")
for i, line in enumerate(expected): for i, line in enumerate(expected):
assert set(line.split(",")) == set(received_lines[i].strip("\r").split(",")) self.assertEqual(
set(line.split(",")), set(received_lines[i].strip("\r").split(","))
)
def test_export_escape_formulae(self): def test_export_escape_formulae(self):
self.post_project("raclette", default_currency="EUR") self.post_project("raclette", default_currency="EUR")
@ -668,7 +608,6 @@ class TestExport(IhatemoneyTestCase):
data={ data={
"date": "2016-12-31", "date": "2016-12-31",
"what": "=COS(36)", "what": "=COS(36)",
"bill_type": "Expense",
"payer": 1, "payer": 1,
"payed_for": [1], "payed_for": [1],
"amount": "10.0", "amount": "10.0",
@ -679,23 +618,29 @@ class TestExport(IhatemoneyTestCase):
# generate csv export of bills # generate csv export of bills
resp = self.client.get("/raclette/export/bills.csv") resp = self.client.get("/raclette/export/bills.csv")
expected = [ expected = [
"date,what,bill_type,amount,currency,payer_name,payer_weight,owers", "date,what,amount,currency,payer_name,payer_weight,owers",
"2016-12-31,'=COS(36),Expense,10.0,EUR,zorglub,1.0,zorglub", "2016-12-31,'=COS(36),10.0,EUR,zorglub,1.0,zorglub",
] ]
received_lines = resp.data.decode("utf-8").split("\n") received_lines = resp.data.decode("utf-8").split("\n")
for i, line in enumerate(expected): for i, line in enumerate(expected):
assert set(line.split(",")) == set(received_lines[i].strip("\r").split(",")) self.assertEqual(
set(line.split(",")), set(received_lines[i].strip("\r").split(","))
)
class TestImportJSON(CommonTestCase.Import): class ImportTestCaseJSON(CommonTestCase.Import):
def generate_form_data(self, data): def generate_form_data(self, data):
return {"file": (list_of_dicts2json(data), "test.json")} return {"file": (list_of_dicts2json(data), "test.json")}
class TestImportCSV(CommonTestCase.Import): class ImportTestCaseCSV(CommonTestCase.Import):
def generate_form_data(self, data): def generate_form_data(self, data):
formatted_data = copy.deepcopy(data) formatted_data = copy.deepcopy(data)
for d in formatted_data: for d in formatted_data:
d["owers"] = ", ".join([o for o in d.get("owers", [])]) d["owers"] = ", ".join([o for o in d.get("owers", [])])
return {"file": (list_of_dicts2csv(formatted_data), "test.csv")} return {"file": (list_of_dicts2csv(formatted_data), "test.csv")}
if __name__ == "__main__":
unittest.main()

View file

@ -1,6 +1,7 @@
import os import os
import smtplib import smtplib
import socket import socket
import unittest
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
from sqlalchemy import orm from sqlalchemy import orm
@ -8,12 +9,7 @@ from werkzeug.security import check_password_hash
from ihatemoney import models from ihatemoney import models
from ihatemoney.currency_convertor import CurrencyConverter from ihatemoney.currency_convertor import CurrencyConverter
from ihatemoney.manage import ( from ihatemoney.manage import delete_project, generate_config, password_hash
delete_project,
generate_config,
get_project_count,
password_hash,
)
from ihatemoney.run import load_configuration from ihatemoney.run import load_configuration
from ihatemoney.tests.common.ihatemoney_testcase import BaseTestCase, IhatemoneyTestCase from ihatemoney.tests.common.ihatemoney_testcase import BaseTestCase, IhatemoneyTestCase
@ -23,18 +19,19 @@ os.environ.pop("IHATEMONEY_SETTINGS_FILE_PATH", None)
__HERE__ = os.path.dirname(os.path.abspath(__file__)) __HERE__ = os.path.dirname(os.path.abspath(__file__))
class TestConfiguration(BaseTestCase): class ConfigurationTestCase(BaseTestCase):
def test_default_configuration(self): def test_default_configuration(self):
"""Test that default settings are loaded when no other configuration file is specified""" """Test that default settings are loaded when no other configuration file is specified"""
assert not self.app.config["DEBUG"] self.assertFalse(self.app.config["DEBUG"])
assert not self.app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] self.assertFalse(self.app.config["SQLALCHEMY_TRACK_MODIFICATIONS"])
assert self.app.config["MAIL_DEFAULT_SENDER"] == ( self.assertEqual(
"Budget manager <admin@example.com>" self.app.config["MAIL_DEFAULT_SENDER"],
("Budget manager <admin@example.com>"),
) )
assert self.app.config["ACTIVATE_DEMO_PROJECT"] self.assertTrue(self.app.config["ACTIVATE_DEMO_PROJECT"])
assert self.app.config["ALLOW_PUBLIC_PROJECT_CREATION"] self.assertTrue(self.app.config["ALLOW_PUBLIC_PROJECT_CREATION"])
assert not self.app.config["ACTIVATE_ADMIN_DASHBOARD"] self.assertFalse(self.app.config["ACTIVATE_ADMIN_DASHBOARD"])
assert not self.app.config["ENABLE_CAPTCHA"] self.assertFalse(self.app.config["ENABLE_CAPTCHA"])
def test_env_var_configuration_file(self): def test_env_var_configuration_file(self):
"""Test that settings are loaded from a configuration file specified """Test that settings are loaded from a configuration file specified
@ -43,7 +40,7 @@ class TestConfiguration(BaseTestCase):
__HERE__, "ihatemoney_envvar.cfg" __HERE__, "ihatemoney_envvar.cfg"
) )
load_configuration(self.app) load_configuration(self.app)
assert self.app.config["SECRET_KEY"] == "lalatra" self.assertEqual(self.app.config["SECRET_KEY"], "lalatra")
# Test that the specified configuration file is loaded # Test that the specified configuration file is loaded
# even if the default configuration file ihatemoney.cfg exists # even if the default configuration file ihatemoney.cfg exists
@ -53,7 +50,7 @@ class TestConfiguration(BaseTestCase):
) )
self.app.config.root_path = __HERE__ self.app.config.root_path = __HERE__
load_configuration(self.app) load_configuration(self.app)
assert self.app.config["SECRET_KEY"] == "lalatra" self.assertEqual(self.app.config["SECRET_KEY"], "lalatra")
os.environ.pop("IHATEMONEY_SETTINGS_FILE_PATH", None) os.environ.pop("IHATEMONEY_SETTINGS_FILE_PATH", None)
@ -62,10 +59,10 @@ class TestConfiguration(BaseTestCase):
in the current directory.""" in the current directory."""
self.app.config.root_path = __HERE__ self.app.config.root_path = __HERE__
load_configuration(self.app) load_configuration(self.app)
assert self.app.config["SECRET_KEY"] == "supersecret" self.assertEqual(self.app.config["SECRET_KEY"], "supersecret")
class TestServer(IhatemoneyTestCase): class ServerTestCase(IhatemoneyTestCase):
def test_homepage(self): def test_homepage(self):
# See https://github.com/spiral-project/ihatemoney/pull/358 # See https://github.com/spiral-project/ihatemoney/pull/358
self.app.config["APPLICATION_ROOT"] = "/" self.app.config["APPLICATION_ROOT"] = "/"
@ -83,7 +80,7 @@ class TestServer(IhatemoneyTestCase):
self.assertStatus(200, req) self.assertStatus(200, req)
class TestCommand(BaseTestCase): class CommandTestCase(BaseTestCase):
def test_generate_config(self): def test_generate_config(self):
"""Simply checks that all config file generation """Simply checks that all config file generation
- raise no exception - raise no exception
@ -92,32 +89,32 @@ class TestCommand(BaseTestCase):
runner = self.app.test_cli_runner() runner = self.app.test_cli_runner()
for config_file in generate_config.params[0].type.choices: for config_file in generate_config.params[0].type.choices:
result = runner.invoke(generate_config, config_file) result = runner.invoke(generate_config, config_file)
assert len(result.output.strip()) != 0 self.assertNotEqual(len(result.output.strip()), 0)
def test_generate_password_hash(self): def test_generate_password_hash(self):
runner = self.app.test_cli_runner() runner = self.app.test_cli_runner()
with patch("getpass.getpass", new=lambda prompt: "secret"): with patch("getpass.getpass", new=lambda prompt: "secret"):
result = runner.invoke(password_hash) result = runner.invoke(password_hash)
assert check_password_hash(result.output.strip(), "secret") self.assertTrue(check_password_hash(result.output.strip(), "secret"))
def test_demo_project_deletion(self): def test_demo_project_deletion(self):
self.create_project("demo") self.create_project("demo")
assert self.get_project("demo").name == "demo" self.assertEqual(self.get_project("demo").name, "demo")
runner = self.app.test_cli_runner() runner = self.app.test_cli_runner()
runner.invoke(delete_project, "demo") runner.invoke(delete_project, "demo")
assert len(models.Project.query.all()) == 0 self.assertEqual(len(models.Project.query.all()), 0)
class TestModels(IhatemoneyTestCase): class ModelsTestCase(IhatemoneyTestCase):
def test_weighted_bills(self): def test_weighted_bills(self):
"""Test the SQL request that fetch all bills and weights""" """Test the SQL request that fetch all bills and weights"""
self.post_project("raclette") self.post_project("raclette")
# add members # add members
self.client.post("/raclette/members/add", data={"name": "zorglub", "weight": 2}) self.client.post("/raclette/members/add", data={"name": "zorglub", "weight": 2})
self.client.post("/raclette/members/add", data={"name": "jeanne"}) self.client.post("/raclette/members/add", data={"name": "fred"})
self.client.post("/raclette/members/add", data={"name": "tata"}) self.client.post("/raclette/members/add", data={"name": "tata"})
# Add a member with a balance=0 : # Add a member with a balance=0 :
self.client.post("/raclette/members/add", data={"name": "pépé"}) self.client.post("/raclette/members/add", data={"name": "pépé"})
@ -130,7 +127,6 @@ class TestModels(IhatemoneyTestCase):
"what": "fromage à raclette", "what": "fromage à raclette",
"payer": 1, "payer": 1,
"payed_for": [1, 2, 3], "payed_for": [1, 2, 3],
"bill_type": "Expense",
"amount": "10.0", "amount": "10.0",
}, },
) )
@ -142,7 +138,6 @@ class TestModels(IhatemoneyTestCase):
"what": "red wine", "what": "red wine",
"payer": 2, "payer": 2,
"payed_for": [1], "payed_for": [1],
"bill_type": "Expense",
"amount": "20", "amount": "20",
}, },
) )
@ -154,7 +149,6 @@ class TestModels(IhatemoneyTestCase):
"what": "delicatessen", "what": "delicatessen",
"payer": 1, "payer": 1,
"payed_for": [1, 2], "payed_for": [1, 2],
"bill_type": "Expense",
"amount": "10", "amount": "10",
}, },
) )
@ -162,20 +156,20 @@ class TestModels(IhatemoneyTestCase):
for weight, bill in project.get_bill_weights().all(): for weight, bill in project.get_bill_weights().all():
if bill.what == "red wine": if bill.what == "red wine":
pay_each_expected = 20 / 2 pay_each_expected = 20 / 2
assert bill.amount / weight == pay_each_expected self.assertEqual(bill.amount / weight, pay_each_expected)
if bill.what == "fromage à raclette": if bill.what == "fromage à raclette":
pay_each_expected = 10 / 4 pay_each_expected = 10 / 4
assert bill.amount / weight == pay_each_expected self.assertEqual(bill.amount / weight, pay_each_expected)
if bill.what == "delicatessen": if bill.what == "delicatessen":
pay_each_expected = 10 / 3 pay_each_expected = 10 / 3
assert bill.amount / weight == pay_each_expected self.assertEqual(bill.amount / weight, pay_each_expected)
def test_bill_pay_each(self): def test_bill_pay_each(self):
self.post_project("raclette") self.post_project("raclette")
# add members # add members
self.client.post("/raclette/members/add", data={"name": "zorglub", "weight": 2}) self.client.post("/raclette/members/add", data={"name": "zorglub", "weight": 2})
self.client.post("/raclette/members/add", data={"name": "jeanne"}) self.client.post("/raclette/members/add", data={"name": "fred"})
self.client.post("/raclette/members/add", data={"name": "tata"}) self.client.post("/raclette/members/add", data={"name": "tata"})
# Add a member with a balance=0 : # Add a member with a balance=0 :
self.client.post("/raclette/members/add", data={"name": "pépé"}) self.client.post("/raclette/members/add", data={"name": "pépé"})
@ -188,7 +182,6 @@ class TestModels(IhatemoneyTestCase):
"what": "fromage à raclette", "what": "fromage à raclette",
"payer": 1, "payer": 1,
"payed_for": [1, 2, 3], "payed_for": [1, 2, 3],
"bill_type": "Expense",
"amount": "10.0", "amount": "10.0",
}, },
) )
@ -200,7 +193,6 @@ class TestModels(IhatemoneyTestCase):
"what": "red wine", "what": "red wine",
"payer": 2, "payer": 2,
"payed_for": [1], "payed_for": [1],
"bill_type": "Expense",
"amount": "20", "amount": "20",
}, },
) )
@ -212,7 +204,6 @@ class TestModels(IhatemoneyTestCase):
"what": "delicatessen", "what": "delicatessen",
"payer": 1, "payer": 1,
"payed_for": [1, 2], "payed_for": [1, 2],
"bill_type": "Expense",
"amount": "10", "amount": "10",
}, },
) )
@ -225,75 +216,16 @@ class TestModels(IhatemoneyTestCase):
for bill in zorglub_bills.all(): for bill in zorglub_bills.all():
if bill.what == "red wine": if bill.what == "red wine":
pay_each_expected = 20 / 2 pay_each_expected = 20 / 2
assert bill.pay_each() == pay_each_expected self.assertEqual(bill.pay_each(), pay_each_expected)
if bill.what == "fromage à raclette": if bill.what == "fromage à raclette":
pay_each_expected = 10 / 4 pay_each_expected = 10 / 4
assert bill.pay_each() == pay_each_expected self.assertEqual(bill.pay_each(), pay_each_expected)
if bill.what == "delicatessen": if bill.what == "delicatessen":
pay_each_expected = 10 / 3 pay_each_expected = 10 / 3
assert bill.pay_each() == pay_each_expected self.assertEqual(bill.pay_each(), pay_each_expected)
def test_demo_project_count(self):
"""Test command the get-project-count"""
self.post_project("raclette")
# add members
self.client.post("/raclette/members/add", data={"name": "zorglub", "weight": 2})
self.client.post("/raclette/members/add", data={"name": "fred"})
self.client.post("/raclette/members/add", data={"name": "tata"})
self.client.post("/raclette/members/add", data={"name": "pépé"})
# create bills
self.client.post(
"/raclette/add",
data={
"date": "2011-08-10",
"what": "fromage à raclette",
"payer": 1,
"payed_for": [1, 2, 3],
"amount": "10.0",
},
)
self.client.post(
"/raclette/add",
data={
"date": "2011-08-10",
"what": "red wine",
"payer": 2,
"payed_for": [1],
"amount": "20",
},
)
assert self.get_project("raclette").has_bills()
# Now check the different parameters
runner = self.app.test_cli_runner()
result0 = runner.invoke(get_project_count)
assert result0.output.strip() == "Number of projects: 1"
# With more than 1 bill, without printing emails
result1 = runner.invoke(get_project_count, "False 1")
assert result1.output.strip() == "Number of projects: 1"
# With more than 2 bill, without printing emails
result2 = runner.invoke(get_project_count, "False 2")
assert result2.output.strip() == "Number of projects: 0"
# With more than 0 days old
result3 = runner.invoke(get_project_count, "False 0 0")
assert result3.output.strip() == "Number of projects: 0"
result4 = runner.invoke(get_project_count, "False 0 20000")
assert result4.output.strip() == "Number of projects: 1"
# Print emails
result5 = runner.invoke(get_project_count, "True")
assert "raclette@notmyidea.org" in result5.output
class TestEmailFailure(IhatemoneyTestCase): class EmailFailureTestCase(IhatemoneyTestCase):
def test_creation_email_failure_smtp(self): def test_creation_email_failure_smtp(self):
self.login("raclette") self.login("raclette")
with patch.object( with patch.object(
@ -301,14 +233,14 @@ class TestEmailFailure(IhatemoneyTestCase):
): ):
resp = self.post_project("raclette") resp = self.post_project("raclette")
# Check that an error message is displayed # Check that an error message is displayed
assert ( self.assertIn(
"We tried to send you an reminder email, but there was an error" "We tried to send you an reminder email, but there was an error",
in resp.data.decode("utf-8") resp.data.decode("utf-8"),
) )
# Check that we were redirected to the home page anyway # Check that we were redirected to the home page anyway
assert ( self.assertIn(
'<a href="/raclette/members/add">Add the first participant' 'You probably want to <a href="/raclette/members/add"',
in resp.data.decode("utf-8") resp.data.decode("utf-8"),
) )
def test_creation_email_failure_socket(self): def test_creation_email_failure_socket(self):
@ -316,14 +248,14 @@ class TestEmailFailure(IhatemoneyTestCase):
with patch.object(self.app.mail, "send", MagicMock(side_effect=socket.error)): with patch.object(self.app.mail, "send", MagicMock(side_effect=socket.error)):
resp = self.post_project("raclette") resp = self.post_project("raclette")
# Check that an error message is displayed # Check that an error message is displayed
assert ( self.assertIn(
"We tried to send you an reminder email, but there was an error" "We tried to send you an reminder email, but there was an error",
in resp.data.decode("utf-8") resp.data.decode("utf-8"),
) )
# Check that we were redirected to the home page anyway # Check that we were redirected to the home page anyway
assert ( self.assertIn(
'<a href="/raclette/members/add">Add the first participant' 'You probably want to <a href="/raclette/members/add"',
in resp.data.decode("utf-8") resp.data.decode("utf-8"),
) )
def test_password_reset_email_failure(self): def test_password_reset_email_failure(self):
@ -334,13 +266,14 @@ class TestEmailFailure(IhatemoneyTestCase):
"/password-reminder", data={"id": "raclette"}, follow_redirects=True "/password-reminder", data={"id": "raclette"}, follow_redirects=True
) )
# Check that an error message is displayed # Check that an error message is displayed
assert "there was an error while sending you an email" in resp.data.decode( self.assertIn(
"utf-8" "there was an error while sending you an email",
resp.data.decode("utf-8"),
) )
# Check that we were not redirected to the success page # Check that we were not redirected to the success page
assert ( self.assertNotIn(
"A link to reset your password has been sent to you" "A link to reset your password has been sent to you",
not in resp.data.decode("utf-8") resp.data.decode("utf-8"),
) )
def test_invitation_email_failure(self): def test_invitation_email_failure(self):
@ -354,15 +287,17 @@ class TestEmailFailure(IhatemoneyTestCase):
follow_redirects=True, follow_redirects=True,
) )
# Check that an error message is displayed # Check that an error message is displayed
assert ( self.assertIn(
"there was an error while trying to send the invitation emails" "there was an error while trying to send the invitation emails",
in resp.data.decode("utf-8") resp.data.decode("utf-8"),
) )
# Check that we are still on the same page (no redirection) # Check that we are still on the same page (no redirection)
assert "Invite people to join this project" in resp.data.decode("utf-8") self.assertIn(
"Invite people to join this project", resp.data.decode("utf-8")
)
class TestCaptcha(IhatemoneyTestCase): class CaptchaTestCase(IhatemoneyTestCase):
ENABLE_CAPTCHA = True ENABLE_CAPTCHA = True
def test_project_creation_with_captcha_case_insensitive(self): def test_project_creation_with_captcha_case_insensitive(self):
@ -380,7 +315,7 @@ class TestCaptcha(IhatemoneyTestCase):
"captcha": "éùüß", "captcha": "éùüß",
}, },
) )
assert len(models.Project.query.all()) == 1 self.assertEqual(len(models.Project.query.all()), 1)
def test_project_creation_with_captcha(self): def test_project_creation_with_captcha(self):
with self.client as c: with self.client as c:
@ -394,7 +329,7 @@ class TestCaptcha(IhatemoneyTestCase):
"default_currency": "USD", "default_currency": "USD",
}, },
) )
assert len(models.Project.query.all()) == 0 self.assertEqual(len(models.Project.query.all()), 0)
c.post( c.post(
"/create", "/create",
@ -407,7 +342,7 @@ class TestCaptcha(IhatemoneyTestCase):
"captcha": "nope", "captcha": "nope",
}, },
) )
assert len(models.Project.query.all()) == 0 self.assertEqual(len(models.Project.query.all()), 0)
c.post( c.post(
"/create", "/create",
@ -420,7 +355,7 @@ class TestCaptcha(IhatemoneyTestCase):
"captcha": "euro", "captcha": "euro",
}, },
) )
assert len(models.Project.query.all()) == 1 self.assertEqual(len(models.Project.query.all()), 1)
def test_api_project_creation_does_not_need_captcha(self): def test_api_project_creation_does_not_need_captcha(self):
self.client.get("/") self.client.get("/")
@ -433,11 +368,11 @@ class TestCaptcha(IhatemoneyTestCase):
"contact_email": "raclette@notmyidea.org", "contact_email": "raclette@notmyidea.org",
}, },
) )
assert resp.status_code == 201 self.assertTrue(resp.status, 201)
assert len(models.Project.query.all()) == 1 self.assertEqual(len(models.Project.query.all()), 1)
class TestCurrencyConverter: class TestCurrencyConverter(unittest.TestCase):
converter = CurrencyConverter() converter = CurrencyConverter()
mock_data = { mock_data = {
"USD": 1, "USD": 1,
@ -451,21 +386,28 @@ class TestCurrencyConverter:
def test_only_one_instance(self): def test_only_one_instance(self):
one = id(CurrencyConverter()) one = id(CurrencyConverter())
two = id(CurrencyConverter()) two = id(CurrencyConverter())
assert one == two self.assertEqual(one, two)
def test_get_currencies(self): def test_get_currencies(self):
currencies = self.converter.get_currencies() self.assertCountEqual(
for currency in ["USD", "EUR", "CAD", "PLN", CurrencyConverter.no_currency]: self.converter.get_currencies(),
assert currency in currencies ["USD", "EUR", "CAD", "PLN", CurrencyConverter.no_currency],
)
def test_exchange_currency(self): def test_exchange_currency(self):
result = self.converter.exchange_currency(100, "USD", "EUR") result = self.converter.exchange_currency(100, "USD", "EUR")
assert result == 80.0 self.assertEqual(result, 80.0)
def test_failing_remote(self): def test_failing_remote(self):
rates = {} rates = {}
with patch("requests.Response.json", new=lambda _: {}): with patch("requests.Response.json", new=lambda _: {}), self.assertWarns(
UserWarning
):
# we need a non-patched converter, but it seems that MagickMock # we need a non-patched converter, but it seems that MagickMock
# is mocking EVERY instance of the class method. Too bad. # is mocking EVERY instance of the class method. Too bad.
rates = CurrencyConverter.get_rates(self.converter) rates = CurrencyConverter.get_rates(self.converter)
assert rates == {CurrencyConverter.no_currency: 1} self.assertDictEqual(rates, {CurrencyConverter.no_currency: 1})
if __name__ == "__main__":
unittest.main()

File diff suppressed because it is too large Load diff

Binary file not shown.

View file

@ -3,7 +3,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-07-29 14:24+0200\n" "POT-Creation-Date: 2023-07-14 10:01+0200\n"
"PO-Revision-Date: 2022-04-14 21:10+0000\n" "PO-Revision-Date: 2022-04-14 21:10+0000\n"
"Last-Translator: Hasidul Islam <ihasidul@gmail.com>\n" "Last-Translator: Hasidul Islam <ihasidul@gmail.com>\n"
"Language: bn\n" "Language: bn\n"
@ -27,13 +27,6 @@ msgstr ""
msgid "Project name" msgid "Project name"
msgstr "প্রজেক্টের নাম" msgstr "প্রজেক্টের নাম"
#, fuzzy
msgid "Current private code"
msgstr "নতুন ব্যক্তিগত কোড"
msgid "Enter existing private code to edit project"
msgstr ""
msgid "New private code" msgid "New private code"
msgstr "নতুন ব্যক্তিগত কোড" msgstr "নতুন ব্যক্তিগত কোড"
@ -55,12 +48,6 @@ msgstr "ডিফল্ট মুদ্রা"
msgid "Setting a default currency enables currency conversion between bills" msgid "Setting a default currency enables currency conversion between bills"
msgstr "" msgstr ""
msgid "Unknown error"
msgstr "অজানা ত্রুটি"
msgid "Invalid private code."
msgstr "অবৈধ ব্যক্তিগত কোড."
msgid "" msgid ""
"This project cannot be set to 'no currency' because it contains bills in " "This project cannot be set to 'no currency' because it contains bills in "
"multiple currencies." "multiple currencies."
@ -100,6 +87,12 @@ msgstr "অনুগ্রহ করে, এগিয়ে যেতে ক্
msgid "Enter private code to confirm deletion" msgid "Enter private code to confirm deletion"
msgstr "মুছে ফেলা নিশ্চিত করতে ব্যক্তিগত কোড লিখুন" msgstr "মুছে ফেলা নিশ্চিত করতে ব্যক্তিগত কোড লিখুন"
msgid "Unknown error"
msgstr "অজানা ত্রুটি"
msgid "Invalid private code."
msgstr "অবৈধ ব্যক্তিগত কোড."
msgid "Get in" msgid "Get in"
msgstr "প্রবেশ করুন" msgstr "প্রবেশ করুন"
@ -263,9 +256,6 @@ msgstr ""
msgid "Password successfully reset." msgid "Password successfully reset."
msgstr "" msgstr ""
msgid "Project settings have been changed successfully."
msgstr ""
msgid "Unable to parse CSV" msgid "Unable to parse CSV"
msgstr "" msgstr ""
@ -757,9 +747,6 @@ msgstr ""
msgid "Settings" msgid "Settings"
msgstr "" msgstr ""
msgid "RSS Feed"
msgstr ""
msgid "Other projects :" msgid "Other projects :"
msgstr "" msgstr ""
@ -782,7 +769,7 @@ msgstr ""
msgid "Documentation" msgid "Documentation"
msgstr "" msgstr ""
msgid "Administration Dashboard" msgid "Administation Dashboard"
msgstr "" msgstr ""
msgid "Legal information" msgid "Legal information"
@ -836,10 +823,13 @@ msgstr ""
msgid "Nothing to list yet." msgid "Nothing to list yet."
msgstr "" msgstr ""
msgid "Add your first bill" msgid "You probably want to"
msgstr "" msgstr ""
msgid "Add the first participant" msgid "add a bill"
msgstr ""
msgid "add participants"
msgstr "" msgstr ""
msgid "Password reminder" msgid "Password reminder"
@ -862,14 +852,21 @@ msgstr ""
msgid "Invite people to join this project" msgid "Invite people to join this project"
msgstr "" msgstr ""
msgid "Share an invitation link" msgid "Share Identifier & code"
msgstr "" msgstr ""
msgid "" msgid ""
"The easiest way to invite people is to give them the following invitation" "You can share the project identifier and the private code by any "
" link.<br />They will be able to access the project, manage participants," "communication means."
" add/edit/delete bills. However, they will not have access to important " msgstr ""
"settings such as changing the private code or deleting the whole project."
msgid "Identifier:"
msgstr ""
msgid "Share the Link"
msgstr ""
msgid "You can directly share the following link via your prefered medium"
msgstr "" msgstr ""
msgid "Scan QR code" msgid "Scan QR code"
@ -882,29 +879,10 @@ msgid "Send via Emails"
msgstr "" msgstr ""
msgid "" msgid ""
"Specify a list of email adresses (separated by comma) of people you want " "Specify a (comma separated) list of email adresses you want to notify "
"to notify about the creation of this project. We will send them an email " "about the\n"
"with the invitation link." " creation of this budget management project and we will "
msgstr "" "send them an email for you."
msgid "Share Identifier & code"
msgstr ""
msgid ""
"You can share the project identifier and the private code by any "
"communication means.<br />Anyone with the private code will have access "
"to the full project, including changing settings such as the private code"
" or project email address, or even deleting the whole project."
msgstr ""
msgid "Identifier:"
msgstr ""
#, fuzzy
msgid "Private code:"
msgstr "ব্যক্তিগত কোড"
msgid "the private code was defined when you created the project"
msgstr "" msgstr ""
msgid "Who pays?" msgid "Who pays?"
@ -1011,43 +989,3 @@ msgstr ""
#~ msgid "Edit the project" #~ msgid "Edit the project"
#~ msgstr "" #~ msgstr ""
#~ msgid "You probably want to"
#~ msgstr ""
#~ msgid "add a bill"
#~ msgstr ""
#~ msgid "add participants"
#~ msgstr ""
#~ msgid ""
#~ "You can share the project identifier "
#~ "and the private code by any "
#~ "communication means."
#~ msgstr ""
#~ msgid "Share the Link"
#~ msgstr ""
#~ msgid "You can directly share the following link via your prefered medium"
#~ msgstr ""
#~ msgid ""
#~ "Specify a (comma separated) list of "
#~ "email adresses you want to notify "
#~ "about the\n"
#~ " creation of this budget "
#~ "management project and we will send "
#~ "them an email for you."
#~ msgstr ""
#~ msgid ""
#~ "Specify a (comma separated) list of "
#~ "email adresses you want to notify "
#~ "about the\n"
#~ " creation of this budget "
#~ "management project and we will send "
#~ "them an email with the invitation "
#~ "link."
#~ msgstr ""

Binary file not shown.

View file

@ -3,7 +3,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-07-29 14:24+0200\n" "POT-Creation-Date: 2023-07-14 10:01+0200\n"
"PO-Revision-Date: 2020-08-01 10:41+0000\n" "PO-Revision-Date: 2020-08-01 10:41+0000\n"
"Last-Translator: Oymate <dhruboadittya96@gmail.com>\n" "Last-Translator: Oymate <dhruboadittya96@gmail.com>\n"
"Language: bn_BD\n" "Language: bn_BD\n"
@ -29,13 +29,6 @@ msgstr ""
msgid "Project name" msgid "Project name"
msgstr "প্রকল্পের নাম" msgstr "প্রকল্পের নাম"
#, fuzzy
msgid "Current private code"
msgstr "ব্যক্তিগত কোড"
msgid "Enter existing private code to edit project"
msgstr ""
#, fuzzy #, fuzzy
msgid "New private code" msgid "New private code"
msgstr "ব্যক্তিগত কোড" msgstr "ব্যক্তিগত কোড"
@ -58,13 +51,6 @@ msgstr "ডিফল্ট মুদ্রা"
msgid "Setting a default currency enables currency conversion between bills" msgid "Setting a default currency enables currency conversion between bills"
msgstr "" msgstr ""
msgid "Unknown error"
msgstr "অজানা ত্রুট"
#, fuzzy
msgid "Invalid private code."
msgstr "ব্যক্তিগত কোড"
msgid "" msgid ""
"This project cannot be set to 'no currency' because it contains bills in " "This project cannot be set to 'no currency' because it contains bills in "
"multiple currencies." "multiple currencies."
@ -100,6 +86,13 @@ msgstr ""
msgid "Enter private code to confirm deletion" msgid "Enter private code to confirm deletion"
msgstr "মুছে ফেলার জন্য ব্যক্তিগত কোড লিখুন" msgstr "মুছে ফেলার জন্য ব্যক্তিগত কোড লিখুন"
msgid "Unknown error"
msgstr "অজানা ত্রুট"
#, fuzzy
msgid "Invalid private code."
msgstr "ব্যক্তিগত কোড"
msgid "Get in" msgid "Get in"
msgstr "ভিতরে আস" msgstr "ভিতরে আস"
@ -263,9 +256,6 @@ msgstr ""
msgid "Password successfully reset." msgid "Password successfully reset."
msgstr "" msgstr ""
msgid "Project settings have been changed successfully."
msgstr ""
msgid "Unable to parse CSV" msgid "Unable to parse CSV"
msgstr "" msgstr ""
@ -760,9 +750,6 @@ msgstr ""
msgid "Settings" msgid "Settings"
msgstr "" msgstr ""
msgid "RSS Feed"
msgstr ""
msgid "Other projects :" msgid "Other projects :"
msgstr "" msgstr ""
@ -785,7 +772,7 @@ msgstr ""
msgid "Documentation" msgid "Documentation"
msgstr "" msgstr ""
msgid "Administration Dashboard" msgid "Administation Dashboard"
msgstr "" msgstr ""
msgid "Legal information" msgid "Legal information"
@ -839,10 +826,13 @@ msgstr ""
msgid "Nothing to list yet." msgid "Nothing to list yet."
msgstr "" msgstr ""
msgid "Add your first bill" msgid "You probably want to"
msgstr "" msgstr ""
msgid "Add the first participant" msgid "add a bill"
msgstr ""
msgid "add participants"
msgstr "" msgstr ""
msgid "Password reminder" msgid "Password reminder"
@ -865,14 +855,21 @@ msgstr ""
msgid "Invite people to join this project" msgid "Invite people to join this project"
msgstr "" msgstr ""
msgid "Share an invitation link" msgid "Share Identifier & code"
msgstr "" msgstr ""
msgid "" msgid ""
"The easiest way to invite people is to give them the following invitation" "You can share the project identifier and the private code by any "
" link.<br />They will be able to access the project, manage participants," "communication means."
" add/edit/delete bills. However, they will not have access to important " msgstr ""
"settings such as changing the private code or deleting the whole project."
msgid "Identifier:"
msgstr ""
msgid "Share the Link"
msgstr ""
msgid "You can directly share the following link via your prefered medium"
msgstr "" msgstr ""
msgid "Scan QR code" msgid "Scan QR code"
@ -885,29 +882,10 @@ msgid "Send via Emails"
msgstr "" msgstr ""
msgid "" msgid ""
"Specify a list of email adresses (separated by comma) of people you want " "Specify a (comma separated) list of email adresses you want to notify "
"to notify about the creation of this project. We will send them an email " "about the\n"
"with the invitation link." " creation of this budget management project and we will "
msgstr "" "send them an email for you."
msgid "Share Identifier & code"
msgstr ""
msgid ""
"You can share the project identifier and the private code by any "
"communication means.<br />Anyone with the private code will have access "
"to the full project, including changing settings such as the private code"
" or project email address, or even deleting the whole project."
msgstr ""
msgid "Identifier:"
msgstr ""
#, fuzzy
msgid "Private code:"
msgstr "ব্যক্তিগত কোড"
msgid "the private code was defined when you created the project"
msgstr "" msgstr ""
msgid "Who pays?" msgid "Who pays?"
@ -1116,43 +1094,3 @@ msgstr ""
#~ msgid "Edit the project" #~ msgid "Edit the project"
#~ msgstr "" #~ msgstr ""
#~ msgid "You probably want to"
#~ msgstr ""
#~ msgid "add a bill"
#~ msgstr ""
#~ msgid "add participants"
#~ msgstr ""
#~ msgid ""
#~ "You can share the project identifier "
#~ "and the private code by any "
#~ "communication means."
#~ msgstr ""
#~ msgid "Share the Link"
#~ msgstr ""
#~ msgid "You can directly share the following link via your prefered medium"
#~ msgstr ""
#~ msgid ""
#~ "Specify a (comma separated) list of "
#~ "email adresses you want to notify "
#~ "about the\n"
#~ " creation of this budget "
#~ "management project and we will send "
#~ "them an email for you."
#~ msgstr ""
#~ msgid ""
#~ "Specify a (comma separated) list of "
#~ "email adresses you want to notify "
#~ "about the\n"
#~ " creation of this budget "
#~ "management project and we will send "
#~ "them an email with the invitation "
#~ "link."
#~ msgstr ""

Binary file not shown.

View file

@ -1,18 +1,18 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-07-29 14:24+0200\n" "POT-Creation-Date: 2023-07-14 10:01+0200\n"
"PO-Revision-Date: 2024-07-03 19:09+0000\n" "PO-Revision-Date: 2022-09-12 15:25+0000\n"
"Last-Translator: Quentin PAGÈS <quentinantonin@free.fr>\n" "Last-Translator: Maite Guix <maite.guix@gmail.com>\n"
"Language-Team: Catalan <https://hosted.weblate.org/projects/i-hate-money/"
"i-hate-money/ca/>\n"
"Language: ca\n" "Language: ca\n"
"Language-Team: Catalan <https://hosted.weblate.org/projects/i-hate-"
"money/i-hate-money/ca/>\n"
"Plural-Forms: nplurals=2; plural=n != 1\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n" "Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.7-dev\n"
"Generated-By: Babel 2.9.0\n" "Generated-By: Babel 2.9.0\n"
#, python-format #, python-format
@ -29,13 +29,6 @@ msgstr ""
msgid "Project name" msgid "Project name"
msgstr "Nom del projecte" msgstr "Nom del projecte"
#, fuzzy
msgid "Current private code"
msgstr "Codi privat nou"
msgid "Enter existing private code to edit project"
msgstr ""
msgid "New private code" msgid "New private code"
msgstr "Codi privat nou" msgstr "Codi privat nou"
@ -59,12 +52,6 @@ msgstr ""
"L'establiment d'una moneda predeterminada permet la conversió de moneda " "L'establiment d'una moneda predeterminada permet la conversió de moneda "
"entre factures" "entre factures"
msgid "Unknown error"
msgstr "Error desconegut"
msgid "Invalid private code."
msgstr "Codi privat no vàlid."
msgid "" msgid ""
"This project cannot be set to 'no currency' because it contains bills in " "This project cannot be set to 'no currency' because it contains bills in "
"multiple currencies." "multiple currencies."
@ -104,6 +91,12 @@ msgstr "Si us plau, valida el captcha per a continuar."
msgid "Enter private code to confirm deletion" msgid "Enter private code to confirm deletion"
msgstr "Introdueix el codi privat per a confirmar la supressió" msgstr "Introdueix el codi privat per a confirmar la supressió"
msgid "Unknown error"
msgstr "Error desconegut"
msgid "Invalid private code."
msgstr "Codi privat no vàlid."
msgid "Get in" msgid "Get in"
msgstr "Entrar-hi" msgstr "Entrar-hi"
@ -222,7 +215,7 @@ msgid "{start_object}, {next_object}"
msgstr "{start_object}, {next_object}" msgstr "{start_object}, {next_object}"
msgid "No Currency" msgid "No Currency"
msgstr "Cap moneda" msgstr "Sense moneda"
#. Form error with only one error #. Form error with only one error
msgid "{prefix}: {error}" msgid "{prefix}: {error}"
@ -280,9 +273,6 @@ msgstr "Projecte desconegut"
msgid "Password successfully reset." msgid "Password successfully reset."
msgstr "La contrasenya s'ha restablert correctament." msgstr "La contrasenya s'ha restablert correctament."
msgid "Project settings have been changed successfully."
msgstr ""
msgid "Unable to parse CSV" msgid "Unable to parse CSV"
msgstr "" msgstr ""
@ -801,9 +791,6 @@ msgstr "Historial"
msgid "Settings" msgid "Settings"
msgstr "Configuració" msgstr "Configuració"
msgid "RSS Feed"
msgstr ""
msgid "Other projects :" msgid "Other projects :"
msgstr "Altres projectes:" msgstr "Altres projectes:"
@ -826,7 +813,7 @@ msgstr "Aplicació mòbil"
msgid "Documentation" msgid "Documentation"
msgstr "Documentació" msgstr "Documentació"
msgid "Administration Dashboard" msgid "Administation Dashboard"
msgstr "Panell d'administració" msgstr "Panell d'administració"
msgid "Legal information" msgid "Legal information"
@ -880,13 +867,14 @@ msgstr "Sense factures"
msgid "Nothing to list yet." msgid "Nothing to list yet."
msgstr "Res a enumerar encara." msgstr "Res a enumerar encara."
#, fuzzy msgid "You probably want to"
msgid "Add your first bill" msgstr "Probablement vulguis"
msgid "add a bill"
msgstr "afegir una factura" msgstr "afegir una factura"
#, fuzzy msgid "add participants"
msgid "Add the first participant" msgstr "afegir participants"
msgstr "Editar aquest participant"
msgid "Password reminder" msgid "Password reminder"
msgstr "Recordatori de contrasenya" msgstr "Recordatori de contrasenya"
@ -910,15 +898,26 @@ msgstr "Restablir la contrasenya"
msgid "Invite people to join this project" msgid "Invite people to join this project"
msgstr "Convidar a persones a unir-se a aquest projecte" msgstr "Convidar a persones a unir-se a aquest projecte"
msgid "Share an invitation link" msgid "Share Identifier & code"
msgstr "" msgstr "Compartir l'identificador i el codi"
msgid "" msgid ""
"The easiest way to invite people is to give them the following invitation" "You can share the project identifier and the private code by any "
" link.<br />They will be able to access the project, manage participants," "communication means."
" add/edit/delete bills. However, they will not have access to important "
"settings such as changing the private code or deleting the whole project."
msgstr "" msgstr ""
"Pots compartir l'identificador del projecte i el codi privat per "
"qualsevol mitjà de comunicació."
msgid "Identifier:"
msgstr "Identificador:"
msgid "Share the Link"
msgstr "Compartir l'enllaç"
msgid "You can directly share the following link via your prefered medium"
msgstr ""
"Pots compartir directament l'enllaç següent a través del teu mitjà "
"preferit"
msgid "Scan QR code" msgid "Scan QR code"
msgstr "" msgstr ""
@ -929,37 +928,17 @@ msgstr ""
msgid "Send via Emails" msgid "Send via Emails"
msgstr "Enviar per correu electrònic" msgstr "Enviar per correu electrònic"
#, fuzzy
msgid "" msgid ""
"Specify a list of email adresses (separated by comma) of people you want " "Specify a (comma separated) list of email adresses you want to notify "
"to notify about the creation of this project. We will send them an email " "about the\n"
"with the invitation link." " creation of this budget management project and we will "
"send them an email for you."
msgstr "" msgstr ""
"Especifica una llista (separada per comes) dels correus electrònic als " "Especifica una llista (separada per comes) dels correus electrònic als "
"que vols notificar la\n" "que vols notificar la\n"
" creació d'aquest projecte de gestió pressupostària i els " " creació d'aquest projecte de gestió pressupostària i els "
"hi enviarem un correu electrònic." "hi enviarem un correu electrònic."
msgid "Share Identifier & code"
msgstr "Compartir l'identificador i el codi"
msgid ""
"You can share the project identifier and the private code by any "
"communication means.<br />Anyone with the private code will have access "
"to the full project, including changing settings such as the private code"
" or project email address, or even deleting the whole project."
msgstr ""
msgid "Identifier:"
msgstr "Identificador:"
#, fuzzy
msgid "Private code:"
msgstr "Codi privat"
msgid "the private code was defined when you created the project"
msgstr ""
msgid "Who pays?" msgid "Who pays?"
msgstr "Qui ha de pagar?" msgstr "Qui ha de pagar?"
@ -1060,25 +1039,3 @@ msgstr "Període"
#~ msgid "Edit the project" #~ msgid "Edit the project"
#~ msgstr "Editar el projecte" #~ msgstr "Editar el projecte"
#~ msgid "You probably want to"
#~ msgstr "Probablement vulguis"
#~ msgid "add participants"
#~ msgstr "afegir participants"
#~ msgid ""
#~ "You can share the project identifier "
#~ "and the private code by any "
#~ "communication means."
#~ msgstr ""
#~ "Pots compartir l'identificador del projecte"
#~ " i el codi privat per qualsevol "
#~ "mitjà de comunicació."
#~ msgid "Share the Link"
#~ msgstr "Compartir l'enllaç"
#~ msgid "You can directly share the following link via your prefered medium"
#~ msgstr ""
#~ "Pots compartir directament l'enllaç següent"
#~ " a través del teu mitjà preferit"

Binary file not shown.

File diff suppressed because it is too large Load diff

Binary file not shown.

View file

@ -1,18 +1,18 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-07-29 14:24+0200\n" "POT-Creation-Date: 2023-07-14 10:01+0200\n"
"PO-Revision-Date: 2024-02-24 00:03+0000\n" "PO-Revision-Date: 2023-07-09 19:50+0000\n"
"Last-Translator: Peter <peteramried@web.de>\n" "Last-Translator: Sebastian Lay <mail@sebastian-lay.de>\n"
"Language-Team: German <https://hosted.weblate.org/projects/i-hate-money/"
"i-hate-money/de/>\n"
"Language: de\n" "Language: de\n"
"Language-Team: German <https://hosted.weblate.org/projects/i-hate-money/i"
"-hate-money/de/>\n"
"Plural-Forms: nplurals=2; plural=n != 1\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n" "Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.5-dev\n"
"Generated-By: Babel 2.9.0\n" "Generated-By: Babel 2.9.0\n"
#, python-format #, python-format
@ -31,12 +31,6 @@ msgstr ""
msgid "Project name" msgid "Project name"
msgstr "Projektname" msgstr "Projektname"
msgid "Current private code"
msgstr "Aktueller privater Code"
msgid "Enter existing private code to edit project"
msgstr "Geben Sie Ihren privaten Code ein, um das Projekt zu bearbeiten"
msgid "New private code" msgid "New private code"
msgstr "Neuer privater Code" msgstr "Neuer privater Code"
@ -60,12 +54,6 @@ msgstr ""
"Das Festlegen einer Standardwährung ermöglicht die Währungsumrechnung " "Das Festlegen einer Standardwährung ermöglicht die Währungsumrechnung "
"zwischen Rechnungen" "zwischen Rechnungen"
msgid "Unknown error"
msgstr "Unbekannter Fehler"
msgid "Invalid private code."
msgstr "ungültiger privater Code."
msgid "" msgid ""
"This project cannot be set to 'no currency' because it contains bills in " "This project cannot be set to 'no currency' because it contains bills in "
"multiple currencies." "multiple currencies."
@ -74,7 +62,7 @@ msgstr ""
"es Rechnungen unterschiedlicher Währungen enthält." "es Rechnungen unterschiedlicher Währungen enthält."
msgid "Compatible with Cospend" msgid "Compatible with Cospend"
msgstr "Kompatibel mit Cospend" msgstr ""
msgid "Project identifier" msgid "Project identifier"
msgstr "Projektkennung" msgstr "Projektkennung"
@ -105,6 +93,12 @@ msgstr "Bitte bestätige das Captcha, um fortzufahren."
msgid "Enter private code to confirm deletion" msgid "Enter private code to confirm deletion"
msgstr "Geben Sie Ihren privaten Code ein, um die Löschung zu bestätigen" msgstr "Geben Sie Ihren privaten Code ein, um die Löschung zu bestätigen"
msgid "Unknown error"
msgstr "Unbekannter Fehler"
msgid "Invalid private code."
msgstr "ungültiger privater Code."
msgid "Get in" msgid "Get in"
msgstr "Eintreten" msgstr "Eintreten"
@ -197,15 +191,16 @@ msgid "Logout"
msgstr "Ausloggen" msgstr "Ausloggen"
msgid "Please check the email configuration of the server." msgid "Please check the email configuration of the server."
msgstr "Bitte überprüfen Sie die E-Mail Konfiguration des Servers." msgstr ""
#, python-format #, fuzzy, python-format
msgid "" msgid ""
"Please check the email configuration of the server or contact the " "Please check the email configuration of the server or contact the "
"administrator: %(admin_email)s" "administrator: %(admin_email)s"
msgstr "" msgstr ""
"Bitte überprüfe die E-Mail Konfiguration des Servers oder kontaktiere einen " "Entschuldigung, es trat ein Fehler beim Versenden der Einladungsmails "
"Administrator: %(admin_email)s" "auf. Bitte überprüfe die E-Mail Konfiguration des Servers oder "
"kontaktiere einen Administrator."
#. List with two items only #. List with two items only
msgid "{dual_object_0} and {dual_object_1}" msgid "{dual_object_0} and {dual_object_1}"
@ -234,8 +229,11 @@ msgstr "{prefix}: {error}"
msgid "{prefix}:<br />{errors}" msgid "{prefix}:<br />{errors}"
msgstr "{prefix}:<br />{errors}" msgstr "{prefix}:<br />{errors}"
#, fuzzy
msgid "Too many failed login attempts." msgid "Too many failed login attempts."
msgstr "Zu viele fehlgeschlagene Anmeldeversuche." msgstr ""
"Zu viele fehlgeschlagene Anmeldeversuche, bitte versuche es später "
"nochmal."
#, python-format #, python-format
msgid "This admin password is not the right one. Only %(num)d attempts left." msgid "This admin password is not the right one. Only %(num)d attempts left."
@ -280,16 +278,12 @@ msgstr "Unbekanntes Projekt"
msgid "Password successfully reset." msgid "Password successfully reset."
msgstr "Passwort erfolgreich zurückgesetzt." msgstr "Passwort erfolgreich zurückgesetzt."
#, fuzzy
msgid "Project settings have been changed successfully."
msgstr "Einstellungen wurden erfolgreich übernommen."
msgid "Unable to parse CSV" msgid "Unable to parse CSV"
msgstr "" msgstr ""
#, fuzzy, python-format #, python-format
msgid "Missing attribute: %(attribute)s" msgid "Missing attribute: %(attribute)s"
msgstr "Fehlendes Attribut: %(attribute)s" msgstr ""
msgid "" msgid ""
"Cannot add bills in multiple currencies to a project without default " "Cannot add bills in multiple currencies to a project without default "
@ -308,7 +302,7 @@ msgid "Error deleting project"
msgstr "Fehler bei Projektlöschung" msgstr "Fehler bei Projektlöschung"
msgid "Unable to logout" msgid "Unable to logout"
msgstr "Verlassen nicht möglich" msgstr ""
#, python-format #, python-format
msgid "You have been invited to share your expenses for %(project)s" msgid "You have been invited to share your expenses for %(project)s"
@ -317,9 +311,12 @@ msgstr "Du wurdest eingeladen, deine Ausgaben für %(project)s zu teilen"
msgid "Your invitations have been sent" msgid "Your invitations have been sent"
msgstr "Deine Einladungen wurden versendet" msgstr "Deine Einladungen wurden versendet"
#, fuzzy
msgid "Sorry, there was an error while trying to send the invitation emails." msgid "Sorry, there was an error while trying to send the invitation emails."
msgstr "" msgstr ""
"Entschuldigung, es trat ein Fehler beim Versenden der Einladungsmails auf." "Entschuldigung, es trat ein Fehler beim Versenden der Einladungsmails "
"auf. Bitte überprüfe die E-Mail Konfiguration des Servers oder "
"kontaktiere einen Administrator."
#, python-format #, python-format
msgid "%(member)s has been added" msgid "%(member)s has been added"
@ -365,7 +362,7 @@ msgstr "Die Ausgabe wurde bearbeitet"
#, python-format #, python-format
msgid "%(lang)s is not a supported language" msgid "%(lang)s is not a supported language"
msgstr "%(lang)s ist keine unterstützte Sprache" msgstr ""
msgid "Error deleting project history" msgid "Error deleting project history"
msgstr "Projekthistorie konnte nicht gelöscht werden" msgstr "Projekthistorie konnte nicht gelöscht werden"
@ -445,8 +442,9 @@ msgstr "Mach mit"
msgid "Edit project" msgid "Edit project"
msgstr "Projekt bearbeiten" msgstr "Projekt bearbeiten"
#, fuzzy
msgid "Import project" msgid "Import project"
msgstr "Projekt importieren" msgstr "Projekt bearbeiten"
msgid "Download project's data" msgid "Download project's data"
msgstr "Projektdaten herunterladen" msgstr "Projektdaten herunterladen"
@ -473,14 +471,14 @@ msgid "Privacy Settings"
msgstr "Datenschutzeinstellungen" msgstr "Datenschutzeinstellungen"
msgid "Save changes" msgid "Save changes"
msgstr "Änderungen speichern" msgstr ""
msgid "This will remove all bills and participants in this project!" msgid "This will remove all bills and participants in this project!"
msgstr "Dies wird alle Ausgaben und Mitglieder dieses Projektes löschen!" msgstr "Dies wird alle Ausgaben und Mitglieder dieses Projektes löschen!"
#, fuzzy #, fuzzy
msgid "Import previously exported project" msgid "Import previously exported project"
msgstr "Zuvor exportierte Projekt-Datei importieren" msgstr "Zuvor exportierte JSON-Datei importieren"
msgid "Choose file" msgid "Choose file"
msgstr "Datei auswählen" msgstr "Datei auswählen"
@ -491,9 +489,8 @@ msgstr "Ausgabe bearbeiten"
msgid "Add a bill" msgid "Add a bill"
msgstr "Ausgabe hinzufügen" msgstr "Ausgabe hinzufügen"
#, fuzzy
msgid "Simple operations are allowed, e.g. (18+36.2)/3" msgid "Simple operations are allowed, e.g. (18+36.2)/3"
msgstr "einfache Operationen sind erlaubt, z.B. (18+36.2)/3" msgstr ""
msgid "Everyone" msgid "Everyone"
msgstr "Jeder" msgstr "Jeder"
@ -585,14 +582,12 @@ msgstr "Rechnung %(name)s: %(owers_list_str)s zur Eigentümerliste hinzugefügt"
msgid "Bill %(name)s: removed %(owers_list_str)s from owers list" msgid "Bill %(name)s: removed %(owers_list_str)s from owers list"
msgstr "Rechnung %(name)s: %(owers_list_str)s von der Eigentümerliste entfernt" msgstr "Rechnung %(name)s: %(owers_list_str)s von der Eigentümerliste entfernt"
#, fuzzy
msgid "This project has history disabled. New actions won't appear below." msgid "This project has history disabled. New actions won't appear below."
msgstr "" msgstr ""
"Dieses Projekt hat den Projektverlauf deaktiviert. Neue Aktionen werden "
"unten nicht mehr angezeigt."
#, fuzzy
msgid "You can enable history on the settings page." msgid "You can enable history on the settings page."
msgstr "Der Verlauf kann in den Einstellungen aktiviert werden." msgstr "IP-Adresserfassung kann in den Einstellungen aktiviert werden"
msgid "" msgid ""
"The table below reflects actions recorded prior to disabling project " "The table below reflects actions recorded prior to disabling project "
@ -799,9 +794,6 @@ msgstr "Verlauf"
msgid "Settings" msgid "Settings"
msgstr "Einstellungen" msgstr "Einstellungen"
msgid "RSS Feed"
msgstr ""
msgid "Other projects :" msgid "Other projects :"
msgstr "Andere Projekte:" msgstr "Andere Projekte:"
@ -813,7 +805,7 @@ msgstr "Dashboard"
#, python-format #, python-format
msgid "Please retry after %(date)s." msgid "Please retry after %(date)s."
msgstr "Bitte nach %(date)s erneut versuchen." msgstr ""
msgid "Code" msgid "Code"
msgstr "Code" msgstr "Code"
@ -824,7 +816,7 @@ msgstr "Handy-Applikation"
msgid "Documentation" msgid "Documentation"
msgstr "Dokumentation" msgstr "Dokumentation"
msgid "Administration Dashboard" msgid "Administation Dashboard"
msgstr "Dashboard Administration" msgstr "Dashboard Administration"
msgid "Legal information" msgid "Legal information"
@ -867,7 +859,7 @@ msgstr "Hinzugefügt am %(date)s"
#, python-format #, python-format
msgid "Everyone but %(excluded)s" msgid "Everyone but %(excluded)s"
msgstr "Alle außer %(excluded)s" msgstr "Jeder außer %(excluded)s"
msgid "delete" msgid "delete"
msgstr "Löschen" msgstr "Löschen"
@ -878,12 +870,14 @@ msgstr "Keine Ausgaben"
msgid "Nothing to list yet." msgid "Nothing to list yet."
msgstr "Noch nichts aufzulisten." msgstr "Noch nichts aufzulisten."
msgid "Add your first bill" msgid "You probably want to"
msgstr "Mach deine erste Rechnung" msgstr "Du willst wahrscheinlich"
#, fuzzy msgid "add a bill"
msgid "Add the first participant" msgstr "eine Ausgabe hinzufügen"
msgstr "Diesen Benutzer bearbeiten"
msgid "add participants"
msgstr "Teilnehmer hinzufügen"
msgid "Password reminder" msgid "Password reminder"
msgstr "Passwort-Erinnerung" msgstr "Passwort-Erinnerung"
@ -907,57 +901,44 @@ msgstr "Setze dein Passwort zurück"
msgid "Invite people to join this project" msgid "Invite people to join this project"
msgstr "Lade Leute ein, diesem Projekt beizutreten" msgstr "Lade Leute ein, diesem Projekt beizutreten"
msgid "Share an invitation link"
msgstr "Einladung verschicken"
msgid ""
"The easiest way to invite people is to give them the following invitation"
" link.<br />They will be able to access the project, manage participants,"
" add/edit/delete bills. However, they will not have access to important "
"settings such as changing the private code or deleting the whole project."
msgstr ""
"Am einfachsten lädt man jemanden ein, indem man folgenden Einladungs-Link "
"teilt. <br /> Sie werden dann in der Lage sein dem Projekt beizutreten, zu "
"bearbeiten und Rechnungen hinzuzufügen, bearbeiten und löschen. Sie werden "
"allerdings keinen Zugriff auf wichtige Einstellungen bekommen, wie Privaten "
"Code zu ändern oder gar das ganze Projekt zu löschen."
msgid "Scan QR code"
msgstr "QR-Code scannen"
msgid "Use a mobile device with a compatible app."
msgstr "Benutzen sie ein Smartphone mit Kompatibler App."
msgid "Send via Emails"
msgstr "Per E-Mail versenden"
msgid ""
"Specify a list of email adresses (separated by comma) of people you want "
"to notify about the creation of this project. We will send them an email "
"with the invitation link."
msgstr ""
"Gib eine (durch Kommas getrennte) Liste von E-Mail-Adressen an, die du über "
"die Erstellung dieses Projekts informieren möchtest, und wir werden ihnen "
"eine E-Mail senden."
msgid "Share Identifier & code" msgid "Share Identifier & code"
msgstr "Teile die ID & den Code" msgstr "Teile die ID & den Code"
msgid "" msgid ""
"You can share the project identifier and the private code by any " "You can share the project identifier and the private code by any "
"communication means.<br />Anyone with the private code will have access " "communication means."
"to the full project, including changing settings such as the private code"
" or project email address, or even deleting the whole project."
msgstr "" msgstr ""
"Du kannst die Projekt-ID und den privaten Code auf jedem "
"Kommunikationsweg weitergeben."
msgid "Identifier:" msgid "Identifier:"
msgstr "ID:" msgstr "ID:"
msgid "Private code:" msgid "Share the Link"
msgstr "Privater Code:" msgstr "Link teilen"
msgid "the private code was defined when you created the project" msgid "You can directly share the following link via your prefered medium"
msgstr "Der Private Code wurde beim erstellen des Projekts von Ihnen festgelegt" msgstr "Du kannst den folgenden Link direkt über dein bevorzugtes Medium teilen"
msgid "Scan QR code"
msgstr ""
msgid "Use a mobile device with a compatible app."
msgstr ""
msgid "Send via Emails"
msgstr "Per E-Mail versenden"
msgid ""
"Specify a (comma separated) list of email adresses you want to notify "
"about the\n"
" creation of this budget management project and we will "
"send them an email for you."
msgstr ""
"Gib eine (durch Kommas getrennte) Liste von E-Mail-Adressen an, die du "
"über die\n"
"\t\t\tErstellung dieses Projekts informieren möchtest, und wir senden "
"ihnen eine E-Mail."
msgid "Who pays?" msgid "Who pays?"
msgstr "Wer zahlt?" msgstr "Wer zahlt?"
@ -1164,23 +1145,3 @@ msgstr "Zeitraum"
#~ msgid "Edit the project" #~ msgid "Edit the project"
#~ msgstr "Projekt bearbeiten" #~ msgstr "Projekt bearbeiten"
#~ msgid "You probably want to"
#~ msgstr "Du willst wahrscheinlich"
#~ msgid "add participants"
#~ msgstr "Teilnehmer hinzufügen"
#~ msgid ""
#~ "You can share the project identifier "
#~ "and the private code by any "
#~ "communication means."
#~ msgstr ""
#~ "Du kannst die Projekt-ID und den"
#~ " privaten Code auf jedem Kommunikationsweg"
#~ " weitergeben."
#~ msgid "Share the Link"
#~ msgstr "Link teilen"
#~ msgid "You can directly share the following link via your prefered medium"
#~ msgstr "Du kannst den folgenden Link direkt über dein bevorzugtes Medium teilen"

Binary file not shown.

View file

@ -3,7 +3,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-07-29 14:24+0200\n" "POT-Creation-Date: 2023-07-14 10:01+0200\n"
"PO-Revision-Date: 2021-08-01 08:34+0000\n" "PO-Revision-Date: 2021-08-01 08:34+0000\n"
"Last-Translator: Eugenia Russell <eugenia.russell2019@gmail.com>\n" "Last-Translator: Eugenia Russell <eugenia.russell2019@gmail.com>\n"
"Language: el\n" "Language: el\n"
@ -27,13 +27,6 @@ msgstr ""
msgid "Project name" msgid "Project name"
msgstr "Τίτλος εργασίας" msgstr "Τίτλος εργασίας"
#, fuzzy
msgid "Current private code"
msgstr "Ιδιωτικός κωδικός"
msgid "Enter existing private code to edit project"
msgstr ""
#, fuzzy #, fuzzy
msgid "New private code" msgid "New private code"
msgstr "Ιδιωτικός κωδικός" msgstr "Ιδιωτικός κωδικός"
@ -56,14 +49,6 @@ msgstr "Προεπιλεγμένο Νόμισμα"
msgid "Setting a default currency enables currency conversion between bills" msgid "Setting a default currency enables currency conversion between bills"
msgstr "" msgstr ""
#, fuzzy
msgid "Unknown error"
msgstr "Άγνωστο πρότζεκτ"
#, fuzzy
msgid "Invalid private code."
msgstr "Ιδιωτικός κωδικός"
msgid "" msgid ""
"This project cannot be set to 'no currency' because it contains bills in " "This project cannot be set to 'no currency' because it contains bills in "
"multiple currencies." "multiple currencies."
@ -104,6 +89,14 @@ msgstr ""
msgid "Enter private code to confirm deletion" msgid "Enter private code to confirm deletion"
msgstr "Εισαγάγετε ιδιωτικό κωδικό για επιβεβαίωση της διαγραφής" msgstr "Εισαγάγετε ιδιωτικό κωδικό για επιβεβαίωση της διαγραφής"
#, fuzzy
msgid "Unknown error"
msgstr "Άγνωστο πρότζεκτ"
#, fuzzy
msgid "Invalid private code."
msgstr "Ιδιωτικός κωδικός"
msgid "Get in" msgid "Get in"
msgstr "Συνδεθείτε" msgstr "Συνδεθείτε"
@ -276,9 +269,6 @@ msgstr "Άγνωστο πρότζεκτ"
msgid "Password successfully reset." msgid "Password successfully reset."
msgstr "Επαναφορά του κωδικού επιτυχώς." msgstr "Επαναφορά του κωδικού επιτυχώς."
msgid "Project settings have been changed successfully."
msgstr ""
msgid "Unable to parse CSV" msgid "Unable to parse CSV"
msgstr "" msgstr ""
@ -786,9 +776,6 @@ msgstr ""
msgid "Settings" msgid "Settings"
msgstr "" msgstr ""
msgid "RSS Feed"
msgstr ""
msgid "Other projects :" msgid "Other projects :"
msgstr "" msgstr ""
@ -811,7 +798,7 @@ msgstr ""
msgid "Documentation" msgid "Documentation"
msgstr "" msgstr ""
msgid "Administration Dashboard" msgid "Administation Dashboard"
msgstr "" msgstr ""
#, fuzzy #, fuzzy
@ -866,12 +853,14 @@ msgstr ""
msgid "Nothing to list yet." msgid "Nothing to list yet."
msgstr "" msgstr ""
msgid "Add your first bill" msgid "You probably want to"
msgstr "" msgstr ""
#, fuzzy msgid "add a bill"
msgid "Add the first participant" msgstr ""
msgstr "Προσθήκη συμμετέχοντος"
msgid "add participants"
msgstr ""
msgid "Password reminder" msgid "Password reminder"
msgstr "" msgstr ""
@ -893,14 +882,21 @@ msgstr ""
msgid "Invite people to join this project" msgid "Invite people to join this project"
msgstr "" msgstr ""
msgid "Share an invitation link" msgid "Share Identifier & code"
msgstr "" msgstr ""
msgid "" msgid ""
"The easiest way to invite people is to give them the following invitation" "You can share the project identifier and the private code by any "
" link.<br />They will be able to access the project, manage participants," "communication means."
" add/edit/delete bills. However, they will not have access to important " msgstr ""
"settings such as changing the private code or deleting the whole project."
msgid "Identifier:"
msgstr ""
msgid "Share the Link"
msgstr ""
msgid "You can directly share the following link via your prefered medium"
msgstr "" msgstr ""
msgid "Scan QR code" msgid "Scan QR code"
@ -913,29 +909,10 @@ msgid "Send via Emails"
msgstr "" msgstr ""
msgid "" msgid ""
"Specify a list of email adresses (separated by comma) of people you want " "Specify a (comma separated) list of email adresses you want to notify "
"to notify about the creation of this project. We will send them an email " "about the\n"
"with the invitation link." " creation of this budget management project and we will "
msgstr "" "send them an email for you."
msgid "Share Identifier & code"
msgstr ""
msgid ""
"You can share the project identifier and the private code by any "
"communication means.<br />Anyone with the private code will have access "
"to the full project, including changing settings such as the private code"
" or project email address, or even deleting the whole project."
msgstr ""
msgid "Identifier:"
msgstr ""
#, fuzzy
msgid "Private code:"
msgstr "Ιδιωτικός κωδικός"
msgid "the private code was defined when you created the project"
msgstr "" msgstr ""
msgid "Who pays?" msgid "Who pays?"
@ -1120,43 +1097,3 @@ msgstr "Περίοδος"
#~ msgid "Edit the project" #~ msgid "Edit the project"
#~ msgstr "Επεξεργαστείτε το έργο" #~ msgstr "Επεξεργαστείτε το έργο"
#~ msgid "You probably want to"
#~ msgstr ""
#~ msgid "add a bill"
#~ msgstr ""
#~ msgid "add participants"
#~ msgstr ""
#~ msgid ""
#~ "You can share the project identifier "
#~ "and the private code by any "
#~ "communication means."
#~ msgstr ""
#~ msgid "Share the Link"
#~ msgstr ""
#~ msgid "You can directly share the following link via your prefered medium"
#~ msgstr ""
#~ msgid ""
#~ "Specify a (comma separated) list of "
#~ "email adresses you want to notify "
#~ "about the\n"
#~ " creation of this budget "
#~ "management project and we will send "
#~ "them an email for you."
#~ msgstr ""
#~ msgid ""
#~ "Specify a (comma separated) list of "
#~ "email adresses you want to notify "
#~ "about the\n"
#~ " creation of this budget "
#~ "management project and we will send "
#~ "them an email with the invitation "
#~ "link."
#~ msgstr ""

Binary file not shown.

View file

@ -3,7 +3,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-07-29 14:24+0200\n" "POT-Creation-Date: 2023-07-14 10:01+0200\n"
"PO-Revision-Date: 2021-10-01 20:35+0000\n" "PO-Revision-Date: 2021-10-01 20:35+0000\n"
"Last-Translator: phlostically <phlostically@mailinator.com>\n" "Last-Translator: phlostically <phlostically@mailinator.com>\n"
"Language: eo\n" "Language: eo\n"
@ -29,13 +29,6 @@ msgstr ""
msgid "Project name" msgid "Project name"
msgstr "Nomo de projekto" msgstr "Nomo de projekto"
#, fuzzy
msgid "Current private code"
msgstr "Nova privata kodo"
msgid "Enter existing private code to edit project"
msgstr ""
msgid "New private code" msgid "New private code"
msgstr "Nova privata kodo" msgstr "Nova privata kodo"
@ -57,12 +50,6 @@ msgstr "Implicita valuto"
msgid "Setting a default currency enables currency conversion between bills" msgid "Setting a default currency enables currency conversion between bills"
msgstr "" msgstr ""
msgid "Unknown error"
msgstr "Nekonata eraro"
msgid "Invalid private code."
msgstr "Nevalida privata kodo."
msgid "" msgid ""
"This project cannot be set to 'no currency' because it contains bills in " "This project cannot be set to 'no currency' because it contains bills in "
"multiple currencies." "multiple currencies."
@ -103,6 +90,12 @@ msgstr ""
msgid "Enter private code to confirm deletion" msgid "Enter private code to confirm deletion"
msgstr "" msgstr ""
msgid "Unknown error"
msgstr "Nekonata eraro"
msgid "Invalid private code."
msgstr "Nevalida privata kodo."
msgid "Get in" msgid "Get in"
msgstr "Eniri" msgstr "Eniri"
@ -277,9 +270,6 @@ msgstr "Nekonata projekto"
msgid "Password successfully reset." msgid "Password successfully reset."
msgstr "Pasvorto sukcese restarigita." msgstr "Pasvorto sukcese restarigita."
msgid "Project settings have been changed successfully."
msgstr ""
msgid "Unable to parse CSV" msgid "Unable to parse CSV"
msgstr "" msgstr ""
@ -796,9 +786,6 @@ msgstr "Historio"
msgid "Settings" msgid "Settings"
msgstr "Agordoj" msgstr "Agordoj"
msgid "RSS Feed"
msgstr ""
msgid "Other projects :" msgid "Other projects :"
msgstr "Aliaj projektoj:" msgstr "Aliaj projektoj:"
@ -821,7 +808,7 @@ msgstr "Poŝaparata programo"
msgid "Documentation" msgid "Documentation"
msgstr "Dokumentaro" msgstr "Dokumentaro"
msgid "Administration Dashboard" msgid "Administation Dashboard"
msgstr "Administra panelo" msgstr "Administra panelo"
#, fuzzy #, fuzzy
@ -876,13 +863,14 @@ msgstr "Neniu fakturo"
msgid "Nothing to list yet." msgid "Nothing to list yet."
msgstr "Nenio listigebla ankoraŭ." msgstr "Nenio listigebla ankoraŭ."
#, fuzzy msgid "You probably want to"
msgid "Add your first bill" msgstr "Vi probable volas"
msgid "add a bill"
msgstr "aldoni fakturon" msgstr "aldoni fakturon"
#, fuzzy msgid "add participants"
msgid "Add the first participant" msgstr "aldoni partoprenantojn"
msgstr "Aldono partoprenanton"
msgid "Password reminder" msgid "Password reminder"
msgstr "Rememorigilo pri pasvorto" msgstr "Rememorigilo pri pasvorto"
@ -906,15 +894,22 @@ msgstr "Restarigi vian pasvorton"
msgid "Invite people to join this project" msgid "Invite people to join this project"
msgstr "Inviti homojn aliĝi al ĉi tiu projekto" msgstr "Inviti homojn aliĝi al ĉi tiu projekto"
msgid "Share an invitation link" msgid "Share Identifier & code"
msgstr "" msgstr "Konigi identigilon kaj kodon"
msgid "" msgid ""
"The easiest way to invite people is to give them the following invitation" "You can share the project identifier and the private code by any "
" link.<br />They will be able to access the project, manage participants," "communication means."
" add/edit/delete bills. However, they will not have access to important " msgstr "Vi povas iel ajn konigi la projektan identigilon kaj la privatan kodon."
"settings such as changing the private code or deleting the whole project."
msgstr "" msgid "Identifier:"
msgstr "Identigilo:"
msgid "Share the Link"
msgstr "Sendi la ligon"
msgid "You can directly share the following link via your prefered medium"
msgstr "Vi povas rekte sendi la jenan hiperligon per via preferata komunikilo"
msgid "Scan QR code" msgid "Scan QR code"
msgstr "" msgstr ""
@ -925,37 +920,17 @@ msgstr ""
msgid "Send via Emails" msgid "Send via Emails"
msgstr "Sendi retpoŝte" msgstr "Sendi retpoŝte"
#, fuzzy
msgid "" msgid ""
"Specify a list of email adresses (separated by comma) of people you want " "Specify a (comma separated) list of email adresses you want to notify "
"to notify about the creation of this project. We will send them an email " "about the\n"
"with the invitation link." " creation of this budget management project and we will "
"send them an email for you."
msgstr "" msgstr ""
"Specifu (kome apartigitan) liston de tiuj retpoŝtaj adresoj, kiujn vi " "Specifu (kome apartigitan) liston de tiuj retpoŝtaj adresoj, kiujn vi "
"volas sciigi pri la\n" "volas sciigi pri la\n"
" kreado de ĉi tiu buĝet-administra projekto, kaj ni sendos" " kreado de ĉi tiu buĝet-administra projekto, kaj ni sendos"
" al ili retpoŝtajn mesaĝojn por vi." " al ili retpoŝtajn mesaĝojn por vi."
msgid "Share Identifier & code"
msgstr "Konigi identigilon kaj kodon"
msgid ""
"You can share the project identifier and the private code by any "
"communication means.<br />Anyone with the private code will have access "
"to the full project, including changing settings such as the private code"
" or project email address, or even deleting the whole project."
msgstr ""
msgid "Identifier:"
msgstr "Identigilo:"
#, fuzzy
msgid "Private code:"
msgstr "Privata kodo"
msgid "the private code was defined when you created the project"
msgstr ""
msgid "Who pays?" msgid "Who pays?"
msgstr "Kiu pagas?" msgstr "Kiu pagas?"
@ -1121,21 +1096,3 @@ msgstr "Periodo"
#~ msgid "Edit the project" #~ msgid "Edit the project"
#~ msgstr "Redakti la projekton" #~ msgstr "Redakti la projekton"
#~ msgid "You probably want to"
#~ msgstr "Vi probable volas"
#~ msgid "add participants"
#~ msgstr "aldoni partoprenantojn"
#~ msgid ""
#~ "You can share the project identifier "
#~ "and the private code by any "
#~ "communication means."
#~ msgstr "Vi povas iel ajn konigi la projektan identigilon kaj la privatan kodon."
#~ msgid "Share the Link"
#~ msgstr "Sendi la ligon"
#~ msgid "You can directly share the following link via your prefered medium"
#~ msgstr "Vi povas rekte sendi la jenan hiperligon per via preferata komunikilo"

Binary file not shown.

File diff suppressed because it is too large Load diff

Binary file not shown.

View file

@ -1,18 +1,19 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-07-29 14:24+0200\n" "POT-Creation-Date: 2023-07-14 10:01+0200\n"
"PO-Revision-Date: 2023-11-27 05:02+0000\n" "PO-Revision-Date: 2022-04-11 17:12+0000\n"
"Last-Translator: Wilfredo Gomez <thepageguy@mailfence.com>\n" "Last-Translator: Santiago José Gutiérrez Llanos "
"Language-Team: Spanish (Latin America) <https://hosted.weblate.org/projects/" "<gutierrezapata17@gmail.com>\n"
"i-hate-money/i-hate-money/es_419/>\n"
"Language: es_419\n" "Language: es_419\n"
"Language-Team: Spanish (Latin America) "
"<https://hosted.weblate.org/projects/i-hate-money/i-hate-money/es_419/>\n"
"Plural-Forms: nplurals=2; plural=n != 1\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n" "Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.2.1-rc\n"
"Generated-By: Babel 2.9.0\n" "Generated-By: Babel 2.9.0\n"
#, python-format #, python-format
@ -29,17 +30,11 @@ msgstr ""
msgid "Project name" msgid "Project name"
msgstr "Nombre del Proyecto" msgstr "Nombre del Proyecto"
msgid "Current private code"
msgstr "código privado actual"
msgid "Enter existing private code to edit project"
msgstr "Ingrese el código privado existente para editar el proyecto"
msgid "New private code" msgid "New private code"
msgstr "Nuevo Código privado" msgstr "Nuevo código privado"
msgid "Enter a new code if you want to change it" msgid "Enter a new code if you want to change it"
msgstr "Introduce un nuevo código si quieres cambiarlo" msgstr "Entra un nuevo código si tu quieres cambiarlo"
msgid "Email" msgid "Email"
msgstr "Correo Electrónico" msgstr "Correo Electrónico"
@ -48,31 +43,25 @@ msgid "Enable project history"
msgstr "Habilitar historial del proyecto" msgstr "Habilitar historial del proyecto"
msgid "Use IP tracking for project history" msgid "Use IP tracking for project history"
msgstr "Utilice el seguimiento de IP para el historial del proyecto" msgstr "Registrar la IPs para el historial del proyecto"
msgid "Default Currency" msgid "Default Currency"
msgstr "Moneda por defecto" msgstr "Moneda por defecto"
msgid "Setting a default currency enables currency conversion between bills" msgid "Setting a default currency enables currency conversion between bills"
msgstr "" msgstr ""
"Establecer una moneda predeterminada permite la conversión de moneda entre " "Establecer una moneda predeterminada permite la conversión de divisas "
"billetes" "entre facturas"
msgid "Unknown error"
msgstr "Error desconocido"
msgid "Invalid private code."
msgstr "Código privado no válido."
msgid "" msgid ""
"This project cannot be set to 'no currency' because it contains bills in " "This project cannot be set to 'no currency' because it contains bills in "
"multiple currencies." "multiple currencies."
msgstr "" msgstr ""
"Este proyecto no se puede configurar como \"sin moneda\" porque contiene " "Este proyecto no se puede establecer en 'ninguna moneda' porque contiene "
"billetes en varias monedas." "facturas en varias monedas."
msgid "Compatible with Cospend" msgid "Compatible with Cospend"
msgstr "Compatible con Cospend" msgstr ""
msgid "Project identifier" msgid "Project identifier"
msgstr "Identificador de proyecto" msgstr "Identificador de proyecto"
@ -92,16 +81,22 @@ msgstr ""
"favor, elija un nuevo identificador" "favor, elija un nuevo identificador"
msgid "Which is a real currency: Euro or Petro dollar?" msgid "Which is a real currency: Euro or Petro dollar?"
msgstr "¿Cuál es la moneda real: el euro o el petrodólar?" msgstr "¿Cuál es una moneda real: euro o petro dólar?"
msgid "euro" msgid "euro"
msgstr "euro" msgstr "Euro"
msgid "Please, validate the captcha to proceed." msgid "Please, validate the captcha to proceed."
msgstr "Por favor, valide la captcha para proceder." msgstr "Por favor, completa el captcha para seguir."
msgid "Enter private code to confirm deletion" msgid "Enter private code to confirm deletion"
msgstr "Ingrese el código privado para confirmar la eliminación" msgstr "Introduzca el código privado para confirmar la eliminación"
msgid "Unknown error"
msgstr "Error desconocido"
msgid "Invalid private code."
msgstr "Código privado inválido."
msgid "Get in" msgid "Get in"
msgstr "Entrar" msgstr "Entrar"
@ -122,7 +117,7 @@ msgid "Password"
msgstr "Contraseña" msgstr "Contraseña"
msgid "Password confirmation" msgid "Password confirmation"
msgstr "confirmación de contraseña" msgstr "Confirmar contraseña"
msgid "Reset password" msgid "Reset password"
msgstr "Restablecer contraseña" msgstr "Restablecer contraseña"
@ -159,7 +154,7 @@ msgstr "Enviar y agregar uno nuevo"
#, python-format #, python-format
msgid "Project default: %(currency)s" msgid "Project default: %(currency)s"
msgstr "Projecto por defecto: %(currency)s" msgstr "moneda predeterminada del projecto: %(currency)s"
msgid "Name" msgid "Name"
msgstr "Nombre" msgstr "Nombre"
@ -194,15 +189,15 @@ msgstr "Cerrar sesión"
msgid "Please check the email configuration of the server." msgid "Please check the email configuration of the server."
msgstr "" msgstr ""
"Por favor verifique la configuración de correo electrónico del servidor."
#, python-format #, fuzzy, python-format
msgid "" msgid ""
"Please check the email configuration of the server or contact the " "Please check the email configuration of the server or contact the "
"administrator: %(admin_email)s" "administrator: %(admin_email)s"
msgstr "" msgstr ""
"Verifique la configuración de correo electrónico del servidor o comuníquese " "Lo sentimos, hubo un error cuando intentamos enviarle correos de "
"con el administrador: %(admin_email)s" "invitación. Por favor, revise la configuración de correo en el servidor o"
" contactese con el administrador."
#. List with two items only #. List with two items only
msgid "{dual_object_0} and {dual_object_1}" msgid "{dual_object_0} and {dual_object_1}"
@ -221,47 +216,53 @@ msgid "{start_object}, {next_object}"
msgstr "{start_object}, {next_object}" msgstr "{start_object}, {next_object}"
msgid "No Currency" msgid "No Currency"
msgstr "No Moneda" msgstr "no moneda"
#. Form error with only one error #. Form error with only one error
msgid "{prefix}: {error}" msgid "{prefix}: {error}"
msgstr "{prefix}: {error}" msgstr "{prefijo}: {error}"
#. Form error with a list of errors #. Form error with a list of errors
msgid "{prefix}:<br />{errors}" msgid "{prefix}:<br />{errors}"
msgstr "{prefix}:<br />{errors}" msgstr "{prefijo}:<br />{errores}"
#, fuzzy
msgid "Too many failed login attempts." msgid "Too many failed login attempts."
msgstr "Demasiados intentos fallidos de inicio de sesión." msgstr ""
"Demasiados intentos fallidos de inicio de sesión, vuelva a intentarlo más"
" tarde."
#, python-format #, python-format
msgid "This admin password is not the right one. Only %(num)d attempts left." msgid "This admin password is not the right one. Only %(num)d attempts left."
msgstr "" msgstr ""
"Esta contraseña de administrador no es la correcta. Solo quedan %(num)d " "Esta contraseña de administrador no es la correcta. Solo quedan %(num)d "
"intentos restantes." "intentos."
msgid "Provided token is invalid" msgid "Provided token is invalid"
msgstr "El token proporcionado no es válido" msgstr "La muestra proporcionada no es válida"
msgid "This private code is not the right one" msgid "This private code is not the right one"
msgstr "Este código privado no es el correcto" msgstr "Este código privado no es el correcto"
msgid "A reminder email has just been sent to you" msgid "A reminder email has just been sent to you"
msgstr "Se le acaba de enviar un correo electrónico de recordatorio" msgstr "Acabamos de enviarte un email de recordatorio"
msgid "" msgid ""
"We tried to send you an reminder email, but there was an error. You can " "We tried to send you an reminder email, but there was an error. You can "
"still use the project normally." "still use the project normally."
msgstr "" msgstr ""
"Intentamos enviarte un correo electrónico de recordatorio, pero hubo un " "Te hemos intentado enviar un correo electrónico recordatorio pero ha "
"error. Aún puedes usar el proyecto normalmente." "habido un error. Todavía puedes usar el proyecto habitualmente."
#, fuzzy
msgid "" msgid ""
"Sorry, there was an error while sending you an email with password reset " "Sorry, there was an error while sending you an email with password reset "
"instructions." "instructions."
msgstr "" msgstr ""
"Lo sentimos, hubo un error al enviarle un correo electrónico con " "Lo sentimos, hubo un error al enviarle un correo electrónico con las "
"instrucciones para restablecer la contraseña." "instrucciones de restablecimiento de contraseña. Compruebe la "
"configuración de correo electrónico del servidor o póngase en contacto "
"con el administrador."
msgid "No token provided" msgid "No token provided"
msgstr "No se proporciono ningún token" msgstr "No se proporciono ningún token"
@ -275,34 +276,31 @@ msgstr "Proyecto desconocido"
msgid "Password successfully reset." msgid "Password successfully reset."
msgstr "Contraseña restablecida con éxito." msgstr "Contraseña restablecida con éxito."
msgid "Project settings have been changed successfully."
msgstr "La configuración del proyecto se ha cambiado correctamente."
msgid "Unable to parse CSV" msgid "Unable to parse CSV"
msgstr "No se puede analizar CSV" msgstr ""
#, python-format #, python-format
msgid "Missing attribute: %(attribute)s" msgid "Missing attribute: %(attribute)s"
msgstr "Atributo faltante: %(attribute)s" msgstr ""
msgid "" msgid ""
"Cannot add bills in multiple currencies to a project without default " "Cannot add bills in multiple currencies to a project without default "
"currency" "currency"
msgstr "" msgstr ""
"No se pueden agregar billetes en varias monedas a un proyecto sin moneda " "No se pueden agregar facturas en varias monedas a un proyecto sin la "
"predeterminada" "moneda predeterminada"
msgid "Project successfully uploaded" msgid "Project successfully uploaded"
msgstr "Proyecto cargado exitosamente" msgstr "El proyecto se subió exitosamente"
msgid "Project successfully deleted" msgid "Project successfully deleted"
msgstr "Proyecto eliminado correctamente" msgstr "Proyecto eliminado correctamente"
msgid "Error deleting project" msgid "Error deleting project"
msgstr "Error al borrar proyecto" msgstr "Error al borrar poryecto"
msgid "Unable to logout" msgid "Unable to logout"
msgstr "No se puede cerrar sesión" msgstr ""
#, python-format #, python-format
msgid "You have been invited to share your expenses for %(project)s" msgid "You have been invited to share your expenses for %(project)s"
@ -311,24 +309,26 @@ msgstr "Usted ha sido invitado a compartir sus gastos para %(project)s"
msgid "Your invitations have been sent" msgid "Your invitations have been sent"
msgstr "Sus invitaciones han sido enviadas" msgstr "Sus invitaciones han sido enviadas"
#, fuzzy
msgid "Sorry, there was an error while trying to send the invitation emails." msgid "Sorry, there was an error while trying to send the invitation emails."
msgstr "" msgstr ""
"Lo sentimos, hubo un error al intentar enviar los correos electrónicos de " "Lo sentimos, hubo un error cuando intentamos enviarle correos de "
"invitación." "invitación. Por favor, revise la configuración de correo en el servidor o"
" contactese con el administrador."
#, python-format #, python-format
msgid "%(member)s has been added" msgid "%(member)s has been added"
msgstr "%(member)s ha sido añadido" msgstr "Se añadieron %(member)s"
msgid "Error activating participant" msgid "Error activating participant"
msgstr "Error al activar el participante" msgstr "Error activando participante"
#, python-format #, python-format
msgid "%(name)s is part of this project again" msgid "%(name)s is part of this project again"
msgstr "%(name)s es parte de este proyecto otra vez" msgstr "%(name)s es parte de este nuevo proyecto"
msgid "Error removing participant" msgid "Error removing participant"
msgstr "Error al eliminar el participante" msgstr "Error eliminando participante"
#, python-format #, python-format
msgid "" msgid ""
@ -350,7 +350,7 @@ msgid "The bill has been added"
msgstr "La factura ha sido agregada" msgstr "La factura ha sido agregada"
msgid "Error deleting bill" msgid "Error deleting bill"
msgstr "Error al eliminar la factura" msgstr "Error eliminando factura"
msgid "The bill has been deleted" msgid "The bill has been deleted"
msgstr "La factura ha sido eliminada" msgstr "La factura ha sido eliminada"
@ -360,7 +360,7 @@ msgstr "La factura ha sido modificada"
#, python-format #, python-format
msgid "%(lang)s is not a supported language" msgid "%(lang)s is not a supported language"
msgstr "%(lang)s no es un idioma admitido" msgstr ""
msgid "Error deleting project history" msgid "Error deleting project history"
msgstr "Error al eliminar el historial del proyecto" msgstr "Error al eliminar el historial del proyecto"
@ -440,8 +440,9 @@ msgstr "Conseguir en"
msgid "Edit project" msgid "Edit project"
msgstr "Editar proyecto" msgstr "Editar proyecto"
#, fuzzy
msgid "Import project" msgid "Import project"
msgstr "Importar proyecto" msgstr "Editar proyecto"
msgid "Download project's data" msgid "Download project's data"
msgstr "Descargar datos del proyecto" msgstr "Descargar datos del proyecto"
@ -470,13 +471,14 @@ msgid "Privacy Settings"
msgstr "Ajustes de privacidad" msgstr "Ajustes de privacidad"
msgid "Save changes" msgid "Save changes"
msgstr "Guardar cambios" msgstr ""
msgid "This will remove all bills and participants in this project!" msgid "This will remove all bills and participants in this project!"
msgstr "Esto va a remover todas las facturas y participantes en este proyecto!" msgstr "Esto va a remover todas las facturas y participantes en este proyecto!"
#, fuzzy
msgid "Import previously exported project" msgid "Import previously exported project"
msgstr "Importar proyecto previamente exportado" msgstr "Importar archivo JSON previamente exportado"
msgid "Choose file" msgid "Choose file"
msgstr "Escoger un archivo" msgstr "Escoger un archivo"
@ -488,7 +490,7 @@ msgid "Add a bill"
msgstr "Agregar una factura" msgstr "Agregar una factura"
msgid "Simple operations are allowed, e.g. (18+36.2)/3" msgid "Simple operations are allowed, e.g. (18+36.2)/3"
msgstr "Se permiten operaciones simples, e.j. (18+36.2)/3" msgstr ""
msgid "Everyone" msgid "Everyone"
msgstr "Todos" msgstr "Todos"
@ -512,13 +514,13 @@ msgid "Download"
msgstr "Descargar" msgstr "Descargar"
msgid "Disabled Project History" msgid "Disabled Project History"
msgstr "Historial de proyectos deshabilitado" msgstr "Historial de proyecto activo"
msgid "Disabled Project History & IP Address Recording" msgid "Disabled Project History & IP Address Recording"
msgstr "Historial de proyecto y registros de dirección IP inactivos" msgstr "Historial de proyecto y registros de dirección IP inactivos"
msgid "Enabled Project History" msgid "Enabled Project History"
msgstr "Historial de proyectos habilitado" msgstr "Historial de proyecto activo"
msgid "Disabled IP Address Recording" msgid "Disabled IP Address Recording"
msgstr "Registro de direcciones IP activo" msgstr "Registro de direcciones IP activo"
@ -580,21 +582,19 @@ msgstr "Factura %(name)s: removida %(owers_list_str)s de la lista de dueños"
msgid "This project has history disabled. New actions won't appear below." msgid "This project has history disabled. New actions won't appear below."
msgstr "" msgstr ""
"Este proyecto tiene el historial deshabilitado. Las nuevas acciones no "
"aparecerán a continuación."
#, fuzzy
msgid "You can enable history on the settings page." msgid "You can enable history on the settings page."
msgstr "Puede habilitar el historial en la página de configuración." msgstr "El registro de direcciones IP se puede activar en la página de ajustes"
msgid "" msgid ""
"The table below reflects actions recorded prior to disabling project " "The table below reflects actions recorded prior to disabling project "
"history." "history."
msgstr "" msgstr ""
"La siguiente tabla refleja las acciones registradas antes de deshabilitar el "
"historial del proyecto."
#, fuzzy
msgid "You can clear the project history to remove them." msgid "You can clear the project history to remove them."
msgstr "Puede borrar el historial del proyecto para eliminarlos." msgstr "Es probable que alguien borrara el historial del proyecto."
msgid "" msgid ""
"Some entries below contain IP addresses, even though this project has IP " "Some entries below contain IP addresses, even though this project has IP "
@ -790,9 +790,6 @@ msgstr "Historial"
msgid "Settings" msgid "Settings"
msgstr "Configuración" msgstr "Configuración"
msgid "RSS Feed"
msgstr "rss Feed"
msgid "Other projects :" msgid "Other projects :"
msgstr "Otros proyectos :" msgstr "Otros proyectos :"
@ -804,7 +801,7 @@ msgstr "Tablero"
#, python-format #, python-format
msgid "Please retry after %(date)s." msgid "Please retry after %(date)s."
msgstr "Vuelva a intentarlo después de %(date)s." msgstr ""
msgid "Code" msgid "Code"
msgstr "Código" msgstr "Código"
@ -815,7 +812,7 @@ msgstr "Aplicación móvil"
msgid "Documentation" msgid "Documentation"
msgstr "Documentación" msgstr "Documentación"
msgid "Administration Dashboard" msgid "Administation Dashboard"
msgstr "Panel de administración" msgstr "Panel de administración"
msgid "Legal information" msgid "Legal information"
@ -869,11 +866,14 @@ msgstr "Sin facturas"
msgid "Nothing to list yet." msgid "Nothing to list yet."
msgstr "Aún no hay nada que listar." msgstr "Aún no hay nada que listar."
msgid "Add your first bill" msgid "You probably want to"
msgstr "Añade tu primera factura" msgstr "Probablemente quieras"
msgid "Add the first participant" msgid "add a bill"
msgstr "Agregar el primer participante" msgstr "agregar una factura"
msgid "add participants"
msgstr "agregar participantes"
msgid "Password reminder" msgid "Password reminder"
msgstr "Recordar contraseña" msgstr "Recordar contraseña"
@ -897,62 +897,46 @@ msgstr "Restablecer su contraseña"
msgid "Invite people to join this project" msgid "Invite people to join this project"
msgstr "Invita a personas a unirse a este proyecto" msgstr "Invita a personas a unirse a este proyecto"
msgid "Share an invitation link"
msgstr "Compartir un enlace de invitación"
msgid ""
"The easiest way to invite people is to give them the following invitation"
" link.<br />They will be able to access the project, manage participants,"
" add/edit/delete bills. However, they will not have access to important "
"settings such as changing the private code or deleting the whole project."
msgstr ""
"La forma más sencilla de invitar personas es dándoles el siguiente enlace de "
"invitación.<br />Podrán acceder al proyecto, administrar participantes, "
"agregar/editar/eliminar facturas. Sin embargo, no tendrán acceso a "
"configuraciones importantes como cambiar el código privado o eliminar todo "
"el proyecto."
msgid "Scan QR code"
msgstr "Escanear código QR"
msgid "Use a mobile device with a compatible app."
msgstr "Utilice un dispositivo móvil con una aplicación compatible."
msgid "Send via Emails"
msgstr "Enviar por correo electrónico"
msgid ""
"Specify a list of email adresses (separated by comma) of people you want "
"to notify about the creation of this project. We will send them an email "
"with the invitation link."
msgstr ""
"Especifique una lista de direcciones de correo electrónico (separadas por "
"comas) de las personas a las que desea notificar sobre la creación de este "
"proyecto. Les enviaremos un correo electrónico con el enlace de invitación."
msgid "Share Identifier & code" msgid "Share Identifier & code"
msgstr "Compartir identificador y código" msgstr "Compartir identificador y código"
msgid "" msgid ""
"You can share the project identifier and the private code by any " "You can share the project identifier and the private code by any "
"communication means.<br />Anyone with the private code will have access " "communication means."
"to the full project, including changing settings such as the private code"
" or project email address, or even deleting the whole project."
msgstr "" msgstr ""
"Puede compartir el identificador del proyecto y el código privado por " "Puede compartir el identificador del proyecto y el código privado por "
"cualquier medio de comunicación.<br />Cualquier persona con el código " "cualquier medio de comunicación."
"privado tendrá acceso al proyecto completo, incluido el cambio de "
"configuraciones como el código privado o la dirección de correo electrónico "
"del proyecto, o incluso la eliminación completa. proyecto."
msgid "Identifier:" msgid "Identifier:"
msgstr "Identificador:" msgstr "Identificador:"
msgid "Private code:" msgid "Share the Link"
msgstr "Código privado:" msgstr "Comparte el enlace"
msgid "the private code was defined when you created the project" msgid "You can directly share the following link via your prefered medium"
msgstr "el código privado se definió cuando creaste el proyecto" msgstr ""
"Puedes compartir directamente el siguiente enlace a través de tu medio "
"preferido"
msgid "Scan QR code"
msgstr ""
msgid "Use a mobile device with a compatible app."
msgstr ""
msgid "Send via Emails"
msgstr "Enviar por correo electrónico"
msgid ""
"Specify a (comma separated) list of email adresses you want to notify "
"about the\n"
" creation of this budget management project and we will "
"send them an email for you."
msgstr ""
"Especifique una lista (separada por comas) de las direcciones de correo "
"electrónico a las que desea notificar acerca de la\n"
"creación de este proyecto de gestión presupuestaria y les enviaremos un "
"correo electrónico para usted."
msgid "Who pays?" msgid "Who pays?"
msgstr "¿Quién paga?" msgstr "¿Quién paga?"
@ -1142,26 +1126,3 @@ msgstr "Período"
#~ msgid "Edit the project" #~ msgid "Edit the project"
#~ msgstr "Editar el proyecto" #~ msgstr "Editar el proyecto"
#~ msgid "You probably want to"
#~ msgstr "Probablemente quieras"
#~ msgid "add participants"
#~ msgstr "agregar participantes"
#~ msgid ""
#~ "You can share the project identifier "
#~ "and the private code by any "
#~ "communication means."
#~ msgstr ""
#~ "Puede compartir el identificador del "
#~ "proyecto y el código privado por "
#~ "cualquier medio de comunicación."
#~ msgid "Share the Link"
#~ msgstr "Comparte el enlace"
#~ msgid "You can directly share the following link via your prefered medium"
#~ msgstr ""
#~ "Puedes compartir directamente el siguiente "
#~ "enlace a través de tu medio "
#~ "preferido"

Binary file not shown.

View file

@ -1,18 +1,18 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-07-29 14:24+0200\n" "POT-Creation-Date: 2023-07-14 10:01+0200\n"
"PO-Revision-Date: 2024-05-23 03:01+0000\n" "PO-Revision-Date: 2023-03-19 21:40+0000\n"
"Last-Translator: Yamin Siahmargooei <yamin8000@yahoo.com>\n" "Last-Translator: Sai Mohammad-Hossein Emami <emami@outlook.com>\n"
"Language-Team: Persian <https://hosted.weblate.org/projects/i-hate-money/"
"i-hate-money/fa/>\n"
"Language: fa\n" "Language: fa\n"
"Language-Team: Persian <https://hosted.weblate.org/projects/i-hate-"
"money/i-hate-money/fa/>\n"
"Plural-Forms: nplurals=1; plural=0\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n" "Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Weblate 5.6-dev\n"
"Generated-By: Babel 2.9.0\n" "Generated-By: Babel 2.9.0\n"
#, python-format #, python-format
@ -27,13 +27,6 @@ msgstr "مقدار یا عبارت نامعتبر. فقط اعداد و عملی
msgid "Project name" msgid "Project name"
msgstr "نام پروژه" msgstr "نام پروژه"
#, fuzzy
msgid "Current private code"
msgstr "کد خصوصی جدید"
msgid "Enter existing private code to edit project"
msgstr ""
msgid "New private code" msgid "New private code"
msgstr "کد خصوصی جدید" msgstr "کد خصوصی جدید"
@ -55,12 +48,6 @@ msgstr "واحد پولی پیش فرض"
msgid "Setting a default currency enables currency conversion between bills" msgid "Setting a default currency enables currency conversion between bills"
msgstr "تنظیم واحد پولی پیش فرض امکان تبدیل ارز بین قبض‌ها رو فراهم می‌کنه" msgstr "تنظیم واحد پولی پیش فرض امکان تبدیل ارز بین قبض‌ها رو فراهم می‌کنه"
msgid "Unknown error"
msgstr "خطای ناشناخته"
msgid "Invalid private code."
msgstr "کد خصوصی نامعتبر."
msgid "" msgid ""
"This project cannot be set to 'no currency' because it contains bills in " "This project cannot be set to 'no currency' because it contains bills in "
"multiple currencies." "multiple currencies."
@ -100,6 +87,12 @@ msgstr "لطفا برای ادامه کپچا رو تایید کن."
msgid "Enter private code to confirm deletion" msgid "Enter private code to confirm deletion"
msgstr "کد خصوصی رو برای تایید حذف وارد کن" msgstr "کد خصوصی رو برای تایید حذف وارد کن"
msgid "Unknown error"
msgstr "خطای ناشناخته"
msgid "Invalid private code."
msgstr "کد خصوصی نامعتبر."
msgid "Get in" msgid "Get in"
msgstr "بیا تو" msgstr "بیا تو"
@ -125,16 +118,16 @@ msgid "Reset password"
msgstr "بازنشانی گذرواژه" msgstr "بازنشانی گذرواژه"
msgid "When?" msgid "When?"
msgstr "چه زمانی؟" msgstr ""
msgid "What?" msgid "What?"
msgstr "چی؟" msgstr "چی؟"
msgid "Who paid?" msgid "Who paid?"
msgstr "چه کسی پرداخت کرد؟" msgstr ""
msgid "How much?" msgid "How much?"
msgstr "چقدر؟" msgstr ""
msgid "Currency" msgid "Currency"
msgstr "واحد پولی" msgstr "واحد پولی"
@ -180,14 +173,14 @@ msgid "People to notify"
msgstr "افرادی که براشون نوتیفیکیشن ارسال میشه" msgstr "افرادی که براشون نوتیفیکیشن ارسال میشه"
msgid "Send the invitations" msgid "Send the invitations"
msgstr "ارسال دعوت نامه ها" msgstr ""
#, python-format #, python-format
msgid "The email %(email)s is not valid" msgid "The email %(email)s is not valid"
msgstr "ایمیل %(email)s نامعتبره" msgstr "ایمیل %(email)s نامعتبره"
msgid "Logout" msgid "Logout"
msgstr "خروج" msgstr ""
msgid "Please check the email configuration of the server." msgid "Please check the email configuration of the server."
msgstr "" msgstr ""
@ -263,9 +256,6 @@ msgstr ""
msgid "Password successfully reset." msgid "Password successfully reset."
msgstr "" msgstr ""
msgid "Project settings have been changed successfully."
msgstr ""
msgid "Unable to parse CSV" msgid "Unable to parse CSV"
msgstr "" msgstr ""
@ -757,9 +747,6 @@ msgstr ""
msgid "Settings" msgid "Settings"
msgstr "" msgstr ""
msgid "RSS Feed"
msgstr ""
msgid "Other projects :" msgid "Other projects :"
msgstr "" msgstr ""
@ -782,7 +769,7 @@ msgstr ""
msgid "Documentation" msgid "Documentation"
msgstr "" msgstr ""
msgid "Administration Dashboard" msgid "Administation Dashboard"
msgstr "" msgstr ""
msgid "Legal information" msgid "Legal information"
@ -836,10 +823,13 @@ msgstr ""
msgid "Nothing to list yet." msgid "Nothing to list yet."
msgstr "" msgstr ""
msgid "Add your first bill" msgid "You probably want to"
msgstr "" msgstr ""
msgid "Add the first participant" msgid "add a bill"
msgstr ""
msgid "add participants"
msgstr "" msgstr ""
msgid "Password reminder" msgid "Password reminder"
@ -862,14 +852,21 @@ msgstr ""
msgid "Invite people to join this project" msgid "Invite people to join this project"
msgstr "" msgstr ""
msgid "Share an invitation link" msgid "Share Identifier & code"
msgstr "" msgstr ""
msgid "" msgid ""
"The easiest way to invite people is to give them the following invitation" "You can share the project identifier and the private code by any "
" link.<br />They will be able to access the project, manage participants," "communication means."
" add/edit/delete bills. However, they will not have access to important " msgstr ""
"settings such as changing the private code or deleting the whole project."
msgid "Identifier:"
msgstr ""
msgid "Share the Link"
msgstr ""
msgid "You can directly share the following link via your prefered medium"
msgstr "" msgstr ""
msgid "Scan QR code" msgid "Scan QR code"
@ -882,29 +879,10 @@ msgid "Send via Emails"
msgstr "" msgstr ""
msgid "" msgid ""
"Specify a list of email adresses (separated by comma) of people you want " "Specify a (comma separated) list of email adresses you want to notify "
"to notify about the creation of this project. We will send them an email " "about the\n"
"with the invitation link." " creation of this budget management project and we will "
msgstr "" "send them an email for you."
msgid "Share Identifier & code"
msgstr ""
msgid ""
"You can share the project identifier and the private code by any "
"communication means.<br />Anyone with the private code will have access "
"to the full project, including changing settings such as the private code"
" or project email address, or even deleting the whole project."
msgstr ""
msgid "Identifier:"
msgstr ""
#, fuzzy
msgid "Private code:"
msgstr "کد خصوصی"
msgid "the private code was defined when you created the project"
msgstr "" msgstr ""
msgid "Who pays?" msgid "Who pays?"
@ -1053,42 +1031,3 @@ msgstr ""
#~ msgid "Edit the project" #~ msgid "Edit the project"
#~ msgstr "" #~ msgstr ""
#~ msgid "You probably want to"
#~ msgstr ""
#~ msgid "add a bill"
#~ msgstr ""
#~ msgid "add participants"
#~ msgstr ""
#~ msgid ""
#~ "You can share the project identifier "
#~ "and the private code by any "
#~ "communication means."
#~ msgstr ""
#~ msgid "Share the Link"
#~ msgstr ""
#~ msgid "You can directly share the following link via your prefered medium"
#~ msgstr ""
#~ msgid ""
#~ "Specify a (comma separated) list of "
#~ "email adresses you want to notify "
#~ "about the\n"
#~ " creation of this budget "
#~ "management project and we will send "
#~ "them an email for you."
#~ msgstr ""
#~ msgid ""
#~ "Specify a (comma separated) list of "
#~ "email adresses you want to notify "
#~ "about the\n"
#~ " creation of this budget "
#~ "management project and we will send "
#~ "them an email with the invitation "
#~ "link."
#~ msgstr ""

Binary file not shown.

View file

@ -7,17 +7,16 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PROJECT VERSION\n" "Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2023-07-29 14:24+0200\n" "POT-Creation-Date: 2023-07-14 10:01+0200\n"
"PO-Revision-Date: 2023-07-29 13:07+0000\n" "PO-Revision-Date: 2023-07-14 07:59+0000\n"
"Last-Translator: Baptiste <weblate@bitsofnetworks.org>\n" "Last-Translator: Baptiste <weblate@bitsofnetworks.org>\n"
"Language-Team: French <https://hosted.weblate.org/projects/i-hate-money/"
"i-hate-money/fr/>\n"
"Language: fr\n" "Language: fr\n"
"Language-Team: French <https://hosted.weblate.org/projects/i-hate-money/i"
"-hate-money/fr/>\n"
"Plural-Forms: nplurals=2; plural=n > 1\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n" "Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n > 1;\n"
"X-Generator: Weblate 5.0-dev\n"
"Generated-By: Babel 2.9.0\n" "Generated-By: Babel 2.9.0\n"
#, python-format #, python-format
@ -34,12 +33,6 @@ msgstr ""
msgid "Project name" msgid "Project name"
msgstr "Nom de projet" msgstr "Nom de projet"
msgid "Current private code"
msgstr "Code daccès actuel"
msgid "Enter existing private code to edit project"
msgstr "Entrez le code d'accès existant pour éditer le projet"
msgid "New private code" msgid "New private code"
msgstr "Nouveau code daccès" msgstr "Nouveau code daccès"
@ -63,12 +56,6 @@ msgstr ""
"Choisir une devise par défaut permet d'activer la conversion de devises " "Choisir une devise par défaut permet d'activer la conversion de devises "
"entre les factures" "entre les factures"
msgid "Unknown error"
msgstr "Erreur inconnue"
msgid "Invalid private code."
msgstr "Code daccès invalide."
msgid "" msgid ""
"This project cannot be set to 'no currency' because it contains bills in " "This project cannot be set to 'no currency' because it contains bills in "
"multiple currencies." "multiple currencies."
@ -108,6 +95,12 @@ msgstr "Merci de valider le captcha avant de continuer."
msgid "Enter private code to confirm deletion" msgid "Enter private code to confirm deletion"
msgstr "Entrez le code d'accès pour confirmer la suppression" msgstr "Entrez le code d'accès pour confirmer la suppression"
msgid "Unknown error"
msgstr "Erreur inconnue"
msgid "Invalid private code."
msgstr "Code daccès invalide."
msgid "Get in" msgid "Get in"
msgstr "Entrer" msgstr "Entrer"
@ -280,9 +273,6 @@ msgstr "Projet inconnu"
msgid "Password successfully reset." msgid "Password successfully reset."
msgstr "Le mot de passe a été changé avec succès." msgstr "Le mot de passe a été changé avec succès."
msgid "Project settings have been changed successfully."
msgstr "Les paramètres du projet ont bien été enregistrés."
msgid "Unable to parse CSV" msgid "Unable to parse CSV"
msgstr "Erreur lors de la lecture du fichier CSV" msgstr "Erreur lors de la lecture du fichier CSV"
@ -471,7 +461,7 @@ msgid "Privacy Settings"
msgstr "Vie privée" msgstr "Vie privée"
msgid "Save changes" msgid "Save changes"
msgstr "Sauvegarder les modifications" msgstr ""
msgid "This will remove all bills and participants in this project!" msgid "This will remove all bills and participants in this project!"
msgstr "Cela supprimera toutes les factures et participant⋅es du projet !" msgstr "Cela supprimera toutes les factures et participant⋅es du projet !"
@ -799,9 +789,6 @@ msgstr "Historique"
msgid "Settings" msgid "Settings"
msgstr "Options" msgstr "Options"
msgid "RSS Feed"
msgstr "Flux RSS"
msgid "Other projects :" msgid "Other projects :"
msgstr "Autres projets :" msgstr "Autres projets :"
@ -824,7 +811,7 @@ msgstr "Application mobile"
msgid "Documentation" msgid "Documentation"
msgstr "Documentation" msgstr "Documentation"
msgid "Administration Dashboard" msgid "Administation Dashboard"
msgstr "Panneau d'administration" msgstr "Panneau d'administration"
msgid "Legal information" msgid "Legal information"
@ -878,11 +865,14 @@ msgstr "Pas encore de factures"
msgid "Nothing to list yet." msgid "Nothing to list yet."
msgstr "Rien à lister pour le moment." msgstr "Rien à lister pour le moment."
msgid "Add your first bill" msgid "You probably want to"
msgstr "Ajouter votre première facture" msgstr "Vous souhaitez sûrement"
msgid "Add the first participant" msgid "add a bill"
msgstr "Ajouter le premier participant ou la première participante" msgstr "ajouter une facture"
msgid "add participants"
msgstr "ajouter des participant⋅es"
msgid "Password reminder" msgid "Password reminder"
msgstr "Rappel du code daccès" msgstr "Rappel du code daccès"
@ -906,20 +896,24 @@ msgstr "Changez votre code d'accès"
msgid "Invite people to join this project" msgid "Invite people to join this project"
msgstr "Invitez des personnes à rejoindre ce projet" msgstr "Invitez des personnes à rejoindre ce projet"
msgid "Share an invitation link" msgid "Share Identifier & code"
msgstr "Partager un lien d'invitation" msgstr "Partager l'identifiant et le code"
msgid "" msgid ""
"The easiest way to invite people is to give them the following invitation" "You can share the project identifier and the private code by any "
" link.<br />They will be able to access the project, manage participants," "communication means."
" add/edit/delete bills. However, they will not have access to important "
"settings such as changing the private code or deleting the whole project."
msgstr "" msgstr ""
"Pour inviter des personnes dans ce projet, vous pouvez leur donner le " "Vous pouvez partager l'identifiant de ce projet et le code d'accès par "
"lien d'invitation ci-dessous.<br />Elles pourront ainsi accéder au " "d'autres moyens."
"projet, gérer les participants, ajouter/modifier/supprimer des factures. "
"En revanche, elles ne pourront pas modifier des paramètres importants " msgid "Identifier:"
"tels que le code d'accès ou supprimer le projet." msgstr "Identifiant :"
msgid "Share the Link"
msgstr "Partagez le lien"
msgid "You can directly share the following link via your prefered medium"
msgstr "Vous pouvez directement partager le lien suivant"
msgid "Scan QR code" msgid "Scan QR code"
msgstr "Scannez le QR code" msgstr "Scannez le QR code"
@ -931,37 +925,13 @@ msgid "Send via Emails"
msgstr "Envoyer par email(s)" msgstr "Envoyer par email(s)"
msgid "" msgid ""
"Specify a list of email adresses (separated by comma) of people you want " "Specify a (comma separated) list of email adresses you want to notify "
"to notify about the creation of this project. We will send them an email " "about the\n"
"with the invitation link." " creation of this budget management project and we will "
"send them an email for you."
msgstr "" msgstr ""
"Entrez les emails des personnes avec qui vous souhaitez partager ce projet (" "Entrez les emails des personnes avec qui vous souhaitez partager ce "
"en séparant les adresses emails avec des virgules). Nous leur enverrons un " "projet, nous leur enverrons un lien d'invitation."
"mail avec le lien d'invitation."
msgid "Share Identifier & code"
msgstr "Partager l'identifiant et le code"
msgid ""
"You can share the project identifier and the private code by any "
"communication means.<br />Anyone with the private code will have access "
"to the full project, including changing settings such as the private code"
" or project email address, or even deleting the whole project."
msgstr ""
"Vous pouvez partager l'identifiant de ce projet et le code d'accès par "
"tout moyen de votre choix.<br />Une personne possédant le code d'accès "
"aura un accès complet au projet, y compris pour changer le code d'accès "
"ou l'adresse email associée au projet, voire même supprimer complètement "
"le projet."
msgid "Identifier:"
msgstr "Identifiant :"
msgid "Private code:"
msgstr "Code daccès :"
msgid "the private code was defined when you created the project"
msgstr "le code d'accès a été défini lorsque vous avez créé le projet"
msgid "Who pays?" msgid "Who pays?"
msgstr "Qui doit payer ?" msgstr "Qui doit payer ?"
@ -1368,23 +1338,3 @@ msgstr "Période"
#~ msgid "Edit the project" #~ msgid "Edit the project"
#~ msgstr "Éditer le projet" #~ msgstr "Éditer le projet"
#~ msgid "You probably want to"
#~ msgstr "Vous souhaitez sûrement"
#~ msgid "add participants"
#~ msgstr "ajouter des participant⋅es"
#~ msgid ""
#~ "You can share the project identifier "
#~ "and the private code by any "
#~ "communication means."
#~ msgstr ""
#~ "Vous pouvez partager l'identifiant de ce"
#~ " projet et le code d'accès par "
#~ "d'autres moyens."
#~ msgid "Share the Link"
#~ msgstr "Partagez le lien"
#~ msgid "You can directly share the following link via your prefered medium"
#~ msgstr "Vous pouvez directement partager le lien suivant"

Binary file not shown.

View file

@ -3,9 +3,9 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-07-29 14:24+0200\n" "POT-Creation-Date: 2023-07-14 10:01+0200\n"
"PO-Revision-Date: 2023-07-24 07:07+0000\n" "PO-Revision-Date: 2022-11-07 10:07+0000\n"
"Last-Translator: Nati Lintzer <nlintzer@gmail.com>\n" "Last-Translator: Raanan Katz <raakatz97@gmail.com>\n"
"Language: he\n" "Language: he\n"
"Language-Team: Hebrew <https://hosted.weblate.org/projects/i-hate-money/i" "Language-Team: Hebrew <https://hosted.weblate.org/projects/i-hate-money/i"
"-hate-money/he/>\n" "-hate-money/he/>\n"
@ -28,13 +28,6 @@ msgstr "כמות או ביטוי לא תקין. יש להזין רק מספרי
msgid "Project name" msgid "Project name"
msgstr "שם הפרויקט" msgstr "שם הפרויקט"
#, fuzzy
msgid "Current private code"
msgstr "קוד פרטי חדש"
msgid "Enter existing private code to edit project"
msgstr ""
msgid "New private code" msgid "New private code"
msgstr "קוד פרטי חדש" msgstr "קוד פרטי חדש"
@ -56,12 +49,6 @@ msgstr "מטבע ברירת המחדל"
msgid "Setting a default currency enables currency conversion between bills" msgid "Setting a default currency enables currency conversion between bills"
msgstr "בחירת מטבע ברירת מחדל מאפשרת המרת מטבע בין חשבונות" msgstr "בחירת מטבע ברירת מחדל מאפשרת המרת מטבע בין חשבונות"
msgid "Unknown error"
msgstr "שגיאה לא ידועה"
msgid "Invalid private code."
msgstr "קוד פרטי לא תקין."
msgid "" msgid ""
"This project cannot be set to 'no currency' because it contains bills in " "This project cannot be set to 'no currency' because it contains bills in "
"multiple currencies." "multiple currencies."
@ -99,6 +86,12 @@ msgstr "אנא אמת את ה-Captcha כדי להמשיך."
msgid "Enter private code to confirm deletion" msgid "Enter private code to confirm deletion"
msgstr "הזן את הקוד הפרטי כדי לאשר מחיקה" msgstr "הזן את הקוד הפרטי כדי לאשר מחיקה"
msgid "Unknown error"
msgstr "שגיאה לא ידועה"
msgid "Invalid private code."
msgstr "קוד פרטי לא תקין."
msgid "Get in" msgid "Get in"
msgstr "היכנס" msgstr "היכנס"
@ -179,25 +172,25 @@ msgid "People to notify"
msgstr "אנשים להתריע להם" msgstr "אנשים להתריע להם"
msgid "Send the invitations" msgid "Send the invitations"
msgstr "שלח את ההזמנות" msgstr ""
#, python-format #, python-format
msgid "The email %(email)s is not valid" msgid "The email %(email)s is not valid"
msgstr "כתובת הדוא\"ל %(email)s אינה תקינה" msgstr "כתובת הדוא\"ל %(email)s אינה תקינה"
msgid "Logout" msgid "Logout"
msgstr "להתנתק" msgstr ""
msgid "Please check the email configuration of the server." msgid "Please check the email configuration of the server."
msgstr "" msgstr ""
#, python-format #, fuzzy, python-format
msgid "" msgid ""
"Please check the email configuration of the server or contact the " "Please check the email configuration of the server or contact the "
"administrator: %(admin_email)s" "administrator: %(admin_email)s"
msgstr "" msgstr ""
"מצטערים, אך אירעה שגיאה בעת ששלחנו לכם את המייל עם הוראות איפוס הסיסמה. " "מצטערים, אך אירעה שגיאה בעת ששלחנו לכם את המייל עם הוראות איפוס הסיסמה. "
"אנא בדקו את הגדרות המייל בשרת או פנו למנהל השרת:%(admin_email)s" "בבקשה תבדקו את הגדרות המייל בשרת או פנו למנהל השרת."
#. List with two items only #. List with two items only
msgid "{dual_object_0} and {dual_object_1}" msgid "{dual_object_0} and {dual_object_1}"
@ -226,8 +219,9 @@ msgstr "{prefix}: {error}"
msgid "{prefix}:<br />{errors}" msgid "{prefix}:<br />{errors}"
msgstr "{prefix}:<br />{errors}" msgstr "{prefix}:<br />{errors}"
#, fuzzy
msgid "Too many failed login attempts." msgid "Too many failed login attempts."
msgstr "יותר מדי ניסיון התחברות כושלים." msgstr "יותר מדי ניסיון התחברות כושלים, אנא נסו שם מאוחר יותר."
#, python-format #, python-format
msgid "This admin password is not the right one. Only %(num)d attempts left." msgid "This admin password is not the right one. Only %(num)d attempts left."
@ -269,15 +263,12 @@ msgstr "פרויקט לא ידוע"
msgid "Password successfully reset." msgid "Password successfully reset."
msgstr "הסיסמה אופסה בהצלחה." msgstr "הסיסמה אופסה בהצלחה."
msgid "Project settings have been changed successfully."
msgstr ""
msgid "Unable to parse CSV" msgid "Unable to parse CSV"
msgstr "" msgstr ""
#, python-format #, python-format
msgid "Missing attribute: %(attribute)s" msgid "Missing attribute: %(attribute)s"
msgstr "תכונה חסרה:%(attribute)s" msgstr ""
msgid "" msgid ""
"Cannot add bills in multiple currencies to a project without default " "Cannot add bills in multiple currencies to a project without default "
@ -294,7 +285,7 @@ msgid "Error deleting project"
msgstr "שגיאה במחיקת הפרויקט" msgstr "שגיאה במחיקת הפרויקט"
msgid "Unable to logout" msgid "Unable to logout"
msgstr "לא ניתן להתנתק" msgstr ""
#, python-format #, python-format
msgid "You have been invited to share your expenses for %(project)s" msgid "You have been invited to share your expenses for %(project)s"
@ -304,7 +295,7 @@ msgid "Your invitations have been sent"
msgstr "ההזמנות שלך נשלחו" msgstr "ההזמנות שלך נשלחו"
msgid "Sorry, there was an error while trying to send the invitation emails." msgid "Sorry, there was an error while trying to send the invitation emails."
msgstr "מצטערים, אך אירעה שגיאה בעת שליחת ההזמנות." msgstr ""
#, python-format #, python-format
msgid "%(member)s has been added" msgid "%(member)s has been added"
@ -335,21 +326,20 @@ msgid "Participant '%(name)s' has been modified"
msgstr "" msgstr ""
msgid "The bill has been added" msgid "The bill has been added"
msgstr "החשבון נוסף" msgstr ""
msgid "Error deleting bill" msgid "Error deleting bill"
msgstr "שגיאה בעת מחיקת החשבון" msgstr ""
#, fuzzy
msgid "The bill has been deleted" msgid "The bill has been deleted"
msgstr "ההוצאה נמחקה" msgstr ""
msgid "The bill has been modified" msgid "The bill has been modified"
msgstr "" msgstr ""
#, python-format #, python-format
msgid "%(lang)s is not a supported language" msgid "%(lang)s is not a supported language"
msgstr "%(lang)s אינה שפה נתמכת" msgstr ""
msgid "Error deleting project history" msgid "Error deleting project history"
msgstr "שגיאה במחיקת הסטוריית הפרויקט" msgstr "שגיאה במחיקת הסטוריית הפרויקט"
@ -364,7 +354,7 @@ msgid "Deleted recorded IP addresses in project history."
msgstr "" msgstr ""
msgid "Sorry, we were unable to find the page you've asked for." msgid "Sorry, we were unable to find the page you've asked for."
msgstr "מצטערים, לא הצלחנו לגשת לדף שחיפשת." msgstr ""
msgid "The best thing to do is probably to get back to the main page." msgid "The best thing to do is probably to get back to the main page."
msgstr "כנראה שהדבר הטוב ביותר לעשות הוא לחזור לעמוד הראשי." msgstr "כנראה שהדבר הטוב ביותר לעשות הוא לחזור לעמוד הראשי."
@ -388,7 +378,7 @@ msgid "?"
msgstr "?" msgstr "?"
msgid "Create a new project" msgid "Create a new project"
msgstr "צור פרויקט חדש" msgstr "צור פרויקט"
msgid "Project" msgid "Project"
msgstr "פרויקט" msgstr "פרויקט"
@ -409,7 +399,7 @@ msgid "Actions"
msgstr "פעולות" msgstr "פעולות"
msgid "edit" msgid "edit"
msgstr "ערוך" msgstr ""
msgid "Delete project" msgid "Delete project"
msgstr "מחק פרויקט" msgstr "מחק פרויקט"
@ -429,8 +419,9 @@ msgstr ""
msgid "Edit project" msgid "Edit project"
msgstr "" msgstr ""
#, fuzzy
msgid "Import project" msgid "Import project"
msgstr "ייבא פרוייקטים" msgstr "הפרויקטים שלך"
msgid "Download project's data" msgid "Download project's data"
msgstr "" msgstr ""
@ -462,44 +453,45 @@ msgstr ""
msgid "This will remove all bills and participants in this project!" msgid "This will remove all bills and participants in this project!"
msgstr "" msgstr ""
#, fuzzy
msgid "Import previously exported project" msgid "Import previously exported project"
msgstr "ייבא פרוייקט קודם מיוצא" msgstr "ייבא קובץ JSON מיוצא"
msgid "Choose file" msgid "Choose file"
msgstr "בחר קובץ" msgstr "בחר קובץ"
msgid "Edit this bill" msgid "Edit this bill"
msgstr "ערוך את החשבון" msgstr ""
msgid "Add a bill" msgid "Add a bill"
msgstr "הוסף חשבון" msgstr ""
msgid "Simple operations are allowed, e.g. (18+36.2)/3" msgid "Simple operations are allowed, e.g. (18+36.2)/3"
msgstr "" msgstr ""
msgid "Everyone" msgid "Everyone"
msgstr "כולם" msgstr ""
msgid "No one" msgid "No one"
msgstr "אף אחד" msgstr ""
msgid "More options" msgid "More options"
msgstr "יותר אפשרויות" msgstr ""
msgid "Add participant" msgid "Add participant"
msgstr "הוסף משתתפים" msgstr ""
msgid "Edit this participant" msgid "Edit this participant"
msgstr "ערוך את המשתתף" msgstr ""
msgid "john.doe@example.com, mary.moe@site.com" msgid "john.doe@example.com, mary.moe@site.com"
msgstr "john.doe@example.com, mary.moe@site.com" msgstr ""
msgid "Download" msgid "Download"
msgstr "הורד" msgstr ""
msgid "Disabled Project History" msgid "Disabled Project History"
msgstr "היסטורית פרויקט מושבתת" msgstr ""
msgid "Disabled Project History & IP Address Recording" msgid "Disabled Project History & IP Address Recording"
msgstr "" msgstr ""
@ -755,7 +747,7 @@ msgid "Projects"
msgstr "פרויקטים" msgstr "פרויקטים"
msgid "Start a new project" msgid "Start a new project"
msgstr "התחל פרוייקט חדש" msgstr ""
msgid "History" msgid "History"
msgstr "הסטוריה" msgstr "הסטוריה"
@ -763,14 +755,11 @@ msgstr "הסטוריה"
msgid "Settings" msgid "Settings"
msgstr "הגדרות" msgstr "הגדרות"
msgid "RSS Feed" msgid "Other projects :"
msgstr "" msgstr ""
msgid "Other projects :"
msgstr "פרויקטים אחרים:"
msgid "switch to" msgid "switch to"
msgstr "שנה ל" msgstr ""
msgid "Dashboard" msgid "Dashboard"
msgstr "" msgstr ""
@ -783,12 +772,12 @@ msgid "Code"
msgstr "קוד" msgstr "קוד"
msgid "Mobile Application" msgid "Mobile Application"
msgstr "יישום לנייד" msgstr ""
msgid "Documentation" msgid "Documentation"
msgstr "דוקומנטציה" msgstr "דוקומנטציה"
msgid "Administration Dashboard" msgid "Administation Dashboard"
msgstr "" msgstr ""
msgid "Legal information" msgid "Legal information"
@ -798,29 +787,29 @@ msgid "\"I hate money\" is free software"
msgstr "\"אני שונא כסף\" היא תוכנה חינמית" msgstr "\"אני שונא כסף\" היא תוכנה חינמית"
msgid "you can contribute and improve it!" msgid "you can contribute and improve it!"
msgstr "אתה יכול לתרום ולשפר אותו!" msgstr ""
#, python-format #, python-format
msgid "%(amount)s each" msgid "%(amount)s each"
msgstr "" msgstr ""
msgid "you sure?" msgid "you sure?"
msgstr "האם אתה בטוח?" msgstr ""
msgid "Invite people" msgid "Invite people"
msgstr "הזמן אנשים" msgstr ""
msgid "Newer bills" msgid "Newer bills"
msgstr "" msgstr ""
msgid "Older bills" msgid "Older bills"
msgstr "הוצאות ישנות" msgstr ""
msgid "You should start by adding participants" msgid "You should start by adding participants"
msgstr "כדאי להתחיל בהוספת משתמשים" msgstr ""
msgid "Add a new bill" msgid "Add a new bill"
msgstr "הוסף הוצאה חדשה" msgstr ""
msgid "For what?" msgid "For what?"
msgstr "עבור מה?" msgstr "עבור מה?"
@ -834,20 +823,22 @@ msgid "Everyone but %(excluded)s"
msgstr "" msgstr ""
msgid "delete" msgid "delete"
msgstr "מחק" msgstr ""
msgid "No bills" msgid "No bills"
msgstr "אין הוצאות" msgstr ""
msgid "Nothing to list yet." msgid "Nothing to list yet."
msgstr "" msgstr ""
msgid "Add your first bill" msgid "You probably want to"
msgstr "" msgstr ""
#, fuzzy msgid "add a bill"
msgid "Add the first participant" msgstr ""
msgstr "ערוך את המשתתף"
msgid "add participants"
msgstr ""
msgid "Password reminder" msgid "Password reminder"
msgstr "תזכורת סיסמה" msgstr "תזכורת סיסמה"
@ -855,7 +846,7 @@ msgstr "תזכורת סיסמה"
msgid "" msgid ""
"A link to reset your password has been sent to you, please check your " "A link to reset your password has been sent to you, please check your "
"emails." "emails."
msgstr "נשלח לחשבונך לינק לאיפוס הסיסמה, אנא בדוק את תיבת הדוא\"ל שלך." msgstr ""
msgid "Return to home page" msgid "Return to home page"
msgstr "חזור לעמוד הבית" msgstr "חזור לעמוד הבית"
@ -864,54 +855,42 @@ msgid "Your projects"
msgstr "הפרויקטים שלך" msgstr "הפרויקטים שלך"
msgid "Reset your password" msgid "Reset your password"
msgstr "אפס את סיסמתך" msgstr ""
msgid "Invite people to join this project" msgid "Invite people to join this project"
msgstr "הזמן אנשים להצטרף לפרויקט"
msgid "Share an invitation link"
msgstr ""
msgid ""
"The easiest way to invite people is to give them the following invitation"
" link.<br />They will be able to access the project, manage participants,"
" add/edit/delete bills. However, they will not have access to important "
"settings such as changing the private code or deleting the whole project."
msgstr ""
msgid "Scan QR code"
msgstr "סרוק את הברקוד"
msgid "Use a mobile device with a compatible app."
msgstr "השתמש במכשיר נייד עם יישום תואם."
msgid "Send via Emails"
msgstr "שלח באמצעות דוא\"ל"
msgid ""
"Specify a list of email adresses (separated by comma) of people you want "
"to notify about the creation of this project. We will send them an email "
"with the invitation link."
msgstr "" msgstr ""
msgid "Share Identifier & code" msgid "Share Identifier & code"
msgstr "שתף מזהה וקוד" msgstr ""
msgid "" msgid ""
"You can share the project identifier and the private code by any " "You can share the project identifier and the private code by any "
"communication means.<br />Anyone with the private code will have access " "communication means."
"to the full project, including changing settings such as the private code"
" or project email address, or even deleting the whole project."
msgstr "" msgstr ""
msgid "Identifier:" msgid "Identifier:"
msgstr "מזהה:" msgstr "מזהה:"
#, fuzzy msgid "Share the Link"
msgid "Private code:" msgstr ""
msgstr "קוד פרטי"
msgid "the private code was defined when you created the project" msgid "You can directly share the following link via your prefered medium"
msgstr ""
msgid "Scan QR code"
msgstr ""
msgid "Use a mobile device with a compatible app."
msgstr ""
msgid "Send via Emails"
msgstr ""
msgid ""
"Specify a (comma separated) list of email adresses you want to notify "
"about the\n"
" creation of this budget management project and we will "
"send them an email for you."
msgstr "" msgstr ""
msgid "Who pays?" msgid "Who pays?"
@ -924,7 +903,7 @@ msgid "Who?"
msgstr "מי?" msgstr "מי?"
msgid "Balance" msgid "Balance"
msgstr "מאזן" msgstr ""
msgid "deactivate" msgid "deactivate"
msgstr "" msgstr ""
@ -939,7 +918,7 @@ msgid "Spent"
msgstr "" msgstr ""
msgid "Expenses by Month" msgid "Expenses by Month"
msgstr "הוצאות לפי חודש" msgstr ""
msgid "Period" msgid "Period"
msgstr "תקופה" msgstr "תקופה"
@ -1004,43 +983,3 @@ msgstr "תקופה"
#~ msgid "Edit the project" #~ msgid "Edit the project"
#~ msgstr "" #~ msgstr ""
#~ msgid "You probably want to"
#~ msgstr "אתה כנראה רוצה"
#~ msgid "add a bill"
#~ msgstr ""
#~ msgid "add participants"
#~ msgstr "הוסף משתמשים"
#~ msgid ""
#~ "You can share the project identifier "
#~ "and the private code by any "
#~ "communication means."
#~ msgstr "אתה יכול לשתף את מזהה הפרויקט והקוד הפרטי באמצעות כל אמצעי תקשורת."
#~ msgid "Share the Link"
#~ msgstr "שתף את הלינק"
#~ msgid "You can directly share the following link via your prefered medium"
#~ msgstr "אתה יכול לשתף ישירות את הלינק באמצעי המועדף עליך"
#~ msgid ""
#~ "Specify a (comma separated) list of "
#~ "email adresses you want to notify "
#~ "about the\n"
#~ " creation of this budget "
#~ "management project and we will send "
#~ "them an email for you."
#~ msgstr ""
#~ msgid ""
#~ "Specify a (comma separated) list of "
#~ "email adresses you want to notify "
#~ "about the\n"
#~ " creation of this budget "
#~ "management project and we will send "
#~ "them an email with the invitation "
#~ "link."
#~ msgstr ""

Binary file not shown.

View file

@ -3,7 +3,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-07-29 14:24+0200\n" "POT-Creation-Date: 2023-07-14 10:01+0200\n"
"PO-Revision-Date: 2020-06-14 14:41+0000\n" "PO-Revision-Date: 2020-06-14 14:41+0000\n"
"Last-Translator: raghupalash <singhpalash0@gmail.com>\n" "Last-Translator: raghupalash <singhpalash0@gmail.com>\n"
"Language: hi\n" "Language: hi\n"
@ -29,13 +29,6 @@ msgstr ""
msgid "Project name" msgid "Project name"
msgstr "परियोजना का नाम" msgstr "परियोजना का नाम"
#, fuzzy
msgid "Current private code"
msgstr "निजी कोड"
msgid "Enter existing private code to edit project"
msgstr ""
#, fuzzy #, fuzzy
msgid "New private code" msgid "New private code"
msgstr "निजी कोड" msgstr "निजी कोड"
@ -58,14 +51,6 @@ msgstr "डिफ़ॉल्ट मुद्रा"
msgid "Setting a default currency enables currency conversion between bills" msgid "Setting a default currency enables currency conversion between bills"
msgstr "" msgstr ""
#, fuzzy
msgid "Unknown error"
msgstr "अज्ञात परियोजना"
#, fuzzy
msgid "Invalid private code."
msgstr "निजी कोड"
msgid "" msgid ""
"This project cannot be set to 'no currency' because it contains bills in " "This project cannot be set to 'no currency' because it contains bills in "
"multiple currencies." "multiple currencies."
@ -104,6 +89,14 @@ msgstr ""
msgid "Enter private code to confirm deletion" msgid "Enter private code to confirm deletion"
msgstr "" msgstr ""
#, fuzzy
msgid "Unknown error"
msgstr "अज्ञात परियोजना"
#, fuzzy
msgid "Invalid private code."
msgstr "निजी कोड"
msgid "Get in" msgid "Get in"
msgstr "अंदर जाइये" msgstr "अंदर जाइये"
@ -278,9 +271,6 @@ msgstr "अज्ञात परियोजना"
msgid "Password successfully reset." msgid "Password successfully reset."
msgstr "पासवर्ड सफलतापूर्वक रीसेट हो गया है।" msgstr "पासवर्ड सफलतापूर्वक रीसेट हो गया है।"
msgid "Project settings have been changed successfully."
msgstr ""
msgid "Unable to parse CSV" msgid "Unable to parse CSV"
msgstr "" msgstr ""
@ -804,9 +794,6 @@ msgstr "इतिहास"
msgid "Settings" msgid "Settings"
msgstr "सेटिंग्स" msgstr "सेटिंग्स"
msgid "RSS Feed"
msgstr ""
msgid "Other projects :" msgid "Other projects :"
msgstr "अन्य परियोजनाएँ :" msgstr "अन्य परियोजनाएँ :"
@ -829,7 +816,7 @@ msgstr "मोबाइल एप्लीकेशन"
msgid "Documentation" msgid "Documentation"
msgstr "प्रलेखन" msgstr "प्रलेखन"
msgid "Administration Dashboard" msgid "Administation Dashboard"
msgstr "व्यवस्थापन डैशबोर्ड" msgstr "व्यवस्थापन डैशबोर्ड"
#, fuzzy #, fuzzy
@ -884,13 +871,14 @@ msgstr "कोई बिल नहीं"
msgid "Nothing to list yet." msgid "Nothing to list yet."
msgstr "सूचि बनाने के लिए कुछ नहीं।" msgstr "सूचि बनाने के लिए कुछ नहीं।"
#, fuzzy msgid "You probably want to"
msgid "Add your first bill" msgstr "आप शायद यह करना चाहते हैं"
msgid "add a bill"
msgstr "बिल जोड़ें" msgstr "बिल जोड़ें"
#, fuzzy msgid "add participants"
msgid "Add the first participant" msgstr "प्रतिभागियों को जोड़ें"
msgstr "प्रतिभागी जोड़ें"
msgid "Password reminder" msgid "Password reminder"
msgstr "पासवर्ड अनुस्मारक" msgstr "पासवर्ड अनुस्मारक"
@ -914,15 +902,24 @@ msgstr "अपना पासवर्ड रीसेट करें"
msgid "Invite people to join this project" msgid "Invite people to join this project"
msgstr "इस परियोजना से जुड़ने के लिए लोगों को आमंत्रित करें" msgstr "इस परियोजना से जुड़ने के लिए लोगों को आमंत्रित करें"
msgid "Share an invitation link" msgid "Share Identifier & code"
msgstr "" msgstr "पहचानकर्ता और कोड साझा करें"
msgid "" msgid ""
"The easiest way to invite people is to give them the following invitation" "You can share the project identifier and the private code by any "
" link.<br />They will be able to access the project, manage participants," "communication means."
" add/edit/delete bills. However, they will not have access to important "
"settings such as changing the private code or deleting the whole project."
msgstr "" msgstr ""
"आप किसी भी संचार माध्यम से परियोजना पहचानकर्ता और निजी कोड साझा कर सकते "
"हैं।"
msgid "Identifier:"
msgstr "पहचानकर्ता:"
msgid "Share the Link"
msgstr "लिंक साझा करें"
msgid "You can directly share the following link via your prefered medium"
msgstr "आप नीचे दिए गए लिंक को सीधे अपने पसंदीदा माध्यम से साझा कर सकते हैं"
msgid "Scan QR code" msgid "Scan QR code"
msgstr "" msgstr ""
@ -933,37 +930,17 @@ msgstr ""
msgid "Send via Emails" msgid "Send via Emails"
msgstr "ईमेल के माध्यम से भेजें" msgstr "ईमेल के माध्यम से भेजें"
#, fuzzy
msgid "" msgid ""
"Specify a list of email adresses (separated by comma) of people you want " "Specify a (comma separated) list of email adresses you want to notify "
"to notify about the creation of this project. We will send them an email " "about the\n"
"with the invitation link." " creation of this budget management project and we will "
"send them an email for you."
msgstr "" msgstr ""
"उन ईमेल पतों की एक (अल्पविराम से अलग की गयी) सूची निर्दिष्ट करें जिन्हे " "उन ईमेल पतों की एक (अल्पविराम से अलग की गयी) सूची निर्दिष्ट करें जिन्हे "
"आप इस \n" "आप इस \n"
"\t\t बजट प्रबंधन परियोजना के निर्माण के बारे में सूचित करना चाहते हैं " "\t\t बजट प्रबंधन परियोजना के निर्माण के बारे में सूचित करना चाहते हैं "
"और हम उन्हें आपके लिए एक ईमेल भेजेंगे।" "और हम उन्हें आपके लिए एक ईमेल भेजेंगे।"
msgid "Share Identifier & code"
msgstr "पहचानकर्ता और कोड साझा करें"
msgid ""
"You can share the project identifier and the private code by any "
"communication means.<br />Anyone with the private code will have access "
"to the full project, including changing settings such as the private code"
" or project email address, or even deleting the whole project."
msgstr ""
msgid "Identifier:"
msgstr "पहचानकर्ता:"
#, fuzzy
msgid "Private code:"
msgstr "निजी कोड"
msgid "the private code was defined when you created the project"
msgstr ""
msgid "Who pays?" msgid "Who pays?"
msgstr "किसे भुगतान करना है?" msgstr "किसे भुगतान करना है?"
@ -1133,24 +1110,3 @@ msgstr "अवधि"
#~ msgid "Edit the project" #~ msgid "Edit the project"
#~ msgstr "प्रोजेक्ट संपादित करें" #~ msgstr "प्रोजेक्ट संपादित करें"
#~ msgid "You probably want to"
#~ msgstr "आप शायद यह करना चाहते हैं"
#~ msgid "add participants"
#~ msgstr "प्रतिभागियों को जोड़ें"
#~ msgid ""
#~ "You can share the project identifier "
#~ "and the private code by any "
#~ "communication means."
#~ msgstr ""
#~ "आप किसी भी संचार माध्यम से "
#~ "परियोजना पहचानकर्ता और निजी कोड साझा "
#~ "कर सकते हैं।"
#~ msgid "Share the Link"
#~ msgstr "लिंक साझा करें"
#~ msgid "You can directly share the following link via your prefered medium"
#~ msgstr "आप नीचे दिए गए लिंक को सीधे अपने पसंदीदा माध्यम से साझा कर सकते हैं"

Binary file not shown.

File diff suppressed because it is too large Load diff

Binary file not shown.

View file

@ -3,7 +3,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-07-29 14:24+0200\n" "POT-Creation-Date: 2023-07-14 10:01+0200\n"
"PO-Revision-Date: 2022-04-11 17:12+0000\n" "PO-Revision-Date: 2022-04-11 17:12+0000\n"
"Last-Translator: Santiago José Gutiérrez Llanos " "Last-Translator: Santiago José Gutiérrez Llanos "
"<gutierrezapata17@gmail.com>\n" "<gutierrezapata17@gmail.com>\n"
@ -30,13 +30,6 @@ msgstr ""
msgid "Project name" msgid "Project name"
msgstr "Nama proyek" msgstr "Nama proyek"
#, fuzzy
msgid "Current private code"
msgstr "Kode pribadi baru"
msgid "Enter existing private code to edit project"
msgstr ""
msgid "New private code" msgid "New private code"
msgstr "Kode pribadi baru" msgstr "Kode pribadi baru"
@ -58,12 +51,6 @@ msgstr "Mata Uang Standar"
msgid "Setting a default currency enables currency conversion between bills" msgid "Setting a default currency enables currency conversion between bills"
msgstr "Menetapkan mata uang default memungkinkan konversi mata uang antar tagihan" msgstr "Menetapkan mata uang default memungkinkan konversi mata uang antar tagihan"
msgid "Unknown error"
msgstr "Kesalahan tidak diketahui"
msgid "Invalid private code."
msgstr "Kode pribadi tidak valid."
msgid "" msgid ""
"This project cannot be set to 'no currency' because it contains bills in " "This project cannot be set to 'no currency' because it contains bills in "
"multiple currencies." "multiple currencies."
@ -103,6 +90,12 @@ msgstr "Mohon, silahkan validasi captcha untuk melanjutkan."
msgid "Enter private code to confirm deletion" msgid "Enter private code to confirm deletion"
msgstr "Masukkan kode pribadi untuk mengkonfirmasi penghapusan" msgstr "Masukkan kode pribadi untuk mengkonfirmasi penghapusan"
msgid "Unknown error"
msgstr "Kesalahan tidak diketahui"
msgid "Invalid private code."
msgstr "Kode pribadi tidak valid."
msgid "Get in" msgid "Get in"
msgstr "Masuk" msgstr "Masuk"
@ -275,9 +268,6 @@ msgstr "Proyek tidak diketahui"
msgid "Password successfully reset." msgid "Password successfully reset."
msgstr "Kata sandi berhasil diatur ulang." msgstr "Kata sandi berhasil diatur ulang."
msgid "Project settings have been changed successfully."
msgstr ""
msgid "Unable to parse CSV" msgid "Unable to parse CSV"
msgstr "" msgstr ""
@ -787,9 +777,6 @@ msgstr "Riwayat"
msgid "Settings" msgid "Settings"
msgstr "Pengaturan" msgstr "Pengaturan"
msgid "RSS Feed"
msgstr ""
msgid "Other projects :" msgid "Other projects :"
msgstr "Proyek lainnya:" msgstr "Proyek lainnya:"
@ -812,7 +799,7 @@ msgstr "Aplikasi Gawai"
msgid "Documentation" msgid "Documentation"
msgstr "Dokumentasi" msgstr "Dokumentasi"
msgid "Administration Dashboard" msgid "Administation Dashboard"
msgstr "Dasbor Administrasi" msgstr "Dasbor Administrasi"
msgid "Legal information" msgid "Legal information"
@ -866,13 +853,14 @@ msgstr "Tidak ada tagihan"
msgid "Nothing to list yet." msgid "Nothing to list yet."
msgstr "Belum ada untuk didaftarkan." msgstr "Belum ada untuk didaftarkan."
#, fuzzy msgid "You probably want to"
msgid "Add your first bill" msgstr "Anda mungkin menginginkan"
msgid "add a bill"
msgstr "tambah tagihan" msgstr "tambah tagihan"
#, fuzzy msgid "add participants"
msgid "Add the first participant" msgstr "tambah partisipan"
msgstr "Sunting anggota ini"
msgid "Password reminder" msgid "Password reminder"
msgstr "Pengingat kata sandi" msgstr "Pengingat kata sandi"
@ -896,15 +884,26 @@ msgstr "Atur ulang kata sandi Anda"
msgid "Invite people to join this project" msgid "Invite people to join this project"
msgstr "Undang orang untuk bergabung dalam proyek ini" msgstr "Undang orang untuk bergabung dalam proyek ini"
msgid "Share an invitation link" msgid "Share Identifier & code"
msgstr "" msgstr "Bagikan ID & kode"
msgid "" msgid ""
"The easiest way to invite people is to give them the following invitation" "You can share the project identifier and the private code by any "
" link.<br />They will be able to access the project, manage participants," "communication means."
" add/edit/delete bills. However, they will not have access to important "
"settings such as changing the private code or deleting the whole project."
msgstr "" msgstr ""
"Anda bisa membagikan ID proyek dan kode pribadi dengan cara komunikasi "
"apapun."
msgid "Identifier:"
msgstr "ID:"
msgid "Share the Link"
msgstr "Bagikan tautan"
msgid "You can directly share the following link via your prefered medium"
msgstr ""
"Anda bisa membagikan tautan secara langsung melalui media yang Anda "
"inginkan"
msgid "Scan QR code" msgid "Scan QR code"
msgstr "" msgstr ""
@ -915,37 +914,17 @@ msgstr ""
msgid "Send via Emails" msgid "Send via Emails"
msgstr "Kirim melalui surel" msgstr "Kirim melalui surel"
#, fuzzy
msgid "" msgid ""
"Specify a list of email adresses (separated by comma) of people you want " "Specify a (comma separated) list of email adresses you want to notify "
"to notify about the creation of this project. We will send them an email " "about the\n"
"with the invitation link." " creation of this budget management project and we will "
"send them an email for you."
msgstr "" msgstr ""
"Spesifikkan daftar alamat surel (dipisah dengan koma) yang akan Anda " "Spesifikkan daftar alamat surel (dipisah dengan koma) yang akan Anda "
"kirim pemberitahuan tentang\n" "kirim pemberitahuan tentang\n"
" pembuatan dari manajemen anggaran proyek ini dan kami " " pembuatan dari manajemen anggaran proyek ini dan kami "
"akan memngirim mereka sebuah surel." "akan memngirim mereka sebuah surel."
msgid "Share Identifier & code"
msgstr "Bagikan ID & kode"
msgid ""
"You can share the project identifier and the private code by any "
"communication means.<br />Anyone with the private code will have access "
"to the full project, including changing settings such as the private code"
" or project email address, or even deleting the whole project."
msgstr ""
msgid "Identifier:"
msgstr "ID:"
#, fuzzy
msgid "Private code:"
msgstr "Kode pribadi"
msgid "the private code was defined when you created the project"
msgstr ""
msgid "Who pays?" msgid "Who pays?"
msgstr "Siapa membayar?" msgstr "Siapa membayar?"
@ -1142,27 +1121,3 @@ msgstr "Periode"
#~ msgid "Edit the project" #~ msgid "Edit the project"
#~ msgstr "Ubah proyek" #~ msgstr "Ubah proyek"
#~ msgid "You probably want to"
#~ msgstr "Anda mungkin menginginkan"
#~ msgid "add participants"
#~ msgstr "tambah partisipan"
#~ msgid ""
#~ "You can share the project identifier "
#~ "and the private code by any "
#~ "communication means."
#~ msgstr ""
#~ "Anda bisa membagikan ID proyek dan "
#~ "kode pribadi dengan cara komunikasi "
#~ "apapun."
#~ msgid "Share the Link"
#~ msgstr "Bagikan tautan"
#~ msgid "You can directly share the following link via your prefered medium"
#~ msgstr ""
#~ "Anda bisa membagikan tautan secara "
#~ "langsung melalui media yang Anda "
#~ "inginkan"

Binary file not shown.

View file

@ -1,18 +1,18 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-07-29 14:24+0200\n" "POT-Creation-Date: 2023-07-14 10:01+0200\n"
"PO-Revision-Date: 2023-08-04 17:05+0000\n" "PO-Revision-Date: 2022-07-12 15:18+0000\n"
"Last-Translator: Alessandro Andro <pasandro.pasandro@gmail.com>\n" "Last-Translator: Matteo Piotto <piotto@gmail.com>\n"
"Language-Team: Italian <https://hosted.weblate.org/projects/i-hate-money/"
"i-hate-money/it/>\n"
"Language: it\n" "Language: it\n"
"Language-Team: Italian <https://hosted.weblate.org/projects/i-hate-"
"money/i-hate-money/it/>\n"
"Plural-Forms: nplurals=2; plural=n != 1\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n" "Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.0-dev\n"
"Generated-By: Babel 2.9.0\n" "Generated-By: Babel 2.9.0\n"
#, python-format #, python-format
@ -23,56 +23,45 @@ msgid ""
"Not a valid amount or expression. Only numbers and + - * / operators are " "Not a valid amount or expression. Only numbers and + - * / operators are "
"accepted." "accepted."
msgstr "" msgstr ""
"Quantità o espressione non valida. Sono accettati solo numeri e gli " "Quantità o espressione non valida. Solo numeri e operatori + - * / "
"operatori + - * / ." "accettati."
msgid "Project name" msgid "Project name"
msgstr "Nome del progetto" msgstr "Nome del progetto"
msgid "Current private code" #, fuzzy
msgstr "Codice privato corrente"
msgid "Enter existing private code to edit project"
msgstr "Inserisci il codice privato corrente per modificare il progetto"
msgid "New private code" msgid "New private code"
msgstr "Nuovo codice privato" msgstr "Codice privato"
msgid "Enter a new code if you want to change it" msgid "Enter a new code if you want to change it"
msgstr "Se vuoi modificarlo, inserisci un nuovo codice" msgstr "Inserisci un nuovo codice se lo vuoi modificare"
msgid "Email" msgid "Email"
msgstr "Email" msgstr "Email"
msgid "Enable project history" msgid "Enable project history"
msgstr "Abilita la cronologia del progetto" msgstr "Attivare la cronologia del progetto"
msgid "Use IP tracking for project history" msgid "Use IP tracking for project history"
msgstr "Utilizzare la localizzazione IP per la cronologia del progetto" msgstr "Utilizzare la localizzazione IP per lo storico del progetto"
msgid "Default Currency" msgid "Default Currency"
msgstr "Valuta predefinita" msgstr "Valuta predefinita"
msgid "Setting a default currency enables currency conversion between bills" msgid "Setting a default currency enables currency conversion between bills"
msgstr "" msgstr ""
"L'impostazione di una valuta predefinita consente la conversione di valuta " "Impostando una valuta predefinita abilita la conversione della valuta tra"
"tra le fatture" " le fatture"
msgid "Unknown error"
msgstr "Errore sconosciuto"
msgid "Invalid private code."
msgstr "Codice privato non valido."
msgid "" msgid ""
"This project cannot be set to 'no currency' because it contains bills in " "This project cannot be set to 'no currency' because it contains bills in "
"multiple currencies." "multiple currencies."
msgstr "" msgstr ""
"Questo progetto non può essere impostato su \"nessuna valuta\" perché " "Questo progetto non può essere impostato come 'nessuna valuta' perché "
"contiene fatture in più valute." "contiene fatture in più valute."
msgid "Compatible with Cospend" msgid "Compatible with Cospend"
msgstr "Compatibile con Cospend" msgstr ""
msgid "Project identifier" msgid "Project identifier"
msgstr "Identificatore del progetto" msgstr "Identificatore del progetto"
@ -92,16 +81,25 @@ msgstr ""
"favore scegli un identificatore nuovo" "favore scegli un identificatore nuovo"
msgid "Which is a real currency: Euro or Petro dollar?" msgid "Which is a real currency: Euro or Petro dollar?"
msgstr "Qual è una valuta reale: Euro o Petrol dollar?" msgstr ""
#, fuzzy
msgid "euro" msgid "euro"
msgstr "euro" msgstr "Periodo"
msgid "Please, validate the captcha to proceed." msgid "Please, validate the captcha to proceed."
msgstr "Si prega di validare il captcha per procedere." msgstr "Si prega di validare il captcha per procedere."
msgid "Enter private code to confirm deletion" msgid "Enter private code to confirm deletion"
msgstr "Inserisci il codice privato per confermare la cancellazione" msgstr ""
#, fuzzy
msgid "Unknown error"
msgstr "Progetto non conosciuto"
#, fuzzy
msgid "Invalid private code."
msgstr "Codice privato non valido."
msgid "Get in" msgid "Get in"
msgstr "Entra" msgstr "Entra"
@ -173,12 +171,15 @@ msgstr "Peso"
msgid "Add" msgid "Add"
msgstr "Aggiungi" msgstr "Aggiungi"
#, fuzzy
msgid "The participant name is invalid" msgid "The participant name is invalid"
msgstr "Il nome del partecipante non è valido" msgstr "L'utente '%(name)s' è stato rimosso"
#, fuzzy
msgid "This project already have this participant" msgid "This project already have this participant"
msgstr "Membro già presente in questo progetto" msgstr "Membro già presente in questo progetto"
#, fuzzy
msgid "People to notify" msgid "People to notify"
msgstr "Persone da avvisare" msgstr "Persone da avvisare"
@ -193,43 +194,45 @@ msgid "Logout"
msgstr "Esci" msgstr "Esci"
msgid "Please check the email configuration of the server." msgid "Please check the email configuration of the server."
msgstr "Per favore verifica la configurazione delle email del server." msgstr ""
#, python-format #, fuzzy, python-format
msgid "" msgid ""
"Please check the email configuration of the server or contact the " "Please check the email configuration of the server or contact the "
"administrator: %(admin_email)s" "administrator: %(admin_email)s"
msgstr "" msgstr ""
"Verifica la configurazione dell'email sul server o contatta l'amministratore:" "Spiacenti, si è verificato un errore durante l'invio delle email di "
" %(admin_email)s" "invito. Verifica la configurazione dell'email sul server o contatta "
"l'amministratore."
#. List with two items only #. List with two items only
msgid "{dual_object_0} and {dual_object_1}" msgid "{dual_object_0} and {dual_object_1}"
msgstr "{dual_object_0} e {dual_object_1}" msgstr ""
#. Last two items of a list with more than 3 items #. Last two items of a list with more than 3 items
msgid "{previous_object}, and {end_object}" msgid "{previous_object}, and {end_object}"
msgstr "{previous_object}, e {end_object}" msgstr ""
#. Two items in a middle of a list with more than 5 objects #. Two items in a middle of a list with more than 5 objects
msgid "{previous_object}, {next_object}" msgid "{previous_object}, {next_object}"
msgstr "{previous_object}, {next_object}" msgstr ""
#. First two items of a list with more than 3 items #. First two items of a list with more than 3 items
msgid "{start_object}, {next_object}" msgid "{start_object}, {next_object}"
msgstr "{start_object}, {next_object}" msgstr ""
msgid "No Currency" msgid "No Currency"
msgstr "Nessuna valuta" msgstr "Nessuna valuta"
#. Form error with only one error #. Form error with only one error
msgid "{prefix}: {error}" msgid "{prefix}: {error}"
msgstr "{prefix}: {error}" msgstr ""
#. Form error with a list of errors #. Form error with a list of errors
msgid "{prefix}:<br />{errors}" msgid "{prefix}:<br />{errors}"
msgstr "{prefix}:<br />{errors}" msgstr ""
#, fuzzy
msgid "Too many failed login attempts." msgid "Too many failed login attempts."
msgstr "Troppi tentativi di accesso non riusciti. Riprova più tardi." msgstr "Troppi tentativi di accesso non riusciti. Riprova più tardi."
@ -240,7 +243,7 @@ msgstr ""
"tentativi rimasti." "tentativi rimasti."
msgid "Provided token is invalid" msgid "Provided token is invalid"
msgstr "Il token fornito non è valido" msgstr ""
msgid "This private code is not the right one" msgid "This private code is not the right one"
msgstr "Questo codice privato non è quello corretto" msgstr "Questo codice privato non è quello corretto"
@ -255,12 +258,14 @@ msgstr ""
"Abbiamo provato a inviarti un promemoria per email, ma si è verificato un" "Abbiamo provato a inviarti un promemoria per email, ma si è verificato un"
" errore. Puoi comunque utilizzare normalmente il progetto." " errore. Puoi comunque utilizzare normalmente il progetto."
#, fuzzy
msgid "" msgid ""
"Sorry, there was an error while sending you an email with password reset " "Sorry, there was an error while sending you an email with password reset "
"instructions." "instructions."
msgstr "" msgstr ""
"Spiacenti, si è verificato un errore durante l'invio dell'email con le " "Spiacenti, si è verificato un errore durante l'invio dell'email con le "
"istruzioni per il reset della password." "istruzioni per il reset della password. Verifica la configurazione "
"dell'email del server o contatta l'amministratore."
msgid "No token provided" msgid "No token provided"
msgstr "Nessun token fornito" msgstr "Nessun token fornito"
@ -274,22 +279,17 @@ msgstr "Progetto non conosciuto"
msgid "Password successfully reset." msgid "Password successfully reset."
msgstr "Reset della password effettuato." msgstr "Reset della password effettuato."
msgid "Project settings have been changed successfully."
msgstr "Impostazioni del progetto cambiate correttamente."
msgid "Unable to parse CSV" msgid "Unable to parse CSV"
msgstr "Impossibile analizzare il CSV" msgstr ""
#, python-format #, python-format
msgid "Missing attribute: %(attribute)s" msgid "Missing attribute: %(attribute)s"
msgstr "Attributo mancante: %(attribute)s" msgstr ""
msgid "" msgid ""
"Cannot add bills in multiple currencies to a project without default " "Cannot add bills in multiple currencies to a project without default "
"currency" "currency"
msgstr "" msgstr ""
"Impossibile aggiungere fatture in più valute a un progetto senza valuta "
"predefinita"
msgid "Project successfully uploaded" msgid "Project successfully uploaded"
msgstr "Progetto caricato con successo" msgstr "Progetto caricato con successo"
@ -298,10 +298,10 @@ msgid "Project successfully deleted"
msgstr "Progetto rimosso con successo" msgstr "Progetto rimosso con successo"
msgid "Error deleting project" msgid "Error deleting project"
msgstr "Errore nell'eliminazione del progetto" msgstr ""
msgid "Unable to logout" msgid "Unable to logout"
msgstr "Impossibile effettuare il logout" msgstr ""
#, python-format #, python-format
msgid "You have been invited to share your expenses for %(project)s" msgid "You have been invited to share your expenses for %(project)s"
@ -310,45 +310,48 @@ msgstr "Sei stato invitato a condividere le tue spese per %(project)s"
msgid "Your invitations have been sent" msgid "Your invitations have been sent"
msgstr "I tuoi inviti sono stati spediti" msgstr "I tuoi inviti sono stati spediti"
#, fuzzy
msgid "Sorry, there was an error while trying to send the invitation emails." msgid "Sorry, there was an error while trying to send the invitation emails."
msgstr "" msgstr ""
"Spiacenti, si è verificato un errore durante l'invio delle email di invito." "Spiacenti, si è verificato un errore durante l'invio delle email di "
"invito. Verifica la configurazione dell'email sul server o contatta "
"l'amministratore."
#, python-format #, python-format
msgid "%(member)s has been added" msgid "%(member)s has been added"
msgstr "%(member)s è stato aggiunto" msgstr "%(member)s è stato aggiunto"
msgid "Error activating participant" msgid "Error activating participant"
msgstr "Errore nell'attivazione del partecipante" msgstr ""
#, python-format #, python-format
msgid "%(name)s is part of this project again" msgid "%(name)s is part of this project again"
msgstr "%(name)s fa nuovamente parte di questo progetto" msgstr "%(name)s fa nuovamente parte di questo progetto"
msgid "Error removing participant" msgid "Error removing participant"
msgstr "Errore nella rimozione del partecipante" msgstr ""
#, python-format #, fuzzy, python-format
msgid "" msgid ""
"Participant '%(name)s' has been deactivated. It will still appear in the " "Participant '%(name)s' has been deactivated. It will still appear in the "
"list until its balance reach zero." "list until its balance reach zero."
msgstr "" msgstr ""
"Il partecipante '%(name)s' è stato disattivato. Comparirà ancora nella lista " "L'utente '%(name)s' è stato disattivato. Comparirà ancora nella lista "
"utenti finché il suo bilancio sarà superiore a zero." "utenti finché il suo bilancio sarà superiore a zero."
#, python-format #, fuzzy, python-format
msgid "Participant '%(name)s' has been removed" msgid "Participant '%(name)s' has been removed"
msgstr "Il partecipante '%(name)s' è stato rimosso" msgstr "L'utente '%(name)s' è stato rimosso"
#, python-format #, fuzzy, python-format
msgid "Participant '%(name)s' has been modified" msgid "Participant '%(name)s' has been modified"
msgstr "Il partecipante '%(name)s' è stato modificato" msgstr "L'utente '%(name)s' è stato rimosso"
msgid "The bill has been added" msgid "The bill has been added"
msgstr "L'addebito è stato aggiunto" msgstr "L'addebito è stato aggiunto"
msgid "Error deleting bill" msgid "Error deleting bill"
msgstr "Errore nella cancellazione della spesa" msgstr ""
msgid "The bill has been deleted" msgid "The bill has been deleted"
msgstr "La spesa è stata cancellata" msgstr "La spesa è stata cancellata"
@ -358,19 +361,22 @@ msgstr "L'addebito è stato aggiornato"
#, python-format #, python-format
msgid "%(lang)s is not a supported language" msgid "%(lang)s is not a supported language"
msgstr "%(lang)s non è una lingua supportata" msgstr ""
#, fuzzy
msgid "Error deleting project history" msgid "Error deleting project history"
msgstr "Errore nel cancellare la cronologia del progetto" msgstr "Attivare la cronologia del progetto"
#, fuzzy
msgid "Deleted project history." msgid "Deleted project history."
msgstr "Cronologia del progetto cancellata." msgstr "Cronologia del progetto cancellata."
#, fuzzy
msgid "Error deleting recorded IP addresses" msgid "Error deleting recorded IP addresses"
msgstr "Errore nel cancellare gli indirizzi IP salvati" msgstr "Cancella indirizzi IP conservati"
msgid "Deleted recorded IP addresses in project history." msgid "Deleted recorded IP addresses in project history."
msgstr "Eliminati gli indirizzi IP registrati nella cronologia del progetto." msgstr ""
msgid "Sorry, we were unable to find the page you've asked for." msgid "Sorry, we were unable to find the page you've asked for."
msgstr "Spiacenti, non abbiamo trovato la pagina che cerchi." msgstr "Spiacenti, non abbiamo trovato la pagina che cerchi."
@ -384,8 +390,9 @@ msgstr "Ritorna all'elenco"
msgid "Administration tasks are currently disabled." msgid "Administration tasks are currently disabled."
msgstr "In questo momento le funzionalità amministrative sono disabilitate." msgstr "In questo momento le funzionalità amministrative sono disabilitate."
#, fuzzy
msgid "Authentication" msgid "Authentication"
msgstr "Autenticazione" msgstr "Documentazione"
msgid "The project you are trying to access do not exist, do you want to" msgid "The project you are trying to access do not exist, do you want to"
msgstr "Il progetto cui stai provando ad accedere non esiste, vuoi" msgstr "Il progetto cui stai provando ad accedere non esiste, vuoi"
@ -402,8 +409,9 @@ msgstr "Crea un nuovo progetto"
msgid "Project" msgid "Project"
msgstr "Progetto" msgstr "Progetto"
#, fuzzy
msgid "Number of participants" msgid "Number of participants"
msgstr "Numro di partecipanti" msgstr "aggiunti partecipanti"
msgid "Number of bills" msgid "Number of bills"
msgstr "Numero di spese" msgstr "Numero di spese"
@ -420,8 +428,9 @@ msgstr "Azioni"
msgid "edit" msgid "edit"
msgstr "modifica" msgstr "modifica"
#, fuzzy
msgid "Delete project" msgid "Delete project"
msgstr "Elimina il progetto" msgstr "Modifica progetto"
msgid "show" msgid "show"
msgstr "visualizza" msgstr "visualizza"
@ -429,17 +438,20 @@ msgstr "visualizza"
msgid "The Dashboard is currently deactivated." msgid "The Dashboard is currently deactivated."
msgstr "Il Cruscotto è attualmente disabilitato." msgstr "Il Cruscotto è attualmente disabilitato."
#, fuzzy
msgid "Download Mobile Application" msgid "Download Mobile Application"
msgstr "Scarica l'applicazione per i dispositivi mobili" msgstr "Applicazione mobile"
#, fuzzy
msgid "Get it on" msgid "Get it on"
msgstr "Entra" msgstr "Entra"
msgid "Edit project" msgid "Edit project"
msgstr "Modifica progetto" msgstr "Modifica progetto"
#, fuzzy
msgid "Import project" msgid "Import project"
msgstr "Importa progetto" msgstr "Modifica progetto"
msgid "Download project's data" msgid "Download project's data"
msgstr "Scarica i dati del progetto" msgstr "Scarica i dati del progetto"
@ -468,13 +480,14 @@ msgid "Privacy Settings"
msgstr "Impostazioni Privacy" msgstr "Impostazioni Privacy"
msgid "Save changes" msgid "Save changes"
msgstr "Salva le modifiche" msgstr ""
msgid "This will remove all bills and participants in this project!" msgid "This will remove all bills and participants in this project!"
msgstr "Questo eliminerà tutte le spese e i partecipanti a questo progetto!" msgstr ""
#, fuzzy
msgid "Import previously exported project" msgid "Import previously exported project"
msgstr "Importa il file JSON esportato precedentemente" msgstr "Importare il file JSON esportato precedentemente"
msgid "Choose file" msgid "Choose file"
msgstr "Scegli file" msgstr "Scegli file"
@ -486,22 +499,23 @@ msgid "Add a bill"
msgstr "Aggiungi un addebito" msgstr "Aggiungi un addebito"
msgid "Simple operations are allowed, e.g. (18+36.2)/3" msgid "Simple operations are allowed, e.g. (18+36.2)/3"
msgstr "Sono permesse operazioni semplici, e.g. (18+36.2)/3" msgstr ""
msgid "Everyone" msgid "Everyone"
msgstr "Tutti" msgstr "Tutti"
msgid "No one" msgid "No one"
msgstr "Nessuno" msgstr ""
msgid "More options" msgid "More options"
msgstr "Più opzioni" msgstr ""
msgid "Add participant" msgid "Add participant"
msgstr "Aggiungi partecipante" msgstr "Aggiungi partecipante"
#, fuzzy
msgid "Edit this participant" msgid "Edit this participant"
msgstr "Modifica questo partecipante" msgstr "Aggiungi partecipante"
msgid "john.doe@example.com, mary.moe@site.com" msgid "john.doe@example.com, mary.moe@site.com"
msgstr "mario.rossi@example.com, maria.bianchi@site.com" msgstr "mario.rossi@example.com, maria.bianchi@site.com"
@ -532,11 +546,11 @@ msgstr "Impostazioni Cronologia Aggiornate"
#, python-format #, python-format
msgid "Bill %(name)s: %(property_name)s changed from %(before)s to %(after)s" msgid "Bill %(name)s: %(property_name)s changed from %(before)s to %(after)s"
msgstr "Spesa %(name)s: %(property_name)s cambiata da %(before)s a %(after)s" msgstr ""
#, python-format #, python-format
msgid "Bill %(name)s: %(property_name)s changed to %(after)s" msgid "Bill %(name)s: %(property_name)s changed to %(after)s"
msgstr "Spesa %(name)s: %(property_name)s cambiata in %(after)s" msgstr ""
msgid "Confirm Remove IP Adresses" msgid "Confirm Remove IP Adresses"
msgstr "Conferma Rimozione Indirizzi IP" msgstr "Conferma Rimozione Indirizzi IP"
@ -552,8 +566,9 @@ msgstr ""
" La parte residua dello storico del progetto non subirà " " La parte residua dello storico del progetto non subirà "
"modifiche. Questa azione non potrà essere annullata." "modifiche. Questa azione non potrà essere annullata."
#, fuzzy
msgid "Confirm deletion" msgid "Confirm deletion"
msgstr "Conferma la cancellazione" msgstr "Conferma Cancellazione"
msgid "Close" msgid "Close"
msgstr "Chiudi" msgstr "Chiudi"
@ -570,36 +585,37 @@ msgstr ""
#, python-format #, python-format
msgid "Bill %(name)s: added %(owers_list_str)s to owers list" msgid "Bill %(name)s: added %(owers_list_str)s to owers list"
msgstr "Spesa %(name)s: aggiunto %(owers_list_str)s alla lista dei proprietari" msgstr ""
#, python-format #, python-format
msgid "Bill %(name)s: removed %(owers_list_str)s from owers list" msgid "Bill %(name)s: removed %(owers_list_str)s from owers list"
msgstr "Spesa %(name)s: rimosso %(owers_list_str)s dalla lista dei proprietari" msgstr ""
msgid "This project has history disabled. New actions won't appear below." msgid "This project has history disabled. New actions won't appear below."
msgstr "" msgstr ""
"Questo progetto ha la cronologia disabilitata. Le nuove azioni non "
"appariranno in basso."
#, fuzzy
msgid "You can enable history on the settings page." msgid "You can enable history on the settings page."
msgstr "È possibile attivare la cronologia nella pagina delle impostazioni." msgstr ""
"La registrazione degli indirizzi IP può essere attivata nella pagina "
"delle impostazioni"
msgid "" msgid ""
"The table below reflects actions recorded prior to disabling project " "The table below reflects actions recorded prior to disabling project "
"history." "history."
msgstr "" msgstr ""
"La tabella seguente riflette le azioni registrate prima della "
"disabilitazione della cronologia del progetto."
#, fuzzy
msgid "You can clear the project history to remove them." msgid "You can clear the project history to remove them."
msgstr "Puoi cancellare la cronologia del progetto per rimuoverli." msgstr "Probabilmente qualcuno ha svuotato lo storico del progetto."
#, fuzzy
msgid "" msgid ""
"Some entries below contain IP addresses, even though this project has IP " "Some entries below contain IP addresses, even though this project has IP "
"recording disabled. " "recording disabled. "
msgstr "" msgstr ""
"Alcune voci qui sotto contengono degli indirizzi IP, anche se in questo " "Alcune voci qui sotto contengono indirizzi IP, anche se in questo "
"progetto la registrazione degli IP sia stata disabilitata. " "progetto la registrazione degli IP è disabilitata. "
msgid "Delete stored IP addresses" msgid "Delete stored IP addresses"
msgstr "Cancella indirizzi IP conservati" msgstr "Cancella indirizzi IP conservati"
@ -610,6 +626,7 @@ msgstr "Nessun indirizzo IP da cancellare"
msgid "Delete Stored IP Addresses" msgid "Delete Stored IP Addresses"
msgstr "Cancella Indirizzi IP Conservati" msgstr "Cancella Indirizzi IP Conservati"
#, fuzzy
msgid "No history to erase" msgid "No history to erase"
msgstr "Nessuna cronologia da cancellare" msgstr "Nessuna cronologia da cancellare"
@ -639,47 +656,47 @@ msgstr "Dall'IP"
msgid "Project %(name)s added" msgid "Project %(name)s added"
msgstr "Progetto %(name)s aggiunto" msgstr "Progetto %(name)s aggiunto"
#, python-format #, fuzzy, python-format
msgid "Bill %(name)s added" msgid "Bill %(name)s added"
msgstr "La spesa %(name)s è stata aggiunta" msgstr "L'addebito è stato aggiunto"
#, python-format #, python-format
msgid "Participant %(name)s added" msgid "Participant %(name)s added"
msgstr "Il partecipante %(name)s è stato aggiunto" msgstr ""
msgid "Project private code changed" msgid "Project private code changed"
msgstr "Codice privato del progetto modificato" msgstr "Codice privato del progetto modificato"
#, python-format #, fuzzy, python-format
msgid "Project renamed to %(new_project_name)s" msgid "Project renamed to %(new_project_name)s"
msgstr "Il progetto è stato rinominato in %(new_project_name)s" msgstr "L'identificatore del progetto è %(new_project_name)s"
#, python-format #, fuzzy, python-format
msgid "Project contact email changed to %(new_email)s" msgid "Project contact email changed to %(new_email)s"
msgstr "Contatto email del progetto modificato in %(new_email)s" msgstr "Contatto email del progetto modificato in"
msgid "Project settings modified" msgid "Project settings modified"
msgstr "Impostazione del progetto modificate" msgstr "Impostazione del progetto modificate"
#, python-format #, python-format
msgid "Participant %(name)s deactivated" msgid "Participant %(name)s deactivated"
msgstr "Il partecipante %(name)s è stato disattivato" msgstr ""
#, python-format #, python-format
msgid "Participant %(name)s reactivated" msgid "Participant %(name)s reactivated"
msgstr "Il partecipante %(name)s è stato riattivato" msgstr ""
#, python-format #, python-format
msgid "Participant %(name)s renamed to %(new_name)s" msgid "Participant %(name)s renamed to %(new_name)s"
msgstr "Il partecipante %(name)s è stato rinominato in %(new_name)s" msgstr ""
#, python-format #, python-format
msgid "Bill %(name)s renamed to %(new_description)s" msgid "Bill %(name)s renamed to %(new_description)s"
msgstr "La spesa %(name)s è stata rinominata in %(new_description)s" msgstr ""
#, python-format #, python-format
msgid "Participant %(name)s: weight changed from %(old_weight)s to %(new_weight)s" msgid "Participant %(name)s: weight changed from %(old_weight)s to %(new_weight)s"
msgstr "Partecipante %(name)s: peso cambiato da %(old_weight)s a %(new_weight)s" msgstr ""
msgid "Payer" msgid "Payer"
msgstr "Pagatore" msgstr "Pagatore"
@ -694,33 +711,33 @@ msgstr "Data"
msgid "Amount in %(currency)s" msgid "Amount in %(currency)s"
msgstr "Importo in %(currency)s" msgstr "Importo in %(currency)s"
#, python-format #, fuzzy, python-format
msgid "Bill %(name)s modified" msgid "Bill %(name)s modified"
msgstr "La spesa %(name)s è stata modificata" msgstr "L'addebito è stato aggiornato"
#, python-format #, python-format
msgid "Participant %(name)s modified" msgid "Participant %(name)s modified"
msgstr "Il partecipante %(name)s è stato modificato" msgstr ""
#, python-format #, fuzzy, python-format
msgid "Bill %(name)s removed" msgid "Bill %(name)s removed"
msgstr "La spesa %(name)s è stata rimossa" msgstr "L'utente '%(name)s' è stato rimosso"
#, python-format #, fuzzy, python-format
msgid "Participant %(name)s removed" msgid "Participant %(name)s removed"
msgstr "Il partecipante '%(name)s' è stato rimosso" msgstr "L'utente '%(name)s' è stato rimosso"
#, python-format #, fuzzy, python-format
msgid "Project %(name)s changed in an unknown way" msgid "Project %(name)s changed in an unknown way"
msgstr "Il progetto %(name)s è stato modificato in modo sconosciuto" msgstr "modificato in modo sconosciuto"
#, python-format #, fuzzy, python-format
msgid "Bill %(name)s changed in an unknown way" msgid "Bill %(name)s changed in an unknown way"
msgstr "La spesa %(name)s è stata modificata in modo sconosciuto" msgstr "modificato in modo sconosciuto"
#, python-format #, fuzzy, python-format
msgid "Participant %(name)s changed in an unknown way" msgid "Participant %(name)s changed in an unknown way"
msgstr "Il partecipante %(name)s è stato modificato in modo sconosciuto" msgstr "modificato in modo sconosciuto"
msgid "Nothing to list" msgid "Nothing to list"
msgstr "Niente da elencare" msgstr "Niente da elencare"
@ -792,9 +809,6 @@ msgstr "Cronologia"
msgid "Settings" msgid "Settings"
msgstr "Impostazioni" msgstr "Impostazioni"
msgid "RSS Feed"
msgstr "Feed RSS"
msgid "Other projects :" msgid "Other projects :"
msgstr "Altri progetti:" msgstr "Altri progetti:"
@ -806,7 +820,7 @@ msgstr "Cruscotto"
#, python-format #, python-format
msgid "Please retry after %(date)s." msgid "Please retry after %(date)s."
msgstr "Per favore, riprova dopo %(date)s." msgstr ""
msgid "Code" msgid "Code"
msgstr "Codice" msgstr "Codice"
@ -817,11 +831,12 @@ msgstr "Applicazione mobile"
msgid "Documentation" msgid "Documentation"
msgstr "Documentazione" msgstr "Documentazione"
msgid "Administration Dashboard" msgid "Administation Dashboard"
msgstr "Cruscotto Amministrazione" msgstr "Cruscotto Amministrazione"
#, fuzzy
msgid "Legal information" msgid "Legal information"
msgstr "Informazioni legali" msgstr "Cancella Conferma"
msgid "\"I hate money\" is free software" msgid "\"I hate money\" is free software"
msgstr "\"I hate money\" è un software libero" msgstr "\"I hate money\" è un software libero"
@ -871,11 +886,14 @@ msgstr "Nessun addebito"
msgid "Nothing to list yet." msgid "Nothing to list yet."
msgstr "Ancora niente da mostrare." msgstr "Ancora niente da mostrare."
msgid "Add your first bill" msgid "You probably want to"
msgstr "Aggiungi la tua prima spesa" msgstr "Probabilmente vuoi"
msgid "Add the first participant" msgid "add a bill"
msgstr "Aggiungi il primo partecipante" msgstr "aggiungi una spesa"
msgid "add participants"
msgstr "aggiunti partecipanti"
msgid "Password reminder" msgid "Password reminder"
msgstr "Promemoria password" msgstr "Promemoria password"
@ -899,62 +917,46 @@ msgstr "Reset della tua password"
msgid "Invite people to join this project" msgid "Invite people to join this project"
msgstr "Invita persone a collaborare a questo progetto" msgstr "Invita persone a collaborare a questo progetto"
msgid "Share an invitation link"
msgstr "Condividi un link d'invito"
msgid ""
"The easiest way to invite people is to give them the following invitation"
" link.<br />They will be able to access the project, manage participants,"
" add/edit/delete bills. However, they will not have access to important "
"settings such as changing the private code or deleting the whole project."
msgstr ""
"Il modo più semplice per invitare le persone è fornire loro il seguente link "
"di invito.<br />Potranno accedere al progetto, gestire i partecipanti, "
"aggiungere/modificare/cancellare spese. Tuttavia, non avranno accesso a "
"impostazioni importanti come la modifica del codice privato o la "
"cancellazione dell'intero progetto."
msgid "Scan QR code"
msgstr "Scansiona codice QR"
msgid "Use a mobile device with a compatible app."
msgstr "Usa un dispositivo mobile con una app compatibile."
msgid "Send via Emails"
msgstr "Inviare via Email"
msgid ""
"Specify a list of email adresses (separated by comma) of people you want "
"to notify about the creation of this project. We will send them an email "
"with the invitation link."
msgstr ""
"Specifica un elenco di indirizzi email (separati da virgola) a cui vuoi "
"notificare la creazione di questo progetto. Manderemo loro un messaggio con "
"il link d'invito."
msgid "Share Identifier & code" msgid "Share Identifier & code"
msgstr "Condividi Identificatore & codice" msgstr "Condividi Identificatore & codice"
msgid "" msgid ""
"You can share the project identifier and the private code by any " "You can share the project identifier and the private code by any "
"communication means.<br />Anyone with the private code will have access " "communication means."
"to the full project, including changing settings such as the private code"
" or project email address, or even deleting the whole project."
msgstr "" msgstr ""
"È possibile condividere l'identificativo del progetto e il codice privato " "È possibile condividere l'identificativo del progetto e il codice privato"
"con qualsiasi mezzo di comunicazione.<br />Chiunque abbia il codice privato " " con qualsiasi mezzo di comunicazione."
"avrà accesso all'intero progetto, compresa la modifica di impostazioni quali "
"il codice privato o l'indirizzo e-mail del progetto, o addirittura la "
"cancellazione dell'intero progetto."
msgid "Identifier:" msgid "Identifier:"
msgstr "Identificatore:" msgstr "Identificatore:"
msgid "Private code:" msgid "Share the Link"
msgstr "Codice privato:" msgstr "Condividi il Link"
msgid "the private code was defined when you created the project" msgid "You can directly share the following link via your prefered medium"
msgstr "il codice privato è stato definito quando hai creato il progetto" msgstr ""
"Puoi condividere direttamente il seguente link attraverso il tuo canale "
"preferito"
msgid "Scan QR code"
msgstr ""
msgid "Use a mobile device with a compatible app."
msgstr ""
msgid "Send via Emails"
msgstr "Inviare via Email"
msgid ""
"Specify a (comma separated) list of email adresses you want to notify "
"about the\n"
" creation of this budget management project and we will "
"send them an email for you."
msgstr ""
"Specifica un elenco di indirizzi email (separati da virgola) a cui vuoi "
"notificare la\n"
" creazione di questo progetto di gestione budget e "
"manderemo a ciascuno di loro un messaggio per te."
msgid "Who pays?" msgid "Who pays?"
msgstr "Chi paga?" msgstr "Chi paga?"
@ -1144,25 +1146,3 @@ msgstr "Periodo"
#~ msgid "Edit the project" #~ msgid "Edit the project"
#~ msgstr "Modifica il progetto" #~ msgstr "Modifica il progetto"
#~ msgid "You probably want to"
#~ msgstr "Probabilmente vuoi"
#~ msgid "add participants"
#~ msgstr "aggiunti partecipanti"
#~ msgid ""
#~ "You can share the project identifier "
#~ "and the private code by any "
#~ "communication means."
#~ msgstr ""
#~ "È possibile condividere l'identificativo del"
#~ " progetto e il codice privato con "
#~ "qualsiasi mezzo di comunicazione."
#~ msgid "Share the Link"
#~ msgstr "Condividi il Link"
#~ msgid "You can directly share the following link via your prefered medium"
#~ msgstr ""
#~ "Puoi condividere direttamente il seguente "
#~ "link attraverso il tuo canale preferito"

Binary file not shown.

View file

@ -1,18 +1,18 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-07-29 14:24+0200\n" "POT-Creation-Date: 2023-07-14 10:01+0200\n"
"PO-Revision-Date: 2024-06-24 15:09+0000\n" "PO-Revision-Date: 2020-11-11 16:28+0000\n"
"Last-Translator: Khang Tran <tranchikhang@outlook.com>\n" "Last-Translator: Jwen921 <yangjingwen0921@gmail.com>\n"
"Language-Team: Japanese <https://hosted.weblate.org/projects/i-hate-money/"
"i-hate-money/ja/>\n"
"Language: ja\n" "Language: ja\n"
"Language-Team: Japanese <https://hosted.weblate.org/projects/i-hate-"
"money/i-hate-money/ja/>\n"
"Plural-Forms: nplurals=1; plural=0\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n" "Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Weblate 5.6-rc\n"
"Generated-By: Babel 2.9.0\n" "Generated-By: Babel 2.9.0\n"
#, python-format #, python-format
@ -27,17 +27,12 @@ msgstr "無効な入力です。数字と「+ - * / 」の演算子しか入力
msgid "Project name" msgid "Project name"
msgstr "プロジェクトの名前" msgstr "プロジェクトの名前"
msgid "Current private code" #, fuzzy
msgstr "現在の暗証コード"
msgid "Enter existing private code to edit project"
msgstr "プロジェクトを編集するために、暗証コードを入力してください"
msgid "New private code" msgid "New private code"
msgstr "暗証コード" msgstr "暗証コード"
msgid "Enter a new code if you want to change it" msgid "Enter a new code if you want to change it"
msgstr "変更するために、新しい暗証コードを入力してください" msgstr ""
msgid "Email" msgid "Email"
msgstr "メールアドレス" msgstr "メールアドレス"
@ -52,13 +47,7 @@ msgid "Default Currency"
msgstr "初期設定にする通貨" msgstr "初期設定にする通貨"
msgid "Setting a default currency enables currency conversion between bills" msgid "Setting a default currency enables currency conversion between bills"
msgstr "明細通貨変換のため、デフォルトの通貨を設定してください" msgstr ""
msgid "Unknown error"
msgstr "不明エラー"
msgid "Invalid private code."
msgstr "無効な暗証コード。"
msgid "" msgid ""
"This project cannot be set to 'no currency' because it contains bills in " "This project cannot be set to 'no currency' because it contains bills in "
@ -96,6 +85,14 @@ msgstr ""
msgid "Enter private code to confirm deletion" msgid "Enter private code to confirm deletion"
msgstr "" msgstr ""
#, fuzzy
msgid "Unknown error"
msgstr "未知のプロジェクト"
#, fuzzy
msgid "Invalid private code."
msgstr "暗証コード"
msgid "Get in" msgid "Get in"
msgstr "入る" msgstr "入る"
@ -175,7 +172,7 @@ msgid "This project already have this participant"
msgstr "プロジェクトはすでにこのメンバーを含めています" msgstr "プロジェクトはすでにこのメンバーを含めています"
msgid "People to notify" msgid "People to notify"
msgstr "通知したい人" msgstr ""
msgid "Send the invitations" msgid "Send the invitations"
msgstr "招待状を送る" msgstr "招待状を送る"
@ -190,12 +187,11 @@ msgstr "ログアウト"
msgid "Please check the email configuration of the server." msgid "Please check the email configuration of the server."
msgstr "" msgstr ""
#, python-format #, fuzzy, python-format
msgid "" msgid ""
"Please check the email configuration of the server or contact the " "Please check the email configuration of the server or contact the "
"administrator: %(admin_email)s" "administrator: %(admin_email)s"
msgstr "申し訳ございませんが、エラーが発生しました。メールアドレスを再度チェックする" msgstr "申し訳ございませんが、招待メールを送ったとき、エラーが発生しました。メールアドレスを再度チェックするかまたは管理者に連絡ください。"
"か、または管理者( %(admin_email)sに連絡ください"
#. List with two items only #. List with two items only
msgid "{dual_object_0} and {dual_object_1}" msgid "{dual_object_0} and {dual_object_1}"
@ -264,11 +260,8 @@ msgstr "未知のプロジェクト"
msgid "Password successfully reset." msgid "Password successfully reset."
msgstr "パスワードを再設定できました。" msgstr "パスワードを再設定できました。"
msgid "Project settings have been changed successfully."
msgstr ""
msgid "Unable to parse CSV" msgid "Unable to parse CSV"
msgstr "CSVを読み込むことができません" msgstr ""
#, python-format #, python-format
msgid "Missing attribute: %(attribute)s" msgid "Missing attribute: %(attribute)s"
@ -289,7 +282,7 @@ msgid "Error deleting project"
msgstr "" msgstr ""
msgid "Unable to logout" msgid "Unable to logout"
msgstr "ログアウトできません" msgstr ""
#, python-format #, python-format
msgid "You have been invited to share your expenses for %(project)s" msgid "You have been invited to share your expenses for %(project)s"
@ -350,8 +343,9 @@ msgstr ""
msgid "Error deleting project history" msgid "Error deleting project history"
msgstr "プロジェクトの歴史を有効にする" msgstr "プロジェクトの歴史を有効にする"
#, fuzzy
msgid "Deleted project history." msgid "Deleted project history."
msgstr "プロジェクトの歴史を削除しました。" msgstr "プロジェクトの歴史を有効にする"
#, fuzzy #, fuzzy
msgid "Error deleting recorded IP addresses" msgid "Error deleting recorded IP addresses"
@ -570,8 +564,9 @@ msgstr ""
msgid "This project has history disabled. New actions won't appear below." msgid "This project has history disabled. New actions won't appear below."
msgstr "" msgstr ""
#, fuzzy
msgid "You can enable history on the settings page." msgid "You can enable history on the settings page."
msgstr "設定ページでIPアドレス記録を編集可能にすることができる" msgstr "設定ページでIPアドレス記録を編集可能にすることができる"
msgid "" msgid ""
"The table below reflects actions recorded prior to disabling project " "The table below reflects actions recorded prior to disabling project "
@ -617,13 +612,13 @@ msgstr "設定ページでIPアドレス記録を編集不可にすることが
msgid "From IP" msgid "From IP"
msgstr "IPから" msgstr "IPから"
#, python-format #, fuzzy, python-format
msgid "Project %(name)s added" msgid "Project %(name)s added"
msgstr "プロジェクト%(name)sが作成されました" msgstr "プロジェクトの名前"
#, python-format #, fuzzy, python-format
msgid "Bill %(name)s added" msgid "Bill %(name)s added"
msgstr "%(name)s明細が追加されました" msgstr "明細が追加されました"
#, python-format #, python-format
msgid "Participant %(name)s added" msgid "Participant %(name)s added"
@ -636,9 +631,9 @@ msgstr "プロジェクトの私用コードが変更された"
msgid "Project renamed to %(new_project_name)s" msgid "Project renamed to %(new_project_name)s"
msgstr "プロジェクト名は%(new_project_name)s" msgstr "プロジェクト名は%(new_project_name)s"
#, python-format #, fuzzy, python-format
msgid "Project contact email changed to %(new_email)s" msgid "Project contact email changed to %(new_email)s"
msgstr "プロジェクトの連絡メールが%(new_email)sに変更されました" msgstr "プロジェクトの連絡メールが…に変更された"
msgid "Project settings modified" msgid "Project settings modified"
msgstr "プロジェクトの設定が修正された" msgstr "プロジェクトの設定が修正された"
@ -676,9 +671,9 @@ msgstr "日付"
msgid "Amount in %(currency)s" msgid "Amount in %(currency)s"
msgstr "%(currency)sでの金額" msgstr "%(currency)sでの金額"
#, python-format #, fuzzy, python-format
msgid "Bill %(name)s modified" msgid "Bill %(name)s modified"
msgstr "%(name)s明細が変更されました" msgstr "明細が変更されました"
#, python-format #, python-format
msgid "Participant %(name)s modified" msgid "Participant %(name)s modified"
@ -692,17 +687,17 @@ msgstr "ユーザー%(name)sが既に取り除かれました"
msgid "Participant %(name)s removed" msgid "Participant %(name)s removed"
msgstr "ユーザー%(name)sが既に取り除かれました" msgstr "ユーザー%(name)sが既に取り除かれました"
#, python-format #, fuzzy, python-format
msgid "Project %(name)s changed in an unknown way" msgid "Project %(name)s changed in an unknown way"
msgstr "プロジェクト%(name)sが不明な方法で変更されました" msgstr "未知の方法で変更された"
#, python-format #, fuzzy, python-format
msgid "Bill %(name)s changed in an unknown way" msgid "Bill %(name)s changed in an unknown way"
msgstr "明細%(name)sが不明な方法で変更されました" msgstr "未知の方法で変更された"
#, python-format #, fuzzy, python-format
msgid "Participant %(name)s changed in an unknown way" msgid "Participant %(name)s changed in an unknown way"
msgstr "参加者%(name)sが不明な方法で変更されました" msgstr "未知の方法で変更された"
msgid "Nothing to list" msgid "Nothing to list"
msgstr "表示できるものがない" msgstr "表示できるものがない"
@ -772,9 +767,6 @@ msgstr "歴史"
msgid "Settings" msgid "Settings"
msgstr "設定" msgstr "設定"
msgid "RSS Feed"
msgstr ""
msgid "Other projects :" msgid "Other projects :"
msgstr "他のプロジェクト:" msgstr "他のプロジェクト:"
@ -797,7 +789,7 @@ msgstr "携帯アプリ"
msgid "Documentation" msgid "Documentation"
msgstr "書類" msgstr "書類"
msgid "Administration Dashboard" msgid "Administation Dashboard"
msgstr "管理ダッシュボード" msgstr "管理ダッシュボード"
#, fuzzy #, fuzzy
@ -852,12 +844,13 @@ msgstr "明細なし"
msgid "Nothing to list yet." msgid "Nothing to list yet."
msgstr "表示できるものはありません。" msgstr "表示できるものはありません。"
#, fuzzy msgid "You probably want to"
msgid "Add your first bill" msgstr "…したいかもしれない"
msgid "add a bill"
msgstr "明細を追加する" msgstr "明細を追加する"
#, fuzzy msgid "add participants"
msgid "Add the first participant"
msgstr "参加者を追加する" msgstr "参加者を追加する"
msgid "Password reminder" msgid "Password reminder"
@ -880,15 +873,22 @@ msgstr "パスワードを再設定する"
msgid "Invite people to join this project" msgid "Invite people to join this project"
msgstr "他人をこのプロジェクトに招待する" msgstr "他人をこのプロジェクトに招待する"
msgid "Share an invitation link" msgid "Share Identifier & code"
msgstr "" msgstr "名前とコードを共有する"
msgid "" msgid ""
"The easiest way to invite people is to give them the following invitation" "You can share the project identifier and the private code by any "
" link.<br />They will be able to access the project, manage participants," "communication means."
" add/edit/delete bills. However, they will not have access to important " msgstr "プロジェクト名と私用コードは何の方法でも共有できます。"
"settings such as changing the private code or deleting the whole project."
msgstr "" msgid "Identifier:"
msgstr "名前:"
msgid "Share the Link"
msgstr "リンクを共有する"
msgid "You can directly share the following link via your prefered medium"
msgstr "好きの手段で以下のリンクを直接に共有できる"
msgid "Scan QR code" msgid "Scan QR code"
msgstr "" msgstr ""
@ -900,30 +900,13 @@ msgid "Send via Emails"
msgstr "メールで送る" msgstr "メールで送る"
msgid "" msgid ""
"Specify a list of email adresses (separated by comma) of people you want " "Specify a (comma separated) list of email adresses you want to notify "
"to notify about the creation of this project. We will send them an email " "about the\n"
"with the invitation link." " creation of this budget management project and we will "
msgstr "知らせたいメールアドレスのリスト(カンマ区切り)を指定してください。メールで" "send them an email for you."
"招待リンクを送信します。"
msgid "Share Identifier & code"
msgstr "名前とコードを共有する"
msgid ""
"You can share the project identifier and the private code by any "
"communication means.<br />Anyone with the private code will have access "
"to the full project, including changing settings such as the private code"
" or project email address, or even deleting the whole project."
msgstr ""
msgid "Identifier:"
msgstr "名前:"
msgid "Private code:"
msgstr "暗証コード:"
msgid "the private code was defined when you created the project"
msgstr "" msgstr ""
"…を知らせたいメールアドレスのリストを特定する(カンマ区切り)\n"
"彼らにこの予算管理プロジェクトの作成をメールでお知らせします。"
msgid "Who pays?" msgid "Who pays?"
msgstr "誰が支払った?" msgstr "誰が支払った?"
@ -1084,20 +1067,3 @@ msgstr "期間"
#~ msgid "Edit the project" #~ msgid "Edit the project"
#~ msgstr "プロジェクトを編集する" #~ msgstr "プロジェクトを編集する"
#~ msgid "You probably want to"
#~ msgstr "…したいかもしれない"
#~ msgid "add participants"
#~ msgstr "参加者を追加する"
#~ msgid ""
#~ "You can share the project identifier "
#~ "and the private code by any "
#~ "communication means."
#~ msgstr "プロジェクト名と私用コードは何の方法でも共有できます。"
#~ msgid "Share the Link"
#~ msgstr "リンクを共有する"
#~ msgid "You can directly share the following link via your prefered medium"
#~ msgstr "好きの手段で以下のリンクを直接に共有できる"

Binary file not shown.

View file

@ -1,18 +1,18 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-07-29 14:24+0200\n" "POT-Creation-Date: 2023-07-14 10:01+0200\n"
"PO-Revision-Date: 2023-09-16 10:59+0000\n" "PO-Revision-Date: 2021-10-17 04:56+0000\n"
"Last-Translator: Baptiste <weblate@bitsofnetworks.org>\n" "Last-Translator: a-g-rao <athrigrao@gmail.com>\n"
"Language-Team: Kannada <https://hosted.weblate.org/projects/i-hate-money/"
"i-hate-money/kn/>\n"
"Language: kn\n" "Language: kn\n"
"Language-Team: Kannada <https://hosted.weblate.org/projects/i-hate-"
"money/i-hate-money/kn/>\n"
"Plural-Forms: nplurals=2; plural=n > 1\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n" "Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n > 1;\n"
"X-Generator: Weblate 5.0.2\n"
"Generated-By: Babel 2.9.0\n" "Generated-By: Babel 2.9.0\n"
#, python-format #, python-format
@ -31,17 +31,11 @@ msgstr ""
msgid "Project name" msgid "Project name"
msgstr "ಯೋಜನೆಯ ಹೆಸರು" msgstr "ಯೋಜನೆಯ ಹೆಸರು"
msgid "Current private code"
msgstr "ಪ್ರಸ್ತುತ ಸಂಕೇತಪದ"
msgid "Enter existing private code to edit project"
msgstr "ಯೋಜನೆಯನ್ನು ಪರಿಷ್ಕರಿಸಲು ಚಾಲ್ತಿಯಲ್ಲಿರುವ ಸಂಕೇತಪದವನ್ನು ನಮೂದಿಸಿ"
msgid "New private code" msgid "New private code"
msgstr "ಹೊಸ ಸಂಕೇತಪದ" msgstr "ಹೊಸ ಸಂಕೇತಪದ"
msgid "Enter a new code if you want to change it" msgid "Enter a new code if you want to change it"
msgstr "ಸಂಕೇತಪದ ಬದಲಾಯಿಸಬೇಕೆಂದರೆ ನಮೂದಿಸಿ" msgstr "ಸಂಕೇತಪದ ಬದಲಾಯಿಸಬೇಕೆಂದರೆ ನಮೂದಿಸಿ."
msgid "Email" msgid "Email"
msgstr "ಮಿನ್ನಂಚೆ" msgstr "ಮಿನ್ನಂಚೆ"
@ -53,31 +47,21 @@ msgid "Use IP tracking for project history"
msgstr "" msgstr ""
msgid "Default Currency" msgid "Default Currency"
msgstr "ಪುರ್ವನಿಯೋಜಿತ ಕರನ್ಸಿ" msgstr ""
msgid "Setting a default currency enables currency conversion between bills" msgid "Setting a default currency enables currency conversion between bills"
msgstr "" msgstr ""
"ಪುರ್ವನಿಯೋಜಿತ ಕರನ್ಸಿಯನ್ನು ನಮೋದಿಸುವುದರಿಂದ ರಶೀದಿಗಳ ನಡುವೆ ಕರನ್ಸಿ ಪರಿವರ್ತನೆಯನ್ನು "
"ಸಕ್ರಿಯಗೊಳಿಸುತ್ತದೆ"
msgid "Unknown error"
msgstr "ಅಜ್ಞಾತ ದೋಷ"
msgid "Invalid private code."
msgstr "ನಮೂದಿಸಿರುವ ಸಂಕೇತಪದ ಅಮಾನ್ಯವಾಗಿದೆ."
msgid "" msgid ""
"This project cannot be set to 'no currency' because it contains bills in " "This project cannot be set to 'no currency' because it contains bills in "
"multiple currencies." "multiple currencies."
msgstr "" msgstr ""
"ಅನೇಕ ಕರೆನ್ಸಿಯ ರಶೀದಿ ಇರುವುದರಿಂದ ಈ ಯೋಜನೆಯನ್ನು \"ಯಾವುದೆ ಕರೆನ್ಸಿ ಇಲ್ಲ\" ಯಂದು "
"ನಮೋದಿಸಲು ಆಗುವುದಿಲ್ಲ."
msgid "Compatible with Cospend" msgid "Compatible with Cospend"
msgstr "ಖರ್ಚನ್ನು ಜೊತೆಗಾರನೊಂದಿಗೆ ಹಂಚಿಕೊಳ್ಳಬಹುದು" msgstr ""
msgid "Project identifier" msgid "Project identifier"
msgstr "ಯೋಜನೆ ಸಂಕೇತ" msgstr ""
msgid "Private code" msgid "Private code"
msgstr "ಸಂಕೇತಪದ" msgstr "ಸಂಕೇತಪದ"
@ -90,21 +74,25 @@ msgid ""
"A project with this identifier (\"%(project)s\") already exists. Please " "A project with this identifier (\"%(project)s\") already exists. Please "
"choose a new identifier" "choose a new identifier"
msgstr "" msgstr ""
"ಈ ಸಂಕೇತದ (\"%(project)s\") ಇನೊಂದು ಯೋಜನೆ ಆಗಲೆ ಅಸ್ತಿತ್ವದಲ್ಲಿದೆ. ದಯವಿಟ್ಟು ಹೊಸ "
"ಸಂಕೇತವನ್ನು ಆರಿಸಿ"
msgid "Which is a real currency: Euro or Petro dollar?" msgid "Which is a real currency: Euro or Petro dollar?"
msgstr "ನಿಜವಾದ ಕರೆನ್ಸಿ ಯಾವುದು: ಯುರೋ ಅಥವಾ ಪೆಟ್ರೋ ಡಾಲರ್?" msgstr ""
msgid "euro" msgid "euro"
msgstr "ಯೂರೋ" msgstr "ಯೂರೋ"
msgid "Please, validate the captcha to proceed." msgid "Please, validate the captcha to proceed."
msgstr "ಮುಂದುವರೆಯಲು ದಯವಿಟ್ಟು, ಕ್ಯಾಪ್ಚಾವನ್ನು ಮೌಲ್ಯೀಕರಿಸಿ." msgstr ""
msgid "Enter private code to confirm deletion" msgid "Enter private code to confirm deletion"
msgstr "ತೆಗೆದುಹಾಕಲು ನಿಮ್ಮ ಸಂಕೇತಪದವನ್ನು ನಮೂದಿಸಿ" msgstr "ತೆಗೆದುಹಾಕಲು ನಿಮ್ಮ ಸಂಕೇತಪದವನ್ನು ನಮೂದಿಸಿ"
msgid "Unknown error"
msgstr "ಅಜ್ಞಾತ ದೋಷ"
msgid "Invalid private code."
msgstr "ನಮೂದಿಸಿರುವ ಸಂಕೇತಪದ ಅಮಾನ್ಯವಾಗಿದೆ."
msgid "Get in" msgid "Get in"
msgstr "ಒಳಹೊಗು" msgstr "ಒಳಹೊಗು"
@ -130,26 +118,25 @@ msgid "Reset password"
msgstr "ಪ್ರವೇಶಪದ ಮರುಹೊಂದಿಸಿ" msgstr "ಪ್ರವೇಶಪದ ಮರುಹೊಂದಿಸಿ"
msgid "When?" msgid "When?"
msgstr "ಯಾವಾಗ?" msgstr ""
msgid "What?" msgid "What?"
msgstr "ಏನು?" msgstr "ಏನು?"
msgid "Who paid?" msgid "Who paid?"
msgstr "ಯಾರು ಪಾವತಿಸಿದ್ದಾರೆ?" msgstr ""
msgid "How much?" msgid "How much?"
msgstr "ಎಷ್ಟು?" msgstr ""
msgid "Currency" msgid "Currency"
msgstr "ನಾಣ್ಯಪದ್ಧತಿ" msgstr "ನಾಣ್ಯಪದ್ಧತಿ"
#, fuzzy
msgid "External link" msgid "External link"
msgstr "ಬಾಹ್ಯ ಲಿಂಕ್" msgstr ""
msgid "A link to an external document, related to this bill" msgid "A link to an external document, related to this bill"
msgstr "ಈ ರಶೀದಿಗೆ ಸಂಬಂಧಿಸಿದ ಬಾಹ್ಯ ಕಡತದ ಲಿಂಕ್" msgstr ""
msgid "For whom?" msgid "For whom?"
msgstr "ಯಾರಿಂದ?" msgstr "ಯಾರಿಂದ?"
@ -162,13 +149,13 @@ msgstr "ಕೋರಿಕೆ ಸಲ್ಲಿಸಿ ಹೊಸದನ್ನುಸೇ
#, python-format #, python-format
msgid "Project default: %(currency)s" msgid "Project default: %(currency)s"
msgstr "ಯೋಜನೆಯ ಪೂರ್ವನಿಯೋಜಿತ ಕರೆನ್ಸಿ: %(currency)s" msgstr ""
msgid "Name" msgid "Name"
msgstr "ಹೆಸರು" msgstr "ಹೆಸರು"
msgid "Weights should be positive" msgid "Weights should be positive"
msgstr "ತೂಕವು ಧನಾತ್ಮಕವಾಗಿರಬೇಕು" msgstr ""
msgid "Weight" msgid "Weight"
msgstr "ತೂಕ" msgstr "ತೂಕ"
@ -177,33 +164,33 @@ msgid "Add"
msgstr "ಕೂಡು" msgstr "ಕೂಡು"
msgid "The participant name is invalid" msgid "The participant name is invalid"
msgstr "ಸದಸ್ಯರ ಹೆಸರು ಅಮಾನ್ಯವಾಗಿದೆ" msgstr ""
#, fuzzy
msgid "This project already have this participant" msgid "This project already have this participant"
msgstr "ಈ ಸದಸ್ಯರು ಈಗಾಗಲೆ ಈ ಯೋಜನೆಯ ಸದಸ್ಯರಾಗಿದ್ದಾರೆ" msgstr "ಈ ಸದಸ್ಯರು ಈಗಾಗಲೆ ಈ ಯೋಜನೆಯ ಸದಸ್ಯರಾಗಿದ್ದಾರೆ"
msgid "People to notify" msgid "People to notify"
msgstr "ಸೂಚಿಸಬೇಕಾದ ಜನರು" msgstr ""
msgid "Send the invitations" msgid "Send the invitations"
msgstr "ಆಮಂತ್ರಣಗಳನ್ನು ಕಳುಹಿಸಿ" msgstr ""
#, python-format #, python-format
msgid "The email %(email)s is not valid" msgid "The email %(email)s is not valid"
msgstr "ಮಿನ್ನಂಚೆ %(email)s ಸಮಂಜಸವಾಗಿಲ್ಲ" msgstr "ಮಿನ್ನಂಚೆ %(email)s ಸಮಂಜಸವಾಗಿಲ್ಲ"
msgid "Logout" msgid "Logout"
msgstr "ನಿರ್ಗಮಿಸಿ" msgstr ""
msgid "Please check the email configuration of the server." msgid "Please check the email configuration of the server."
msgstr "ದಯವಿಟ್ಟು, ಇಮೇಲ್ ಸಂರಚನೆ ಪರಿಶೀಲಿಸಿ." msgstr ""
#, python-format #, python-format
msgid "" msgid ""
"Please check the email configuration of the server or contact the " "Please check the email configuration of the server or contact the "
"administrator: %(admin_email)s" "administrator: %(admin_email)s"
msgstr "" msgstr ""
"ದಯವಿಟ್ಟು, ಇಮೇಲ್ ಸಂರಚನೆ ಪರಿಶೀಲಿಸಿ ಅಥವಾ ನಿರ್ವಾಹಕನನ್ನು ಸಂಪರ್ಕಿಸಿ:%(admin_email)s"
#. List with two items only #. List with two items only
msgid "{dual_object_0} and {dual_object_1}" msgid "{dual_object_0} and {dual_object_1}"
@ -222,7 +209,7 @@ msgid "{start_object}, {next_object}"
msgstr "{start_object}, {next_object}" msgstr "{start_object}, {next_object}"
msgid "No Currency" msgid "No Currency"
msgstr "ಯಾವುದೆ ಕರೆನ್ಸಿ ಇಲ್ಲ" msgstr ""
#. Form error with only one error #. Form error with only one error
msgid "{prefix}: {error}" msgid "{prefix}: {error}"
@ -272,9 +259,6 @@ msgstr "ಗೊತ್ತಿಲ್ಲದ ಯೋಜನೆ"
msgid "Password successfully reset." msgid "Password successfully reset."
msgstr "" msgstr ""
msgid "Project settings have been changed successfully."
msgstr ""
msgid "Unable to parse CSV" msgid "Unable to parse CSV"
msgstr "" msgstr ""
@ -768,9 +752,6 @@ msgstr ""
msgid "Settings" msgid "Settings"
msgstr "" msgstr ""
msgid "RSS Feed"
msgstr ""
msgid "Other projects :" msgid "Other projects :"
msgstr "" msgstr ""
@ -793,7 +774,7 @@ msgstr ""
msgid "Documentation" msgid "Documentation"
msgstr "" msgstr ""
msgid "Administration Dashboard" msgid "Administation Dashboard"
msgstr "" msgstr ""
msgid "Legal information" msgid "Legal information"
@ -847,10 +828,13 @@ msgstr ""
msgid "Nothing to list yet." msgid "Nothing to list yet."
msgstr "" msgstr ""
msgid "Add your first bill" msgid "You probably want to"
msgstr "" msgstr ""
msgid "Add the first participant" msgid "add a bill"
msgstr ""
msgid "add participants"
msgstr "" msgstr ""
msgid "Password reminder" msgid "Password reminder"
@ -873,14 +857,21 @@ msgstr ""
msgid "Invite people to join this project" msgid "Invite people to join this project"
msgstr "" msgstr ""
msgid "Share an invitation link" msgid "Share Identifier & code"
msgstr "" msgstr ""
msgid "" msgid ""
"The easiest way to invite people is to give them the following invitation" "You can share the project identifier and the private code by any "
" link.<br />They will be able to access the project, manage participants," "communication means."
" add/edit/delete bills. However, they will not have access to important " msgstr ""
"settings such as changing the private code or deleting the whole project."
msgid "Identifier:"
msgstr ""
msgid "Share the Link"
msgstr ""
msgid "You can directly share the following link via your prefered medium"
msgstr "" msgstr ""
msgid "Scan QR code" msgid "Scan QR code"
@ -893,28 +884,10 @@ msgid "Send via Emails"
msgstr "" msgstr ""
msgid "" msgid ""
"Specify a list of email adresses (separated by comma) of people you want " "Specify a (comma separated) list of email adresses you want to notify "
"to notify about the creation of this project. We will send them an email " "about the\n"
"with the invitation link." " creation of this budget management project and we will "
msgstr "" "send them an email for you."
msgid "Share Identifier & code"
msgstr ""
msgid ""
"You can share the project identifier and the private code by any "
"communication means.<br />Anyone with the private code will have access "
"to the full project, including changing settings such as the private code"
" or project email address, or even deleting the whole project."
msgstr ""
msgid "Identifier:"
msgstr ""
msgid "Private code:"
msgstr "ಸಂಕೇತಪದ:"
msgid "the private code was defined when you created the project"
msgstr "" msgstr ""
msgid "Who pays?" msgid "Who pays?"
@ -1054,42 +1027,3 @@ msgstr ""
#~ msgid "Edit the project" #~ msgid "Edit the project"
#~ msgstr "" #~ msgstr ""
#~ msgid "You probably want to"
#~ msgstr ""
#~ msgid "add a bill"
#~ msgstr ""
#~ msgid "add participants"
#~ msgstr ""
#~ msgid ""
#~ "You can share the project identifier "
#~ "and the private code by any "
#~ "communication means."
#~ msgstr ""
#~ msgid "Share the Link"
#~ msgstr ""
#~ msgid "You can directly share the following link via your prefered medium"
#~ msgstr ""
#~ msgid ""
#~ "Specify a (comma separated) list of "
#~ "email adresses you want to notify "
#~ "about the\n"
#~ " creation of this budget "
#~ "management project and we will send "
#~ "them an email for you."
#~ msgstr ""
#~ msgid ""
#~ "Specify a (comma separated) list of "
#~ "email adresses you want to notify "
#~ "about the\n"
#~ " creation of this budget "
#~ "management project and we will send "
#~ "them an email with the invitation "
#~ "link."
#~ msgstr ""

Binary file not shown.

View file

@ -3,7 +3,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-07-29 14:24+0200\n" "POT-Creation-Date: 2023-07-14 10:01+0200\n"
"PO-Revision-Date: 2021-07-18 12:32+0000\n" "PO-Revision-Date: 2021-07-18 12:32+0000\n"
"Last-Translator: Kemystra <izzmin97@gmail.com>\n" "Last-Translator: Kemystra <izzmin97@gmail.com>\n"
"Language: ms\n" "Language: ms\n"
@ -27,13 +27,6 @@ msgstr "Nilai atau ungkapan yang sah. Hanya nombor dan operasi + - * / diterima.
msgid "Project name" msgid "Project name"
msgstr "Nama projek" msgstr "Nama projek"
#, fuzzy
msgid "Current private code"
msgstr "Kod peribadi baharu"
msgid "Enter existing private code to edit project"
msgstr ""
msgid "New private code" msgid "New private code"
msgstr "Kod peribadi baharu" msgstr "Kod peribadi baharu"
@ -58,13 +51,6 @@ msgstr "Mata wang asal"
msgid "Setting a default currency enables currency conversion between bills" msgid "Setting a default currency enables currency conversion between bills"
msgstr "" msgstr ""
#, fuzzy
msgid "Unknown error"
msgstr "Ralat yang tidak dikenalpasti"
msgid "Invalid private code."
msgstr "Kod peribadi tidak sah."
#, fuzzy #, fuzzy
msgid "" msgid ""
"This project cannot be set to 'no currency' because it contains bills in " "This project cannot be set to 'no currency' because it contains bills in "
@ -103,6 +89,13 @@ msgstr ""
msgid "Enter private code to confirm deletion" msgid "Enter private code to confirm deletion"
msgstr "Masukkan kod peribadi untuk mengesahkan penghapusan" msgstr "Masukkan kod peribadi untuk mengesahkan penghapusan"
#, fuzzy
msgid "Unknown error"
msgstr "Ralat yang tidak dikenalpasti"
msgid "Invalid private code."
msgstr "Kod peribadi tidak sah."
msgid "Get in" msgid "Get in"
msgstr "Masuk" msgstr "Masuk"
@ -273,9 +266,6 @@ msgstr ""
msgid "Password successfully reset." msgid "Password successfully reset."
msgstr "" msgstr ""
msgid "Project settings have been changed successfully."
msgstr ""
msgid "Unable to parse CSV" msgid "Unable to parse CSV"
msgstr "" msgstr ""
@ -768,9 +758,6 @@ msgstr ""
msgid "Settings" msgid "Settings"
msgstr "" msgstr ""
msgid "RSS Feed"
msgstr ""
msgid "Other projects :" msgid "Other projects :"
msgstr "" msgstr ""
@ -793,7 +780,7 @@ msgstr ""
msgid "Documentation" msgid "Documentation"
msgstr "" msgstr ""
msgid "Administration Dashboard" msgid "Administation Dashboard"
msgstr "" msgstr ""
msgid "Legal information" msgid "Legal information"
@ -847,10 +834,13 @@ msgstr ""
msgid "Nothing to list yet." msgid "Nothing to list yet."
msgstr "" msgstr ""
msgid "Add your first bill" msgid "You probably want to"
msgstr "" msgstr ""
msgid "Add the first participant" msgid "add a bill"
msgstr ""
msgid "add participants"
msgstr "" msgstr ""
msgid "Password reminder" msgid "Password reminder"
@ -873,14 +863,21 @@ msgstr ""
msgid "Invite people to join this project" msgid "Invite people to join this project"
msgstr "" msgstr ""
msgid "Share an invitation link" msgid "Share Identifier & code"
msgstr "" msgstr ""
msgid "" msgid ""
"The easiest way to invite people is to give them the following invitation" "You can share the project identifier and the private code by any "
" link.<br />They will be able to access the project, manage participants," "communication means."
" add/edit/delete bills. However, they will not have access to important " msgstr ""
"settings such as changing the private code or deleting the whole project."
msgid "Identifier:"
msgstr ""
msgid "Share the Link"
msgstr ""
msgid "You can directly share the following link via your prefered medium"
msgstr "" msgstr ""
msgid "Scan QR code" msgid "Scan QR code"
@ -893,29 +890,10 @@ msgid "Send via Emails"
msgstr "" msgstr ""
msgid "" msgid ""
"Specify a list of email adresses (separated by comma) of people you want " "Specify a (comma separated) list of email adresses you want to notify "
"to notify about the creation of this project. We will send them an email " "about the\n"
"with the invitation link." " creation of this budget management project and we will "
msgstr "" "send them an email for you."
msgid "Share Identifier & code"
msgstr ""
msgid ""
"You can share the project identifier and the private code by any "
"communication means.<br />Anyone with the private code will have access "
"to the full project, including changing settings such as the private code"
" or project email address, or even deleting the whole project."
msgstr ""
msgid "Identifier:"
msgstr ""
#, fuzzy
msgid "Private code:"
msgstr "Kod peribadi"
msgid "the private code was defined when you created the project"
msgstr "" msgstr ""
msgid "Who pays?" msgid "Who pays?"
@ -1055,43 +1033,3 @@ msgstr ""
#~ msgid "Edit the project" #~ msgid "Edit the project"
#~ msgstr "" #~ msgstr ""
#~ msgid "You probably want to"
#~ msgstr ""
#~ msgid "add a bill"
#~ msgstr ""
#~ msgid "add participants"
#~ msgstr ""
#~ msgid ""
#~ "You can share the project identifier "
#~ "and the private code by any "
#~ "communication means."
#~ msgstr ""
#~ msgid "Share the Link"
#~ msgstr ""
#~ msgid "You can directly share the following link via your prefered medium"
#~ msgstr ""
#~ msgid ""
#~ "Specify a (comma separated) list of "
#~ "email adresses you want to notify "
#~ "about the\n"
#~ " creation of this budget "
#~ "management project and we will send "
#~ "them an email for you."
#~ msgstr ""
#~ msgid ""
#~ "Specify a (comma separated) list of "
#~ "email adresses you want to notify "
#~ "about the\n"
#~ " creation of this budget "
#~ "management project and we will send "
#~ "them an email with the invitation "
#~ "link."
#~ msgstr ""

Binary file not shown.

View file

@ -3,7 +3,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-07-29 14:24+0200\n" "POT-Creation-Date: 2023-07-14 10:01+0200\n"
"PO-Revision-Date: 2022-02-13 16:54+0000\n" "PO-Revision-Date: 2022-02-13 16:54+0000\n"
"Last-Translator: Allan Nordhøy <epost@anotheragency.no>\n" "Last-Translator: Allan Nordhøy <epost@anotheragency.no>\n"
"Language: nb_NO\n" "Language: nb_NO\n"
@ -29,13 +29,6 @@ msgstr ""
msgid "Project name" msgid "Project name"
msgstr "Prosjektnavn" msgstr "Prosjektnavn"
#, fuzzy
msgid "Current private code"
msgstr "Ny privat kode"
msgid "Enter existing private code to edit project"
msgstr ""
msgid "New private code" msgid "New private code"
msgstr "Ny privat kode" msgstr "Ny privat kode"
@ -57,12 +50,6 @@ msgstr "Forvalgt valuta"
msgid "Setting a default currency enables currency conversion between bills" msgid "Setting a default currency enables currency conversion between bills"
msgstr "En forvalg valutainnstilling skrur på konvertering mellom regninger" msgstr "En forvalg valutainnstilling skrur på konvertering mellom regninger"
msgid "Unknown error"
msgstr "Ukjent feil"
msgid "Invalid private code."
msgstr "Ugyldig privat kode"
#, fuzzy #, fuzzy
msgid "" msgid ""
"This project cannot be set to 'no currency' because it contains bills in " "This project cannot be set to 'no currency' because it contains bills in "
@ -104,6 +91,12 @@ msgstr "Bekreft CAPTCHA-en for å fortsette."
msgid "Enter private code to confirm deletion" msgid "Enter private code to confirm deletion"
msgstr "Skriv inn privat kode for å bekrefte sletting" msgstr "Skriv inn privat kode for å bekrefte sletting"
msgid "Unknown error"
msgstr "Ukjent feil"
msgid "Invalid private code."
msgstr "Ugyldig privat kode"
#, fuzzy #, fuzzy
msgid "Get in" msgid "Get in"
msgstr "Til prosjektet" msgstr "Til prosjektet"
@ -282,9 +275,6 @@ msgstr "Ukjent prosjekt"
msgid "Password successfully reset." msgid "Password successfully reset."
msgstr "Passord tilbakestilt." msgstr "Passord tilbakestilt."
msgid "Project settings have been changed successfully."
msgstr ""
msgid "Unable to parse CSV" msgid "Unable to parse CSV"
msgstr "" msgstr ""
@ -829,9 +819,6 @@ msgstr "Historikk"
msgid "Settings" msgid "Settings"
msgstr "Innstillinger" msgstr "Innstillinger"
msgid "RSS Feed"
msgstr ""
#, fuzzy #, fuzzy
msgid "Other projects :" msgid "Other projects :"
msgstr "Andre prosjekter:" msgstr "Andre prosjekter:"
@ -855,7 +842,7 @@ msgstr "Mobilprogram"
msgid "Documentation" msgid "Documentation"
msgstr "Dokumentasjon" msgstr "Dokumentasjon"
msgid "Administration Dashboard" msgid "Administation Dashboard"
msgstr "Administrasjonsoversiktspanel" msgstr "Administrasjonsoversiktspanel"
#, fuzzy #, fuzzy
@ -912,13 +899,14 @@ msgstr "Ingen regninger"
msgid "Nothing to list yet." msgid "Nothing to list yet."
msgstr "Ingenting å liste opp enda." msgstr "Ingenting å liste opp enda."
#, fuzzy msgid "You probably want to"
msgid "Add your first bill" msgstr "Du ønsker antagelig å"
msgid "add a bill"
msgstr "legge til en regning" msgstr "legge til en regning"
#, fuzzy msgid "add participants"
msgid "Add the first participant" msgstr "legge til deltagere"
msgstr "Legg til deltager"
msgid "Password reminder" msgid "Password reminder"
msgstr "Passordpåminner" msgstr "Passordpåminner"
@ -942,15 +930,26 @@ msgstr "Tilbakestill passordet ditt"
msgid "Invite people to join this project" msgid "Invite people to join this project"
msgstr "Inviter folk til å ta del i dette prosjektet" msgstr "Inviter folk til å ta del i dette prosjektet"
msgid "Share an invitation link" #, fuzzy
msgstr "" msgid "Share Identifier & code"
msgstr "Del identifikator og kode"
msgid "" msgid ""
"The easiest way to invite people is to give them the following invitation" "You can share the project identifier and the private code by any "
" link.<br />They will be able to access the project, manage participants," "communication means."
" add/edit/delete bills. However, they will not have access to important "
"settings such as changing the private code or deleting the whole project."
msgstr "" msgstr ""
"Du kan dele prosjektidentifikatoren og den private koden slik det måtte "
"passe deg."
msgid "Identifier:"
msgstr "Identifikator:"
#, fuzzy
msgid "Share the Link"
msgstr "Del lenken"
msgid "You can directly share the following link via your prefered medium"
msgstr "Du kan dele denne lenken slik du ønsker"
msgid "Scan QR code" msgid "Scan QR code"
msgstr "" msgstr ""
@ -964,35 +963,15 @@ msgstr "Send via e-poster"
#, fuzzy #, fuzzy
msgid "" msgid ""
"Specify a list of email adresses (separated by comma) of people you want " "Specify a (comma separated) list of email adresses you want to notify "
"to notify about the creation of this project. We will send them an email " "about the\n"
"with the invitation link." " creation of this budget management project and we will "
"send them an email for you."
msgstr "" msgstr ""
"Angi en (kommainndelt) liste over e-postadresser du ønsker å varsle om\n" "Angi en (kommainndelt) liste over e-postadresser du ønsker å varsle om\n"
"opprettelsen av dette budsjetthåndteringsprosjektet, og de vil få en " "opprettelsen av dette budsjetthåndteringsprosjektet, og de vil få en "
"e-post om det." "e-post om det."
#, fuzzy
msgid "Share Identifier & code"
msgstr "Del identifikator og kode"
msgid ""
"You can share the project identifier and the private code by any "
"communication means.<br />Anyone with the private code will have access "
"to the full project, including changing settings such as the private code"
" or project email address, or even deleting the whole project."
msgstr ""
msgid "Identifier:"
msgstr "Identifikator:"
#, fuzzy
msgid "Private code:"
msgstr "Privat kode"
msgid "the private code was defined when you created the project"
msgstr ""
msgid "Who pays?" msgid "Who pays?"
msgstr "Hvem betaler?" msgstr "Hvem betaler?"
@ -1318,24 +1297,3 @@ msgstr "Periode"
#~ msgid "Edit the project" #~ msgid "Edit the project"
#~ msgstr "Rediger prosjektet" #~ msgstr "Rediger prosjektet"
#~ msgid "You probably want to"
#~ msgstr "Du ønsker antagelig å"
#~ msgid "add participants"
#~ msgstr "legge til deltagere"
#~ msgid ""
#~ "You can share the project identifier "
#~ "and the private code by any "
#~ "communication means."
#~ msgstr ""
#~ "Du kan dele prosjektidentifikatoren og "
#~ "den private koden slik det måtte "
#~ "passe deg."
#~ msgid "Share the Link"
#~ msgstr "Del lenken"
#~ msgid "You can directly share the following link via your prefered medium"
#~ msgstr "Du kan dele denne lenken slik du ønsker"

Binary file not shown.

View file

@ -1,18 +1,18 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: \n" "Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2023-07-29 14:24+0200\n" "POT-Creation-Date: 2023-07-14 10:01+0200\n"
"PO-Revision-Date: 2024-03-17 12:01+0000\n" "PO-Revision-Date: 2021-02-17 02:50+0000\n"
"Last-Translator: Xander Jennie <xanderjennie21@gmail.com>\n" "Last-Translator: Sander Kooijmans <weblate@gogognome.nl>\n"
"Language-Team: Dutch <https://hosted.weblate.org/projects/i-hate-money/"
"i-hate-money/nl/>\n"
"Language: nl\n" "Language: nl\n"
"Language-Team: Dutch <https://hosted.weblate.org/projects/i-hate-money/i"
"-hate-money/nl/>\n"
"Plural-Forms: nplurals=2; plural=n != 1\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n" "Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.5-dev\n"
"Generated-By: Babel 2.9.0\n" "Generated-By: Babel 2.9.0\n"
#, python-format #, python-format
@ -31,17 +31,12 @@ msgstr ""
msgid "Project name" msgid "Project name"
msgstr "Projectnaam" msgstr "Projectnaam"
msgid "Current private code" #, fuzzy
msgstr "Huidige Privécode"
msgid "Enter existing private code to edit project"
msgstr "Voer het huidige privécode in om dit project te bewerken"
msgid "New private code" msgid "New private code"
msgstr "Nieuwe privécode" msgstr "Privécode"
msgid "Enter a new code if you want to change it" msgid "Enter a new code if you want to change it"
msgstr "Voer een nieuwe code in als u deze wilt wijzigen" msgstr ""
msgid "Email" msgid "Email"
msgstr "E-mailadres" msgstr "E-mailadres"
@ -57,24 +52,14 @@ msgstr "Standaard munteenheid"
msgid "Setting a default currency enables currency conversion between bills" msgid "Setting a default currency enables currency conversion between bills"
msgstr "" msgstr ""
"Door een standaardvaluta in te stellen, is valutaconversie tussen facturen "
"mogelijk"
msgid "Unknown error"
msgstr "Onbekende fout"
msgid "Invalid private code."
msgstr "Ongeldige privécode."
msgid "" msgid ""
"This project cannot be set to 'no currency' because it contains bills in " "This project cannot be set to 'no currency' because it contains bills in "
"multiple currencies." "multiple currencies."
msgstr "" msgstr ""
"Dit project kan niet op 'geen valuta' worden gezet, omdat het facturen in "
"meerdere valuta's bevat."
msgid "Compatible with Cospend" msgid "Compatible with Cospend"
msgstr "Compatibel met mede besteden" msgstr ""
msgid "Project identifier" msgid "Project identifier"
msgstr "Project-id" msgstr "Project-id"
@ -92,16 +77,25 @@ msgid ""
msgstr "Er is al een project genaamd (\"%(project)s\"). Kies een andere naam" msgstr "Er is al een project genaamd (\"%(project)s\"). Kies een andere naam"
msgid "Which is a real currency: Euro or Petro dollar?" msgid "Which is a real currency: Euro or Petro dollar?"
msgstr "Wat is een echte valuta: de euro of de petro-dollar?" msgstr ""
#, fuzzy
msgid "euro" msgid "euro"
msgstr "euro" msgstr "Periode"
msgid "Please, validate the captcha to proceed." msgid "Please, validate the captcha to proceed."
msgstr "Valideer de captcha om door te gaan." msgstr ""
msgid "Enter private code to confirm deletion" msgid "Enter private code to confirm deletion"
msgstr "Voer de privécode in om de verwijder opdracht te bevestigen" msgstr ""
#, fuzzy
msgid "Unknown error"
msgstr "Onbekend project"
#, fuzzy
msgid "Invalid private code."
msgstr "Privécode"
msgid "Get in" msgid "Get in"
msgstr "Inloggen" msgstr "Inloggen"
@ -125,7 +119,7 @@ msgid "Password confirmation"
msgstr "Wachtwoord bevestigen" msgstr "Wachtwoord bevestigen"
msgid "Reset password" msgid "Reset password"
msgstr "Wachtwoord opnieuw instellen" msgstr "Wachtwoord herstellen"
msgid "When?" msgid "When?"
msgstr "Wanneer?" msgstr "Wanneer?"
@ -173,14 +167,16 @@ msgstr "Gewicht"
msgid "Add" msgid "Add"
msgstr "Toevoegen" msgstr "Toevoegen"
#, fuzzy
msgid "The participant name is invalid" msgid "The participant name is invalid"
msgstr "De naam van de deelnemer is ongeldig" msgstr "De gebruiker '%(name)s' is verwijderd"
#, fuzzy
msgid "This project already have this participant" msgid "This project already have this participant"
msgstr "Deze deelnemer is al lid van het project" msgstr "Deze deelnemer is al lid van het project"
msgid "People to notify" msgid "People to notify"
msgstr "Mensen om op de hoogte te stellen" msgstr ""
msgid "Send the invitations" msgid "Send the invitations"
msgstr "Uitnodigingen versturen" msgstr "Uitnodigingen versturen"
@ -193,52 +189,54 @@ msgid "Logout"
msgstr "Uitloggen" msgstr "Uitloggen"
msgid "Please check the email configuration of the server." msgid "Please check the email configuration of the server."
msgstr "Controleer de e-mailconfiguratie van de server." msgstr ""
#, python-format #, fuzzy, python-format
msgid "" msgid ""
"Please check the email configuration of the server or contact the " "Please check the email configuration of the server or contact the "
"administrator: %(admin_email)s" "administrator: %(admin_email)s"
msgstr "" msgstr ""
"Controleer de e-mailinstellingen van de server of neem contact op met de " "Sorry, er is iets fout gegaan bij het verzenden van de uitnodigingsmails."
"beheerder: %(admin_email)s" " Controleer de e-mailinstellingen van de server of neem contact op met de"
" beheerder."
#. List with two items only #. List with two items only
msgid "{dual_object_0} and {dual_object_1}" msgid "{dual_object_0} and {dual_object_1}"
msgstr "{dual_object_0} en {dual_object_1}" msgstr ""
#. Last two items of a list with more than 3 items #. Last two items of a list with more than 3 items
msgid "{previous_object}, and {end_object}" msgid "{previous_object}, and {end_object}"
msgstr "{previous_object} en{end_object}" msgstr ""
#. Two items in a middle of a list with more than 5 objects #. Two items in a middle of a list with more than 5 objects
msgid "{previous_object}, {next_object}" msgid "{previous_object}, {next_object}"
msgstr "{previous_object}, {next_object}" msgstr ""
#. First two items of a list with more than 3 items #. First two items of a list with more than 3 items
msgid "{start_object}, {next_object}" msgid "{start_object}, {next_object}"
msgstr "{start_object}, {next_object}" msgstr ""
msgid "No Currency" msgid "No Currency"
msgstr "Geen valuta" msgstr "Geen munteenheid"
#. Form error with only one error #. Form error with only one error
msgid "{prefix}: {error}" msgid "{prefix}: {error}"
msgstr "{prefix}: {error}" msgstr ""
#. Form error with a list of errors #. Form error with a list of errors
msgid "{prefix}:<br />{errors}" msgid "{prefix}:<br />{errors}"
msgstr "{prefix}:<br />{errors}" msgstr ""
#, fuzzy
msgid "Too many failed login attempts." msgid "Too many failed login attempts."
msgstr "Te veel mislukte inlogpogingen." msgstr "Te vaak onjuist ingelogd. Probeer het later opnieuw."
#, python-format #, python-format
msgid "This admin password is not the right one. Only %(num)d attempts left." msgid "This admin password is not the right one. Only %(num)d attempts left."
msgstr "Het admin-wachtwoord is onjuist. Je kunt het nog %(num)d keer proberen." msgstr "Het admin-wachtwoord is onjuist. Je kunt het nog %(num)d keer proberen."
msgid "Provided token is invalid" msgid "Provided token is invalid"
msgstr "Het opgegeven token is ongeldig" msgstr ""
msgid "This private code is not the right one" msgid "This private code is not the right one"
msgstr "Deze privécode is onjuist" msgstr "Deze privécode is onjuist"
@ -253,12 +251,14 @@ msgstr ""
"We hebben geprobeerd een herinneringsmail te versturen, maar er is iets " "We hebben geprobeerd een herinneringsmail te versturen, maar er is iets "
"fout gegaan. Je kunt het project nog steeds normaal gebruiken." "fout gegaan. Je kunt het project nog steeds normaal gebruiken."
#, fuzzy
msgid "" msgid ""
"Sorry, there was an error while sending you an email with password reset " "Sorry, there was an error while sending you an email with password reset "
"instructions." "instructions."
msgstr "" msgstr ""
"Sorry, er is iets fout gegaan bij het verzenden van een e-mail met " "Sorry, er is iets fout gegaan bij het verzenden van een e-mail met "
"instructies om je wachtwoord te herstellen." "instructies om je wachtwoord te herstellen. Controleer de "
"e-mailinstellingen van de server of neem contact op met de beheerder."
msgid "No token provided" msgid "No token provided"
msgstr "Geen toegangssleutel opgegeven" msgstr "Geen toegangssleutel opgegeven"
@ -272,15 +272,12 @@ msgstr "Onbekend project"
msgid "Password successfully reset." msgid "Password successfully reset."
msgstr "Wachtwoord is hersteld." msgstr "Wachtwoord is hersteld."
msgid "Project settings have been changed successfully."
msgstr ""
msgid "Unable to parse CSV" msgid "Unable to parse CSV"
msgstr "Kan CSV niet parseren" msgstr ""
#, python-format #, python-format
msgid "Missing attribute: %(attribute)s" msgid "Missing attribute: %(attribute)s"
msgstr "Ontbrekende attribute: %(attribute)s" msgstr ""
msgid "" msgid ""
"Cannot add bills in multiple currencies to a project without default " "Cannot add bills in multiple currencies to a project without default "
@ -297,7 +294,7 @@ msgid "Error deleting project"
msgstr "" msgstr ""
msgid "Unable to logout" msgid "Unable to logout"
msgstr "Kan niet uitloggen" msgstr ""
#, python-format #, python-format
msgid "You have been invited to share your expenses for %(project)s" msgid "You have been invited to share your expenses for %(project)s"
@ -347,10 +344,10 @@ msgid "The bill has been added"
msgstr "De rekening is toegevoegd" msgstr "De rekening is toegevoegd"
msgid "Error deleting bill" msgid "Error deleting bill"
msgstr "Fout bij verwijderen factuur" msgstr ""
msgid "The bill has been deleted" msgid "The bill has been deleted"
msgstr "De factuur is verwijderd" msgstr "De rekening is verwijderd"
msgid "The bill has been modified" msgid "The bill has been modified"
msgstr "De rekening is aangepast" msgstr "De rekening is aangepast"
@ -386,8 +383,9 @@ msgstr "Terug naar de lijst"
msgid "Administration tasks are currently disabled." msgid "Administration tasks are currently disabled."
msgstr "Beheerderstaken zijn momenteel uitgeschakeld." msgstr "Beheerderstaken zijn momenteel uitgeschakeld."
#, fuzzy
msgid "Authentication" msgid "Authentication"
msgstr "Authenticatie" msgstr "Documentatie"
msgid "The project you are trying to access do not exist, do you want to" msgid "The project you are trying to access do not exist, do you want to"
msgstr "Het project dat je probeert te benaderen bestaat niet. Wil je" msgstr "Het project dat je probeert te benaderen bestaat niet. Wil je"
@ -404,17 +402,18 @@ msgstr "Nieuw project aanmaken"
msgid "Project" msgid "Project"
msgstr "Project" msgstr "Project"
#, fuzzy
msgid "Number of participants" msgid "Number of participants"
msgstr "Aantal deelnemers" msgstr "deelnemers toevoegen"
msgid "Number of bills" msgid "Number of bills"
msgstr "Aantal facturen" msgstr "Aantal rekeningen"
msgid "Newest bill" msgid "Newest bill"
msgstr "Nieuwste factuur" msgstr "Nieuwste rekening"
msgid "Oldest bill" msgid "Oldest bill"
msgstr "Oudste factuur" msgstr "Oudste rekening"
msgid "Actions" msgid "Actions"
msgstr "Acties" msgstr "Acties"
@ -422,8 +421,9 @@ msgstr "Acties"
msgid "edit" msgid "edit"
msgstr "bewerken" msgstr "bewerken"
#, fuzzy
msgid "Delete project" msgid "Delete project"
msgstr "Project verwijderen" msgstr "Project aanpassen"
msgid "show" msgid "show"
msgstr "tonen" msgstr "tonen"
@ -431,8 +431,9 @@ msgstr "tonen"
msgid "The Dashboard is currently deactivated." msgid "The Dashboard is currently deactivated."
msgstr "De overzichtspagina is momenteel uitgeschakeld." msgstr "De overzichtspagina is momenteel uitgeschakeld."
#, fuzzy
msgid "Download Mobile Application" msgid "Download Mobile Application"
msgstr "Mobiele applicatie downloaden" msgstr "Mobiele app"
#, fuzzy #, fuzzy
msgid "Get it on" msgid "Get it on"
@ -441,8 +442,9 @@ msgstr "Inloggen"
msgid "Edit project" msgid "Edit project"
msgstr "Project aanpassen" msgstr "Project aanpassen"
#, fuzzy
msgid "Import project" msgid "Import project"
msgstr "Project importeren" msgstr "Project aanpassen"
msgid "Download project's data" msgid "Download project's data"
msgstr "Projectgegevens downloaden" msgstr "Projectgegevens downloaden"
@ -789,9 +791,6 @@ msgstr "Geschiedenis"
msgid "Settings" msgid "Settings"
msgstr "Instellingen" msgstr "Instellingen"
msgid "RSS Feed"
msgstr ""
msgid "Other projects :" msgid "Other projects :"
msgstr "Overige projecten:" msgstr "Overige projecten:"
@ -814,7 +813,7 @@ msgstr "Mobiele app"
msgid "Documentation" msgid "Documentation"
msgstr "Documentatie" msgstr "Documentatie"
msgid "Administration Dashboard" msgid "Administation Dashboard"
msgstr "Administratie-overzicht" msgstr "Administratie-overzicht"
#, fuzzy #, fuzzy
@ -869,13 +868,14 @@ msgstr "Geen rekeningen"
msgid "Nothing to list yet." msgid "Nothing to list yet."
msgstr "Nog niks." msgstr "Nog niks."
#, fuzzy msgid "You probably want to"
msgid "Add your first bill" msgstr "Waarschijnlijk wil je"
msgid "add a bill"
msgstr "een rekening toevoegen" msgstr "een rekening toevoegen"
#, fuzzy msgid "add participants"
msgid "Add the first participant" msgstr "deelnemers toevoegen"
msgstr "Deelnemer toevoegen"
msgid "Password reminder" msgid "Password reminder"
msgstr "Wachtwoordhint" msgstr "Wachtwoordhint"
@ -899,15 +899,24 @@ msgstr "Wachtwoord herstellen"
msgid "Invite people to join this project" msgid "Invite people to join this project"
msgstr "Nodig mensen uit voor dit project" msgstr "Nodig mensen uit voor dit project"
msgid "Share an invitation link" msgid "Share Identifier & code"
msgstr "" msgstr "Identificatie en code delen"
msgid "" msgid ""
"The easiest way to invite people is to give them the following invitation" "You can share the project identifier and the private code by any "
" link.<br />They will be able to access the project, manage participants," "communication means."
" add/edit/delete bills. However, they will not have access to important "
"settings such as changing the private code or deleting the whole project."
msgstr "" msgstr ""
"Je kunt de projectidentificatie en privécode delen via welk "
"communicatieplatform dan ook."
msgid "Identifier:"
msgstr "Identificatie:"
msgid "Share the Link"
msgstr "Link delen"
msgid "You can directly share the following link via your prefered medium"
msgstr "Je kunt de volgende link direct delen"
msgid "Scan QR code" msgid "Scan QR code"
msgstr "" msgstr ""
@ -918,37 +927,17 @@ msgstr ""
msgid "Send via Emails" msgid "Send via Emails"
msgstr "Versturen via e-mail" msgstr "Versturen via e-mail"
#, fuzzy
msgid "" msgid ""
"Specify a list of email adresses (separated by comma) of people you want " "Specify a (comma separated) list of email adresses you want to notify "
"to notify about the creation of this project. We will send them an email " "about the\n"
"with the invitation link." " creation of this budget management project and we will "
"send them an email for you."
msgstr "" msgstr ""
"Stel een (kommagescheiden) lijst op met e-mailadressen van personen die " "Stel een (kommagescheiden) lijst op met e-mailadressen van personen die "
"je op de\n" "je op de\n"
" hoogte wilt stellen van dit project - wij sturen hen een " " hoogte wilt stellen van dit project - wij sturen hen een "
"e-mail." "e-mail."
msgid "Share Identifier & code"
msgstr "Identificatie en code delen"
msgid ""
"You can share the project identifier and the private code by any "
"communication means.<br />Anyone with the private code will have access "
"to the full project, including changing settings such as the private code"
" or project email address, or even deleting the whole project."
msgstr ""
msgid "Identifier:"
msgstr "Identificatie:"
#, fuzzy
msgid "Private code:"
msgstr "Privécode"
msgid "the private code was defined when you created the project"
msgstr ""
msgid "Who pays?" msgid "Who pays?"
msgstr "Wie betaalt?" msgstr "Wie betaalt?"
@ -1131,23 +1120,3 @@ msgstr "Periode"
#~ msgid "Edit the project" #~ msgid "Edit the project"
#~ msgstr "Project bewerken" #~ msgstr "Project bewerken"
#~ msgid "You probably want to"
#~ msgstr "Waarschijnlijk wil je"
#~ msgid "add participants"
#~ msgstr "deelnemers toevoegen"
#~ msgid ""
#~ "You can share the project identifier "
#~ "and the private code by any "
#~ "communication means."
#~ msgstr ""
#~ "Je kunt de projectidentificatie en "
#~ "privécode delen via welk communicatieplatform"
#~ " dan ook."
#~ msgid "Share the Link"
#~ msgstr "Link delen"
#~ msgid "You can directly share the following link via your prefered medium"
#~ msgstr "Je kunt de volgende link direct delen"

View file

@ -1,932 +0,0 @@
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-06-15 16:06+0200\n"
"PO-Revision-Date: 2024-07-03 19:09+0000\n"
"Last-Translator: Quentin PAGÈS <quentinantonin@free.fr>\n"
"Language-Team: Occitan <https://hosted.weblate.org/projects/i-hate-money/"
"i-hate-money/oc/>\n"
"Language: oc\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n > 1;\n"
"X-Generator: Weblate 5.7-dev\n"
#, python-format
msgid "You have just created '%(project)s' to share your expenses"
msgstr "Venètz de crear « %(project)s » per despartir vòstras despensas"
msgid ""
"Not a valid amount or expression. Only numbers and + - * / operators are "
"accepted."
msgstr ""
"Quantitat o expression non valida. Sonque los nombre e operator +-*/ son "
"acceptats."
msgid "Project name"
msgstr "Nom del projècte"
msgid "Current private code"
msgstr "Còdi daccès actual"
msgid "Enter existing private code to edit project"
msgstr "Picatz lo còdi daccès existissent per editar lo projècte"
msgid "New private code"
msgstr "Còdi privat novèl"
msgid "Enter a new code if you want to change it"
msgstr "Picatz lo còdi novèl se lo volètz cambiar"
msgid "Email"
msgstr "Adreça electronica"
msgid "Enable project history"
msgstr "Activar listoric de projècte"
msgid "Use IP tracking for project history"
msgstr "Reculhir las adreças IP dins listoric de projècte"
msgid "Default Currency"
msgstr "Moneda predeterminada"
msgid "Setting a default currency enables currency conversion between bills"
msgstr ""
msgid "Unknown error"
msgstr "Error desconeguda"
msgid "Invalid private code."
msgstr "Còdi daccès invalid."
msgid ""
"This project cannot be set to 'no currency' because it contains bills in "
"multiple currencies."
msgstr ""
msgid "Compatible with Cospend"
msgstr "Compatible amb Cospend"
msgid "Project identifier"
msgstr "Identificant del projècte"
msgid "Private code"
msgstr "Còdi privat"
msgid "Create the project"
msgstr "Crear lo projècte"
#, python-format
msgid ""
"A project with this identifier (\"%(project)s\") already exists. Please "
"choose a new identifier"
msgstr ""
msgid "Which is a real currency: Euro or Petro dollar?"
msgstr ""
msgid "euro"
msgstr "èuro"
msgid "Please, validate the captcha to proceed."
msgstr "Se vos plai, validatz el captcha per contunhar."
msgid "Enter private code to confirm deletion"
msgstr "Picatz lo còdi daccès per confirmar la supression"
msgid "Get in"
msgstr "Dintrar"
msgid "Admin password"
msgstr "Senhal dadministracion"
msgid "Send me the code by email"
msgstr "Mandatz-me lo còdi per corrièl"
msgid "This project does not exists"
msgstr "Aqueste projècte existís pas"
msgid "Password mismatch"
msgstr ""
msgid "Password"
msgstr "Senhal"
msgid "Password confirmation"
msgstr "Confirmacion del senhla"
msgid "Reset password"
msgstr "Reïnicializar lo senhal"
msgid "When?"
msgstr "Quand ?"
msgid "What?"
msgstr "Qué ?"
msgid "Who paid?"
msgstr "Qual paguèt ?"
msgid "How much?"
msgstr "Quant ?"
msgid "Currency"
msgstr "Moneda"
msgid "External link"
msgstr "Ligam extèrne"
msgid "A link to an external document, related to this bill"
msgstr ""
msgid "For whom?"
msgstr "Per qual ?"
msgid "Submit"
msgstr "Enviar"
msgid "Submit and add a new one"
msgstr ""
#, python-format
msgid "Project default: %(currency)s"
msgstr ""
msgid "Name"
msgstr "Nom"
msgid "Weights should be positive"
msgstr ""
msgid "Weight"
msgstr "Pes"
msgid "Add"
msgstr "Apondre"
msgid "The participant name is invalid"
msgstr "Lo nom del participant es incorrècte"
msgid "This project already have this participant"
msgstr "Aqueste projècte a ja aqueste participant"
msgid "People to notify"
msgstr "Personas de convidar"
msgid "Send the invitations"
msgstr "Enviar las invitacions"
#, python-format
msgid "The email %(email)s is not valid"
msgstr ""
msgid "Logout"
msgstr "Desconnexion"
msgid "Please check the email configuration of the server."
msgstr ""
#, python-format
msgid ""
"Please check the email configuration of the server or contact the "
"administrator: %(admin_email)s"
msgstr ""
#. List with two items only
msgid "{dual_object_0} and {dual_object_1}"
msgstr ""
#. Last two items of a list with more than 3 items
msgid "{previous_object}, and {end_object}"
msgstr ""
#. Two items in a middle of a list with more than 5 objects
msgid "{previous_object}, {next_object}"
msgstr "{previous_object}, {next_object}"
#. First two items of a list with more than 3 items
msgid "{start_object}, {next_object}"
msgstr "{start_object}, {next_object}"
msgid "No Currency"
msgstr "Cap de moneda"
#. Form error with only one error
msgid "{prefix}: {error}"
msgstr "{prefix} : {error}"
#. Form error with a list of errors
msgid "{prefix}:<br />{errors}"
msgstr "{prefix} :<br />{errors}"
msgid "Too many failed login attempts."
msgstr ""
#, python-format
msgid "This admin password is not the right one. Only %(num)d attempts left."
msgstr ""
msgid "Provided token is invalid"
msgstr "Aqueste geton es invalid"
msgid "This private code is not the right one"
msgstr ""
msgid "A reminder email has just been sent to you"
msgstr ""
msgid ""
"We tried to send you an reminder email, but there was an error. You can "
"still use the project normally."
msgstr ""
msgid ""
"Sorry, there was an error while sending you an email with password reset "
"instructions."
msgstr ""
msgid "No token provided"
msgstr ""
msgid "Invalid token"
msgstr "Geton invalid"
msgid "Unknown project"
msgstr ""
msgid "Password successfully reset."
msgstr ""
msgid "Project settings have been changed successfully."
msgstr ""
msgid "Unable to parse CSV"
msgstr ""
#, python-format
msgid "Missing attribute: %(attribute)s"
msgstr "Atribut mancant : %(attribute)s"
msgid ""
"Cannot add bills in multiple currencies to a project without default "
"currency"
msgstr ""
msgid "Project successfully uploaded"
msgstr ""
msgid "Project successfully deleted"
msgstr ""
msgid "Error deleting project"
msgstr ""
msgid "Unable to logout"
msgstr ""
#, python-format
msgid "You have been invited to share your expenses for %(project)s"
msgstr ""
msgid "Your invitations have been sent"
msgstr ""
msgid "Sorry, there was an error while trying to send the invitation emails."
msgstr ""
#, python-format
msgid "%(member)s has been added"
msgstr ""
msgid "Error activating participant"
msgstr ""
#, python-format
msgid "%(name)s is part of this project again"
msgstr ""
msgid "Error removing participant"
msgstr ""
#, python-format
msgid ""
"Participant '%(name)s' has been deactivated. It will still appear in the "
"list until its balance reach zero."
msgstr ""
#, python-format
msgid "Participant '%(name)s' has been removed"
msgstr ""
#, python-format
msgid "Participant '%(name)s' has been modified"
msgstr ""
msgid "The bill has been added"
msgstr ""
msgid "Error deleting bill"
msgstr ""
msgid "The bill has been deleted"
msgstr ""
msgid "The bill has been modified"
msgstr ""
#, python-format
msgid "%(lang)s is not a supported language"
msgstr ""
msgid "Error deleting project history"
msgstr ""
msgid "Deleted project history."
msgstr ""
msgid "Error deleting recorded IP addresses"
msgstr ""
msgid "Deleted recorded IP addresses in project history."
msgstr ""
msgid "Sorry, we were unable to find the page you've asked for."
msgstr ""
msgid "The best thing to do is probably to get back to the main page."
msgstr ""
msgid "Back to the list"
msgstr ""
msgid "Administration tasks are currently disabled."
msgstr ""
msgid "Authentication"
msgstr ""
msgid "The project you are trying to access do not exist, do you want to"
msgstr ""
msgid "create it"
msgstr ""
msgid "?"
msgstr ""
msgid "Create a new project"
msgstr ""
msgid "Project"
msgstr "Projècte"
msgid "Number of participants"
msgstr ""
msgid "Number of bills"
msgstr ""
msgid "Newest bill"
msgstr ""
msgid "Oldest bill"
msgstr ""
msgid "Actions"
msgstr ""
msgid "edit"
msgstr "modifica"
msgid "Delete project"
msgstr ""
msgid "show"
msgstr "mostrar"
msgid "The Dashboard is currently deactivated."
msgstr ""
msgid "Download Mobile Application"
msgstr ""
msgid "Get it on"
msgstr ""
msgid "Edit project"
msgstr ""
msgid "Import project"
msgstr ""
msgid "Download project's data"
msgstr ""
msgid "Bill items"
msgstr ""
msgid "Download the list of bills with owner, amount, reason,... "
msgstr ""
msgid "Settle plans"
msgstr ""
msgid "Download the list of transactions needed to settle the current bills."
msgstr ""
msgid "Can't remember the password?"
msgstr ""
msgid "Cancel"
msgstr "Anullar"
msgid "Privacy Settings"
msgstr "Paramètres de confidencialitat"
msgid "Save changes"
msgstr "Enregistrar las modificacions"
msgid "This will remove all bills and participants in this project!"
msgstr ""
msgid "Import previously exported project"
msgstr ""
msgid "Choose file"
msgstr ""
msgid "Edit this bill"
msgstr ""
msgid "Add a bill"
msgstr ""
msgid "Simple operations are allowed, e.g. (18+36.2)/3"
msgstr ""
msgid "Everyone"
msgstr "Tot lo monde"
msgid "No one"
msgstr "Degun"
msgid "More options"
msgstr "Mai d'opcions"
msgid "Add participant"
msgstr ""
msgid "Edit this participant"
msgstr ""
msgid "john.doe@example.com, mary.moe@site.com"
msgstr ""
msgid "Download"
msgstr ""
msgid "Disabled Project History"
msgstr ""
msgid "Disabled Project History & IP Address Recording"
msgstr ""
msgid "Enabled Project History"
msgstr ""
msgid "Disabled IP Address Recording"
msgstr ""
msgid "Enabled Project History & IP Address Recording"
msgstr ""
msgid "Enabled IP Address Recording"
msgstr ""
msgid "History Settings Changed"
msgstr ""
#, python-format
msgid "Bill %(name)s: %(property_name)s changed from %(before)s to %(after)s"
msgstr ""
#, python-format
msgid "Bill %(name)s: %(property_name)s changed to %(after)s"
msgstr ""
msgid "Confirm Remove IP Adresses"
msgstr ""
msgid ""
"Are you sure you want to delete all recorded IP addresses from this "
"project?\n"
" The rest of the project history will be unaffected. This "
"action cannot be undone."
msgstr ""
msgid "Confirm deletion"
msgstr "Confirmar la supression"
msgid "Close"
msgstr "Tampar"
msgid "Delete Confirmation"
msgstr ""
msgid ""
"Are you sure you want to erase all history for this project? This action "
"cannot be undone."
msgstr ""
#, python-format
msgid "Bill %(name)s: added %(owers_list_str)s to owers list"
msgstr ""
#, python-format
msgid "Bill %(name)s: removed %(owers_list_str)s from owers list"
msgstr ""
msgid "This project has history disabled. New actions won't appear below."
msgstr ""
msgid "You can enable history on the settings page."
msgstr ""
msgid ""
"The table below reflects actions recorded prior to disabling project "
"history."
msgstr ""
msgid "You can clear the project history to remove them."
msgstr ""
msgid ""
"Some entries below contain IP addresses, even though this project has IP "
"recording disabled. "
msgstr ""
msgid "Delete stored IP addresses"
msgstr ""
msgid "No IP Addresses to erase"
msgstr ""
msgid "Delete Stored IP Addresses"
msgstr ""
msgid "No history to erase"
msgstr ""
msgid "Clear Project History"
msgstr ""
msgid "Time"
msgstr ""
msgid "Event"
msgstr ""
msgid "IP address recording can be enabled on the settings page"
msgstr ""
msgid "IP address recording can be disabled on the settings page"
msgstr ""
msgid "From IP"
msgstr ""
#, python-format
msgid "Project %(name)s added"
msgstr ""
#, python-format
msgid "Bill %(name)s added"
msgstr ""
#, python-format
msgid "Participant %(name)s added"
msgstr ""
msgid "Project private code changed"
msgstr ""
#, python-format
msgid "Project renamed to %(new_project_name)s"
msgstr ""
#, python-format
msgid "Project contact email changed to %(new_email)s"
msgstr ""
msgid "Project settings modified"
msgstr ""
#, python-format
msgid "Participant %(name)s deactivated"
msgstr ""
#, python-format
msgid "Participant %(name)s reactivated"
msgstr ""
#, python-format
msgid "Participant %(name)s renamed to %(new_name)s"
msgstr ""
#, python-format
msgid "Bill %(name)s renamed to %(new_description)s"
msgstr ""
#, python-format
msgid "Participant %(name)s: weight changed from %(old_weight)s to %(new_weight)s"
msgstr ""
msgid "Payer"
msgstr "Pagaire"
msgid "Amount"
msgstr "Soma"
msgid "Date"
msgstr "Data"
#, python-format
msgid "Amount in %(currency)s"
msgstr ""
#, python-format
msgid "Bill %(name)s modified"
msgstr ""
#, python-format
msgid "Participant %(name)s modified"
msgstr ""
#, python-format
msgid "Bill %(name)s removed"
msgstr ""
#, python-format
msgid "Participant %(name)s removed"
msgstr ""
#, python-format
msgid "Project %(name)s changed in an unknown way"
msgstr ""
#, python-format
msgid "Bill %(name)s changed in an unknown way"
msgstr ""
#, python-format
msgid "Participant %(name)s changed in an unknown way"
msgstr ""
msgid "Nothing to list"
msgstr "Pas res a listar"
msgid "Someone probably cleared the project history."
msgstr ""
msgid "Manage your shared <br />expenses, easily"
msgstr ""
msgid "Try out the demo"
msgstr ""
msgid "You're sharing a house?"
msgstr ""
msgid "Going on holidays with friends?"
msgstr ""
msgid "Simply sharing money with others?"
msgstr ""
msgid "We can help!"
msgstr ""
msgid "Log in to an existing project"
msgstr ""
msgid "Log in"
msgstr "Connexion"
msgid "can't remember your password?"
msgstr ""
msgid "Create"
msgstr "Crear"
msgid ""
"Don\\'t reuse a personal password. Choose a private code and send it to "
"your friends"
msgstr ""
msgid "Account manager"
msgstr "Gestionari de compte"
msgid "Bills"
msgstr "Facturas"
msgid "Settle"
msgstr ""
msgid "Statistics"
msgstr "Estatisticas"
msgid "Languages"
msgstr "Lengas"
msgid "Projects"
msgstr "Projèctes"
msgid "Start a new project"
msgstr ""
msgid "History"
msgstr ""
msgid "Settings"
msgstr ""
msgid "RSS Feed"
msgstr ""
msgid "Other projects :"
msgstr ""
msgid "switch to"
msgstr ""
msgid "Dashboard"
msgstr "Panèl dadministracion"
#, python-format
msgid "Please retry after %(date)s."
msgstr ""
msgid "Code"
msgstr "Còdi"
msgid "Mobile Application"
msgstr ""
msgid "Documentation"
msgstr "Documentacion"
msgid "Administration Dashboard"
msgstr "Panèl dadministracion"
msgid "Legal information"
msgstr ""
msgid "\"I hate money\" is free software"
msgstr ""
msgid "you can contribute and improve it!"
msgstr ""
#, python-format
msgid "%(amount)s each"
msgstr "%(amount)s cadun"
msgid "you sure?"
msgstr "O volètz vertadièrament?"
msgid "Invite people"
msgstr "Convidar de monde"
msgid "Newer bills"
msgstr ""
msgid "Older bills"
msgstr ""
msgid "You should start by adding participants"
msgstr ""
msgid "Add a new bill"
msgstr ""
msgid "For what?"
msgstr "Per qué ?"
#, python-format
msgid "Added on %(date)s"
msgstr ""
#, python-format
msgid "Everyone but %(excluded)s"
msgstr ""
msgid "delete"
msgstr "suprimir"
msgid "No bills"
msgstr "Cap de factura"
msgid "Nothing to list yet."
msgstr "Pas res a listar pel moment."
msgid "Add your first bill"
msgstr ""
msgid "Add the first participant"
msgstr ""
msgid "Password reminder"
msgstr ""
msgid ""
"A link to reset your password has been sent to you, please check your "
"emails."
msgstr ""
msgid "Return to home page"
msgstr ""
msgid "Your projects"
msgstr ""
msgid "Reset your password"
msgstr ""
msgid "Invite people to join this project"
msgstr ""
msgid "Share an invitation link"
msgstr ""
msgid ""
"The easiest way to invite people is to give them the following invitation"
" link.<br />They will be able to access the project, manage participants,"
" add/edit/delete bills. However, they will not have access to important "
"settings such as changing the private code or deleting the whole project."
msgstr ""
msgid "Scan QR code"
msgstr ""
msgid "Use a mobile device with a compatible app."
msgstr ""
msgid "Send via Emails"
msgstr ""
msgid ""
"Specify a list of email adresses (separated by comma) of people you want "
"to notify about the creation of this project. We will send them an email "
"with the invitation link."
msgstr ""
msgid "Share Identifier & code"
msgstr ""
msgid ""
"You can share the project identifier and the private code by any "
"communication means.<br />Anyone with the private code will have access "
"to the full project, including changing settings such as the private code"
" or project email address, or even deleting the whole project."
msgstr ""
msgid "Identifier:"
msgstr ""
msgid "Private code:"
msgstr "Còdi privat :"
msgid "the private code was defined when you created the project"
msgstr ""
msgid "Who pays?"
msgstr "Qual paga ?"
msgid "To whom?"
msgstr "A qual ?"
msgid "Who?"
msgstr "Qual ?"
msgid "Balance"
msgstr ""
msgid "deactivate"
msgstr ""
msgid "reactivate"
msgstr ""
msgid "Paid"
msgstr "Pagat"
msgid "Spent"
msgstr "Despensat"
msgid "Expenses by Month"
msgstr "Despensas per mes"
msgid "Period"
msgstr "Periòde"

Binary file not shown.

View file

@ -1,19 +1,19 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-07-29 14:24+0200\n" "POT-Creation-Date: 2023-07-14 10:01+0200\n"
"PO-Revision-Date: 2023-08-24 18:48+0000\n" "PO-Revision-Date: 2022-09-27 14:19+0000\n"
"Last-Translator: Eryk Michalak <gnu.ewm@protonmail.com>\n" "Last-Translator: Andrzej Ochodek <andrzej.ochodek@gmail.com>\n"
"Language-Team: Polish <https://hosted.weblate.org/projects/i-hate-money/"
"i-hate-money/pl/>\n"
"Language: pl\n" "Language: pl\n"
"Language-Team: Polish <https://hosted.weblate.org/projects/i-hate-money/i"
"-hate-money/pl/>\n"
"Plural-Forms: nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && "
"(n%100<10 || n%100>=20) ? 1 : 2\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n" "Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
"|| n%100>=20) ? 1 : 2;\n"
"X-Generator: Weblate 5.0\n"
"Generated-By: Babel 2.9.0\n" "Generated-By: Babel 2.9.0\n"
#, python-format #, python-format
@ -30,12 +30,6 @@ msgstr ""
msgid "Project name" msgid "Project name"
msgstr "Nazwa projektu" msgstr "Nazwa projektu"
msgid "Current private code"
msgstr "Bieżący kod prywatny"
msgid "Enter existing private code to edit project"
msgstr "Wprowadź kod prywatny aby edytować projekt"
msgid "New private code" msgid "New private code"
msgstr "Nowy kod prywatny" msgstr "Nowy kod prywatny"
@ -59,12 +53,6 @@ msgstr ""
"Wybranie domyślnej waluty pozwala na konwersję walutową pomiędzy " "Wybranie domyślnej waluty pozwala na konwersję walutową pomiędzy "
"rachunkami" "rachunkami"
msgid "Unknown error"
msgstr "Nieznany błąd"
msgid "Invalid private code."
msgstr "Nieprawidłowy kod prywatny."
msgid "" msgid ""
"This project cannot be set to 'no currency' because it contains bills in " "This project cannot be set to 'no currency' because it contains bills in "
"multiple currencies." "multiple currencies."
@ -73,7 +61,7 @@ msgstr ""
" on rachunki w różnych walutach." " on rachunki w różnych walutach."
msgid "Compatible with Cospend" msgid "Compatible with Cospend"
msgstr "Zgodne z Cospend" msgstr ""
msgid "Project identifier" msgid "Project identifier"
msgstr "Identyfikator projektu" msgstr "Identyfikator projektu"
@ -104,6 +92,12 @@ msgstr "Rozwiąż captcha, by kontynuować."
msgid "Enter private code to confirm deletion" msgid "Enter private code to confirm deletion"
msgstr "Wprowadź kod prywatny w celu potwierdzenia usunięcia" msgstr "Wprowadź kod prywatny w celu potwierdzenia usunięcia"
msgid "Unknown error"
msgstr "Nieznany błąd"
msgid "Invalid private code."
msgstr "Nieprawidłowy kod prywatny."
msgid "Get in" msgid "Get in"
msgstr "Wejdź" msgstr "Wejdź"
@ -194,15 +188,16 @@ msgid "Logout"
msgstr "Wyloguj" msgstr "Wyloguj"
msgid "Please check the email configuration of the server." msgid "Please check the email configuration of the server."
msgstr "Prosimy sprawdzić konfigurację e-mail serwera." msgstr ""
#, python-format #, fuzzy, python-format
msgid "" msgid ""
"Please check the email configuration of the server or contact the " "Please check the email configuration of the server or contact the "
"administrator: %(admin_email)s" "administrator: %(admin_email)s"
msgstr "" msgstr ""
"Prosimy sprawdzić konfigurację e-mail serwera lub skontaktować się z " "Przepraszamy, wystąpił błąd podczas próby wysłania wiadomości e-mail z "
"administratorem: %(admin_email)s" "zaproszeniem. Sprawdź konfigurację e-mail serwera lub skontaktuj się z "
"administratorem."
#. List with two items only #. List with two items only
msgid "{dual_object_0} and {dual_object_1}" msgid "{dual_object_0} and {dual_object_1}"
@ -231,8 +226,9 @@ msgstr "{prefix}: {error}"
msgid "{prefix}:<br />{errors}" msgid "{prefix}:<br />{errors}"
msgstr "{prefix}:<br />{errors}" msgstr "{prefix}:<br />{errors}"
#, fuzzy
msgid "Too many failed login attempts." msgid "Too many failed login attempts."
msgstr "Zbyt wiele nieudanych prób logowania." msgstr "Zbyt wiele nieudanych prób logowania, spróbuj ponownie później."
#, python-format #, python-format
msgid "This admin password is not the right one. Only %(num)d attempts left." msgid "This admin password is not the right one. Only %(num)d attempts left."
@ -254,12 +250,14 @@ msgstr ""
"Próbowaliśmy wysłać Ci wiadomość e-mail z przypomnieniem, ale wystąpił " "Próbowaliśmy wysłać Ci wiadomość e-mail z przypomnieniem, ale wystąpił "
"błąd. Nadal możesz normalnie korzystać z projektu." "błąd. Nadal możesz normalnie korzystać z projektu."
#, fuzzy
msgid "" msgid ""
"Sorry, there was an error while sending you an email with password reset " "Sorry, there was an error while sending you an email with password reset "
"instructions." "instructions."
msgstr "" msgstr ""
"Przepraszamy, wystąpił błąd podczas wysyłania wiadomości e-mail z " "Przepraszamy, wystąpił błąd podczas wysyłania wiadomości e-mail z "
"instrukcjami resetowania hasła." "instrukcjami resetowania hasła. Sprawdź konfigurację e-mail serwera lub "
"skontaktuj się z administratorem."
msgid "No token provided" msgid "No token provided"
msgstr "Nie podano tokena" msgstr "Nie podano tokena"
@ -273,15 +271,12 @@ msgstr "Nieznany projekt"
msgid "Password successfully reset." msgid "Password successfully reset."
msgstr "Hasło zostało pomyślnie zresetowane." msgstr "Hasło zostało pomyślnie zresetowane."
msgid "Project settings have been changed successfully."
msgstr "Ustawienia projektu zostały pomyślnie zmienione."
msgid "Unable to parse CSV" msgid "Unable to parse CSV"
msgstr "Nie udało się odczytać pliku CSV" msgstr ""
#, python-format #, python-format
msgid "Missing attribute: %(attribute)s" msgid "Missing attribute: %(attribute)s"
msgstr "Brakujący atrybut: %(attribute)s" msgstr ""
msgid "" msgid ""
"Cannot add bills in multiple currencies to a project without default " "Cannot add bills in multiple currencies to a project without default "
@ -300,7 +295,7 @@ msgid "Error deleting project"
msgstr "Błąd podczas usuwania projektu" msgstr "Błąd podczas usuwania projektu"
msgid "Unable to logout" msgid "Unable to logout"
msgstr "Nie można się wylogować" msgstr ""
#, python-format #, python-format
msgid "You have been invited to share your expenses for %(project)s" msgid "You have been invited to share your expenses for %(project)s"
@ -309,10 +304,12 @@ msgstr "Zostałeś zaproszony do podzielenia się swoimi wydatkami w %(project)s
msgid "Your invitations have been sent" msgid "Your invitations have been sent"
msgstr "Twoje zaproszenia zostały wysłane" msgstr "Twoje zaproszenia zostały wysłane"
#, fuzzy
msgid "Sorry, there was an error while trying to send the invitation emails." msgid "Sorry, there was an error while trying to send the invitation emails."
msgstr "" msgstr ""
"Przepraszamy, wystąpił błąd podczas próby wysłania wiadomości e-mail z " "Przepraszamy, wystąpił błąd podczas próby wysłania wiadomości e-mail z "
"zaproszeniem." "zaproszeniem. Sprawdź konfigurację e-mail serwera lub skontaktuj się z "
"administratorem."
#, python-format #, python-format
msgid "%(member)s has been added" msgid "%(member)s has been added"
@ -358,7 +355,7 @@ msgstr "Rachunek został zmieniony"
#, python-format #, python-format
msgid "%(lang)s is not a supported language" msgid "%(lang)s is not a supported language"
msgstr "%(lang)s nie jest obsługowanym językiem" msgstr ""
msgid "Error deleting project history" msgid "Error deleting project history"
msgstr "Błąd podczas usuwania historii projektu" msgstr "Błąd podczas usuwania historii projektu"
@ -440,8 +437,9 @@ msgstr "Pobierz na"
msgid "Edit project" msgid "Edit project"
msgstr "Edytuj projekt" msgstr "Edytuj projekt"
#, fuzzy
msgid "Import project" msgid "Import project"
msgstr "Importuj projekt" msgstr "Edytuj projekt"
msgid "Download project's data" msgid "Download project's data"
msgstr "Pobierz dane projektu" msgstr "Pobierz dane projektu"
@ -468,13 +466,14 @@ msgid "Privacy Settings"
msgstr "Ustawienia prywatności" msgstr "Ustawienia prywatności"
msgid "Save changes" msgid "Save changes"
msgstr "Zapisz zmiany" msgstr ""
msgid "This will remove all bills and participants in this project!" msgid "This will remove all bills and participants in this project!"
msgstr "Spowoduje to usunięcie wszystkich rachunków i uczestników tego projektu!" msgstr "Spowoduje to usunięcie wszystkich rachunków i uczestników tego projektu!"
#, fuzzy
msgid "Import previously exported project" msgid "Import previously exported project"
msgstr "Zaimportuj wcześniej wyeksportowany projekt" msgstr "Zaimportuj wcześniej wyeksportowany plik JSON"
msgid "Choose file" msgid "Choose file"
msgstr "Wybierz plik" msgstr "Wybierz plik"
@ -486,7 +485,7 @@ msgid "Add a bill"
msgstr "Dodaj rachunek" msgstr "Dodaj rachunek"
msgid "Simple operations are allowed, e.g. (18+36.2)/3" msgid "Simple operations are allowed, e.g. (18+36.2)/3"
msgstr "Dozwolone są proste operacje, np. (18+36.2)/3" msgstr ""
msgid "Everyone" msgid "Everyone"
msgstr "Wszyscy" msgstr "Wszyscy"
@ -578,20 +577,19 @@ msgstr "Bill %(name)s: usunięto %(owers_list_str)s z listy właścicieli"
msgid "This project has history disabled. New actions won't appear below." msgid "This project has history disabled. New actions won't appear below."
msgstr "" msgstr ""
"Ten projekt ma wyłączoną historię. Nowe działania nie pojawią się poniżej."
#, fuzzy
msgid "You can enable history on the settings page." msgid "You can enable history on the settings page."
msgstr "Możliwe jest włączenie historii na stronie ustawień." msgstr "Rejestrowanie adresu IP można włączyć na stronie ustawień"
msgid "" msgid ""
"The table below reflects actions recorded prior to disabling project " "The table below reflects actions recorded prior to disabling project "
"history." "history."
msgstr "" msgstr ""
"Poniższa tabela przedstawia działania zarejestrowane przed wyłączeniem "
"historii projektu."
#, fuzzy
msgid "You can clear the project history to remove them." msgid "You can clear the project history to remove them."
msgstr "Możesz wyczyścić historię projektu aby ją usunąć." msgstr "Ktoś prawdopodobnie wyczyścił historię projektu."
msgid "" msgid ""
"Some entries below contain IP addresses, even though this project has IP " "Some entries below contain IP addresses, even though this project has IP "
@ -787,9 +785,6 @@ msgstr "Historia"
msgid "Settings" msgid "Settings"
msgstr "Ustawienia" msgstr "Ustawienia"
msgid "RSS Feed"
msgstr "Kanał RSS"
msgid "Other projects :" msgid "Other projects :"
msgstr "Inne projekty:" msgstr "Inne projekty:"
@ -801,7 +796,7 @@ msgstr "Kokpit"
#, python-format #, python-format
msgid "Please retry after %(date)s." msgid "Please retry after %(date)s."
msgstr "Prosimy spróbować ponownie po %(date)s." msgstr ""
msgid "Code" msgid "Code"
msgstr "Kod" msgstr "Kod"
@ -812,7 +807,7 @@ msgstr "Aplikacja mobilna"
msgid "Documentation" msgid "Documentation"
msgstr "Dokumentacja" msgstr "Dokumentacja"
msgid "Administration Dashboard" msgid "Administation Dashboard"
msgstr "Kokpit administracyjny" msgstr "Kokpit administracyjny"
msgid "Legal information" msgid "Legal information"
@ -866,11 +861,14 @@ msgstr "Brak rachunków"
msgid "Nothing to list yet." msgid "Nothing to list yet."
msgstr "Nie ma jeszcze żadnej listy." msgstr "Nie ma jeszcze żadnej listy."
msgid "Add your first bill" msgid "You probably want to"
msgstr "Dodaj swój pierwszy rachunek" msgstr "Prawdopodobnie chcesz"
msgid "Add the first participant" msgid "add a bill"
msgstr "Dodaj pierwszego uczestnika" msgstr "dodać rachunek"
msgid "add participants"
msgstr "dodać członków"
msgid "Password reminder" msgid "Password reminder"
msgstr "Przypomnienie hasła" msgstr "Przypomnienie hasła"
@ -894,61 +892,46 @@ msgstr "Zresetuj swoje hasło"
msgid "Invite people to join this project" msgid "Invite people to join this project"
msgstr "Zaproś ludzi do dołączenia do tego projektu" msgstr "Zaproś ludzi do dołączenia do tego projektu"
msgid "Share an invitation link"
msgstr "Udostępnij link z zaproszeniem"
msgid ""
"The easiest way to invite people is to give them the following invitation"
" link.<br />They will be able to access the project, manage participants,"
" add/edit/delete bills. However, they will not have access to important "
"settings such as changing the private code or deleting the whole project."
msgstr ""
"Najłatwiejszym sposobem zapraszania nowych osób jest podanie im poniższego "
"linku ze zaproszeniem.<br />Osoby te będą mogły uzyskać dostęp do projektu, "
"zarządzać uczestnikami, dodawać/edytować/usuwać rachunki. Nie będą jednak "
"mieli dostępu do ważnych ustawień, takich jak zmiana kodu prywatnego lub "
"usunięcie całego projektu."
msgid "Scan QR code"
msgstr "Skanuj kod QR"
msgid "Use a mobile device with a compatible app."
msgstr "Użyj urządzenia mobilnego ze zgodną aplikacją."
msgid "Send via Emails"
msgstr "Wyślij przez maile"
msgid ""
"Specify a list of email adresses (separated by comma) of people you want "
"to notify about the creation of this project. We will send them an email "
"with the invitation link."
msgstr ""
"Podaj listę adresów e-mail (oddzielonych przecinkami) osób, które chcesz "
"powiadomić o utworzeniu tego projektu. Wyślemy im wiadomość e-mail z linkiem "
"zaproszającym."
msgid "Share Identifier & code" msgid "Share Identifier & code"
msgstr "Udostępnij identyfikator i kod" msgstr "Udostępnij identyfikator i kod"
msgid "" msgid ""
"You can share the project identifier and the private code by any " "You can share the project identifier and the private code by any "
"communication means.<br />Anyone with the private code will have access " "communication means."
"to the full project, including changing settings such as the private code"
" or project email address, or even deleting the whole project."
msgstr "" msgstr ""
"Identyfikator projektu i kod prywatny można udostępniać za pomocą dowolnych " "Identyfikator projektu i kod prywatny można udostępniać dowolnymi "
"środków komunikacji.<br />Każda osoba posiadająca kod prywatny będzie miała " "środkami komunikacji."
"dostęp do całego projektu, w tym do zmiany ustawień, takich jak kod prywatny "
"lub adres e-mail projektu, a nawet do usunięcia całego projektu."
msgid "Identifier:" msgid "Identifier:"
msgstr "Identyfikator:" msgstr "Identyfikator:"
msgid "Private code:" msgid "Share the Link"
msgstr "Kod prywatny:" msgstr "Udostępnij link"
msgid "the private code was defined when you created the project" msgid "You can directly share the following link via your prefered medium"
msgstr "kod prywatny został zdefiniowany podczas tworzenia projektu" msgstr ""
"Możesz bezpośrednio udostępnić poniższy link za pośrednictwem "
"preferowanego medium"
msgid "Scan QR code"
msgstr ""
msgid "Use a mobile device with a compatible app."
msgstr ""
msgid "Send via Emails"
msgstr "Wyślij przez maile"
msgid ""
"Specify a (comma separated) list of email adresses you want to notify "
"about the\n"
" creation of this budget management project and we will "
"send them an email for you."
msgstr ""
"Podaj (adresy rozdzielone przecinkami) adresy email, które chcesz "
"powiadomić o \n"
" utworzeniu tego projektu zarządzania budżetem, a my "
"wyślemy Ci wiadomość email."
msgid "Who pays?" msgid "Who pays?"
msgstr "Kto płaci?" msgstr "Kto płaci?"
@ -1134,25 +1117,3 @@ msgstr "Okres"
#~ msgid "Edit the project" #~ msgid "Edit the project"
#~ msgstr "Edytuj projekt" #~ msgstr "Edytuj projekt"
#~ msgid "You probably want to"
#~ msgstr "Prawdopodobnie chcesz"
#~ msgid "add participants"
#~ msgstr "dodać członków"
#~ msgid ""
#~ "You can share the project identifier "
#~ "and the private code by any "
#~ "communication means."
#~ msgstr ""
#~ "Identyfikator projektu i kod prywatny "
#~ "można udostępniać dowolnymi środkami "
#~ "komunikacji."
#~ msgid "Share the Link"
#~ msgstr "Udostępnij link"
#~ msgid "You can directly share the following link via your prefered medium"
#~ msgstr ""
#~ "Możesz bezpośrednio udostępnić poniższy link"
#~ " za pośrednictwem preferowanego medium"

Binary file not shown.

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