Merge branch 'main' into prepare_6_1_2

This commit is contained in:
Alexis Métaireau 2024-12-27 00:06:12 +01:00 committed by GitHub
commit 268d7a1d82
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
66 changed files with 5002 additions and 1123 deletions

122
.github/workflows/ci.yml vendored Normal file
View file

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

View file

@ -1,106 +0,0 @@
name: Test & Docs
on:
push:
branches: [ 'master', 'stable-*' ]
pull_request:
branches: [ 'master', 'stable-*' ]
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", "3.12"]
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
- python-version: "3.12"
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: '**/pyproject.toml'
- name: Change dependencies to minimal supported versions
run: sed -i -e 's/>=/==/g; s/~=.*==\(.*\)/==\1/g; s/~=/==/g;' pyproject.toml
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.12'

View file

@ -9,7 +9,10 @@ This document describes changes between each past release.
- 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)

View file

@ -1,60 +1,40 @@
VIRTUALENV = python3 -m venv
SPHINX_BUILDDIR = docs/_build
VIRTUALENV = uv venv
VENV := $(shell realpath $${VIRTUAL_ENV-.venv})
PYTHON = $(VENV)/bin/python3
BIN := uv tool run
PIP := uv pip
PYTHON = $(BIN)/python3
DEV_STAMP = $(VENV)/.dev_env_installed.stamp
INSTALL_STAMP = $(VENV)/.install.stamp
TEMPDIR := $(shell mktemp -d)
ZOPFLIPNG := zopflipng
MAGICK_MOGRIFY := mogrify
.PHONY: all
all: install ## Alias for install
.PHONY: install
install: virtualenv pyproject.toml $(INSTALL_STAMP) ## Install dependencies
$(INSTALL_STAMP):
$(VENV)/bin/pip install -U pip
$(VENV)/bin/pip install -e .
touch $(INSTALL_STAMP)
.PHONY: virtualenv
virtualenv: $(PYTHON)
$(PYTHON):
$(VIRTUALENV) $(VENV)
.PHONY: install-dev
install-dev: virtualenv pyproject.toml $(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
serve: install build-translations ## Run the ihatemoney server
serve: build-translations ## Run the ihatemoney server
@echo 'Running ihatemoney on http://localhost:5000'
FLASK_DEBUG=1 FLASK_APP=ihatemoney.wsgi $(VENV)/bin/flask run --host=0.0.0.0
FLASK_DEBUG=1 FLASK_APP=ihatemoney.wsgi uv run flask run --host=0.0.0.0
.PHONY: test
test: install-dev ## Run the tests
$(VENV)/bin/tox
test:
uv run --extra dev --extra database pytest .
.PHONY: black
black: install-dev ## Run the tests
$(VENV)/bin/black --target-version=py37 .
.PHONY: lint
lint:
uv tool run ruff check .
uv tool run vermin --no-tips --violations -t=3.8- .
.PHONY: isort
isort: install-dev ## Run the tests
$(VENV)/bin/isort .
.PHONY: format
format:
uv tool run ruff format .
.PHONY: release
release: install-dev ## Release a new version (see https://ihatemoney.readthedocs.io/en/latest/contributing.html#how-to-release)
$(VENV)/bin/fullrelease
release: # Release a new version (see https://ihatemoney.readthedocs.io/en/latest/contributing.html#how-to-release)
uv run --extra dev fullreleas
.PHONY: compress-showcase
compress-showcase:
@ -72,27 +52,30 @@ compress-assets: compress-showcase ## Compress static assets
.PHONY: build-translations
build-translations: ## Build the translations
$(VENV)/bin/pybabel compile -d ihatemoney/translations
uv run --extra dev pybabel compile -d ihatemoney/translations
.PHONY: extract-translations
extract-translations: ## Extract new translations from source code
$(VENV)/bin/pybabel extract --add-comments "I18N:" --strip-comments --omit-header --no-location --mapping-file ihatemoney/babel.cfg -o ihatemoney/messages.pot ihatemoney
$(VENV)/bin/pybabel update -i ihatemoney/messages.pot -d ihatemoney/translations/
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
uv run --extra dev pybabel update -i ihatemoney/messages.pot -d ihatemoney/translations/
.PHONY: create-database-revision
create-database-revision: ## Create a new database revision
@read -p "Please enter a message describing this revision: " rev_message; \
$(PYTHON) -m ihatemoney.manage db migrate -d ihatemoney/migrations -m "$${rev_message}"
uv run python -m ihatemoney.manage db migrate -d ihatemoney/migrations -m "$${rev_message}"
.PHONY: create-empty-database-revision
create-empty-database-revision: ## Create an empty database revision
@read -p "Please enter a message describing this revision: " rev_message; \
$(PYTHON) -m ihatemoney.manage db revision -d ihatemoney/migrations -m "$${rev_message}"
uv run python -m ihatemoney.manage db revision -d ihatemoney/migrations -m "$${rev_message}"
.PHONY: clean
clean: ## Destroy the virtual environment
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
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}'

View file

@ -22,10 +22,41 @@ highly encouraged to do so.
## Requirements
- **Python**: version 3.7 to 3.12.
- **Python**: version 3.8 to 3.12.
- **Backends**: SQLite, PostgreSQL, MariaDB (version 10.3.2 or above),
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
Do you wish to contribute to IHateMoney? Fantastic! There's a lot of

View file

@ -173,6 +173,14 @@ URL you want.
- **Default value:** `""` (empty string)
- **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
By default, Ihatemoney sends emails using a local SMTP server, but it's

View file

@ -1,5 +1,37 @@
# 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
You would like to contribute? First, thanks a bunch! This project is a
@ -46,6 +78,15 @@ Thanks again!
(setup-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:
git clone https://github.com/spiral-project/ihatemoney.git
@ -151,7 +192,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.
To do so, just run:
make black isort
make lint
You can also integrate them with your dev environment (as a
*format-on-save* hook, for instance).

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
have Docker installed on your system, just issue :
docker run -d -p 8000:8000 ihatemoney/ihatemoney
docker run -d -p 8000:8000 ihatemoney/ihatemoney:latest
Ihatemoney is now available on <http://localhost:8000>.
@ -54,6 +54,10 @@ To enable the Admin dashboard, first generate a hashed password with:
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
command will print the hashed password string.
@ -62,12 +66,18 @@ Add these additional environment variables to the docker run invocation:
-e ACTIVATE_ADMIN_DASHBOARD=True \
-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`
parameter. For example, use the following command to add more gunicorn
workers:
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)=
## On a Cloud Provider
@ -83,7 +93,7 @@ Some Paas (Platform-as-a-Service), provide a documentation or even a quick insta
«Ihatemoney» depends on:
- **Python**: any version from 3.7 to 3.12 will work.
- **Python**: any version from 3.8 to 3.12 will work.
- **A database backend**: choose among SQLite, PostgreSQL, MariaDB (>=
10.3.2).
- **Virtual environment** (recommended): [python3-venv]{.title-ref}

View file

@ -3,6 +3,7 @@ DEBUG = SQLACHEMY_ECHO = False
SQLALCHEMY_DATABASE_URI = "sqlite:////tmp/ihatemoney.db"
SQLALCHEMY_TRACK_MODIFICATIONS = False
SECRET_KEY = "tralala"
SITE_NAME = "I Hate Money"
MAIL_DEFAULT_SENDER = "Budget manager <admin@example.com>"
SHOW_ADMIN_EMAIL = True
ACTIVATE_DEMO_PROJECT = True
@ -14,6 +15,7 @@ APPLICATION_ROOT = "/"
ENABLE_CAPTCHA = False
LEGAL_LINK = ""
SUPPORTED_LANGUAGES = [
"az",
"ca",
"cs",
"de",

View file

@ -90,7 +90,6 @@ def get_billform_for(project, set_default=True, **kwargs):
class CommaDecimalField(DecimalField):
"""A class to deal with comma in Decimal Field"""
def process_formdata(self, value):
@ -364,7 +363,12 @@ class BillForm(FlaskForm):
payed_for = SelectMultipleField(
_("For whom?"), validators=[DataRequired()], coerce=int
)
bill_type = SelectField(_("Bill Type"), choices=BillType.choices(), coerce=BillType, default=BillType.EXPENSE)
bill_type = SelectField(
_("Bill Type"),
choices=BillType.choices(),
coerce=BillType,
default=BillType.EXPENSE,
)
submit = SubmitField(_("Submit"))
submit2 = SubmitField(_("Submit and add a new one"))

View file

@ -38,6 +38,9 @@ def history_sort_key(history_item_dict):
def describe_version(version_obj):
"""Use the base model str() function to describe a version object"""
if not version_obj:
return ""
else:
return parent_class(type(version_obj)).__str__(version_obj)

View file

@ -4,6 +4,7 @@ import getpass
import os
import random
import sys
import datetime
import click
from flask.cli import FlaskGroup
@ -93,5 +94,31 @@ def delete_project(project_name):
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__":
cli()

View file

@ -759,7 +759,7 @@ msgstr ""
msgid "Documentation"
msgstr ""
msgid "Administation Dashboard"
msgid "Administration Dashboard"
msgstr ""
msgid "Legal information"

View file

@ -19,7 +19,10 @@ 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",
sa.Column("bill_type", billtype_enum, server_default=BillType.EXPENSE.name),
)
op.add_column("bill_version", sa.Column("bill_type", sa.UnicodeText()))

View file

@ -1,6 +1,6 @@
from collections import defaultdict
from enum import Enum
import datetime
from enum import Enum
import itertools
from dateutil.parser import parse
@ -22,7 +22,7 @@ from sqlalchemy_continuum.plugins import FlaskPlugin
from ihatemoney.currency_convertor import CurrencyConverter
from ihatemoney.monkeypath_continuum import PatchedTransactionFactory
from ihatemoney.utils import generate_password_hash, get_members, same_bill, FormEnum
from ihatemoney.utils import generate_password_hash, get_members, same_bill
from ihatemoney.versioning import (
ConditionalVersioningManager,
LoggingMode,
@ -51,13 +51,14 @@ make_versioned(
],
)
class BillType(Enum):
EXPENSE = "Expense"
REIMBURSEMENT = "Reimbursement"
@classmethod
def choices(cls):
return [(choice, choice.value) for choice in cls]
return [(choice.value, choice.value) for choice in cls]
db = SQLAlchemy()
@ -131,7 +132,9 @@ class Project(db.Model):
if bill.bill_type == BillType.EXPENSE:
should_receive[bill.payer.id] += bill.converted_amount
for ower in bill.owers:
should_pay[ower.id] += (ower.weight * bill.converted_amount / total_weight)
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
@ -206,7 +209,6 @@ class Project(db.Model):
)
return pretty_transactions
# cache value for better performance
members = {person.id: person for person in self.members}
settle_plan = settle(self.balance.items()) or []
@ -222,22 +224,6 @@ class Project(db.Model):
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):
"""return if the project do have bills or not"""
return self.get_bills_unordered().count() > 0
@ -563,7 +549,7 @@ class Project(db.Model):
("Alice", 20, ("Amina", "Alice"), "Beer !", "Expense"),
("Amina", 50, ("Amina", "Alice", "Georg"), "AMAP", "Expense"),
)
for (payer, amount, owers, what, bill_type) in operations:
for payer, amount, owers, what, bill_type in operations:
db.session.add(
Bill(
amount=amount,

View file

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

View file

@ -20,7 +20,7 @@
<!DOCTYPE html>
<html class="h-100">
<head>
<title>{{ _("Account manager") }}{% block title %}{% endblock %}</title>
<title>{{ SITE_NAME }} — {{ _("Account manager") }}{% block title %}{% endblock %}</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel=stylesheet type=text/css href="{{ url_for("static", filename='css/main.css') }}">
@ -168,7 +168,7 @@
<i class="icon book">{{ static_include("images/book.svg") | safe }}</i>
</a>
{% if g.show_admin_dashboard_link %}
<a target="_blank" rel="noopener" data-toggle="tooltip" data-placement="top" title="{{ _('Administation Dashboard') }}" href="{{ url_for('main.dashboard') }}">
<a target="_blank" rel="noopener" data-toggle="tooltip" data-placement="top" title="{{ _('Administration Dashboard') }}" href="{{ url_for('main.dashboard') }}">
<i class="icon admin">{{ static_include("images/cog.svg") | safe }}</i>
</a>
{% endif %}

View file

@ -9,7 +9,6 @@ from ihatemoney.tests.common.ihatemoney_testcase import IhatemoneyTestCase
class TestAPI(IhatemoneyTestCase):
"""Tests the API"""
def api_create(
@ -1018,7 +1017,6 @@ class TestAPI(IhatemoneyTestCase):
self.api_create("raclette")
self.api_add_member("raclette", "zorglub")
req = self.client.post(
"/api/projects/raclette/bills",
data={
@ -1029,7 +1027,7 @@ class TestAPI(IhatemoneyTestCase):
"bill_type": "wrong_bill_type",
"amount": "50",
},
headers=self.get_auth("raclette")
headers=self.get_auth("raclette"),
)
self.assertStatus(400, req)
@ -1044,7 +1042,7 @@ class TestAPI(IhatemoneyTestCase):
"bill_type": "Expense",
"amount": "50",
},
headers=self.get_auth("raclette")
headers=self.get_auth("raclette"),
)
self.assertStatus(201, req)
@ -1063,7 +1061,7 @@ class TestAPI(IhatemoneyTestCase):
"payed_for": ["1"],
"amount": "50",
},
headers=self.get_auth("raclette")
headers=self.get_auth("raclette"),
)
self.assertStatus(201, req)
@ -1076,4 +1074,3 @@ class TestAPI(IhatemoneyTestCase):
# Bill type should now be "Expense"
got = json.loads(req.data.decode("utf-8"))
assert got["bill_type"] == "Expense"

View file

@ -1,10 +1,9 @@
from collections import defaultdict
import datetime
from datetime import datetime, timedelta, date
import re
from urllib.parse import unquote, urlparse, urlunparse
from flask import session, url_for
from libfaketime import fake_time
import pytest
from werkzeug.security import check_password_hash
@ -239,7 +238,10 @@ class TestBudget(IhatemoneyTestCase):
url, data={"password": "pass", "password_confirmation": "pass"}
)
resp = self.login("raclette", password="pass")
assert "<title>Account manager - raclette</title>" in resp.data.decode("utf-8")
assert (
"<title>I Hate Money — Account manager - raclette</title>"
in resp.data.decode("utf-8")
)
# Test empty and null tokens
resp = self.client.get("/reset-password")
assert "No token provided" in resp.data.decode("utf-8")
@ -717,18 +719,6 @@ class TestBudget(IhatemoneyTestCase):
},
)
#transfer bill should not affect balances at all
self.client.post(
"/raclette/add",
data={
"date": "2011-08-10",
"what": "Transfer",
"payer": members_ids[1],
"payed_for": members_ids[0],
"bill_type": "Transfer",
"amount": "500",
},
)
balance = self.get_project("raclette").balance
assert set(balance.values()) == set([19.0, -19.0])
@ -842,122 +832,6 @@ class TestBudget(IhatemoneyTestCase):
alice_paid = self.get_project("rent").full_balance[2][members_ids[1]]
assert bob_paid == 500
assert alice_paid == 500
def test_transfer_bill(self):
self.post_project("random")
# add two participants
self.client.post("/random/members/add", data={"name": "zorglub"})
self.client.post("/random/members/add", data={"name": "fred"})
members_ids = [m.id for m in self.get_project("random").members]
self.client.post(
"/random/add",
data={
"date": "2022-10-10",
"what": "Rent",
"payer": members_ids[0], #zorglub
"payed_for": members_ids, #zorglub + fred
"bill_type": "Expense",
"amount": "1000",
},
)
# test transfer bill (should not affect anything whatsoever)
self.client.post(
"/random/add",
data={
"date": "2022-10-10",
"what": "Transfer of 500 to fred",
"payer": members_ids[0], #zorglub
"payed_for": members_ids[1], #fred
"bill_type": "Transfer",
"amount": "500",
},
)
balance = self.get_project("random").balance
assert set(balance.values()), set([500 == -500])
def test_reimbursement_bill(self):
self.post_project("rent")
# add two participants
self.client.post("/rent/members/add", data={"name": "bob"})
self.client.post("/rent/members/add", data={"name": "alice"})
members_ids = [m.id for m in self.get_project("rent").members]
# create a bill to test reimbursement
self.client.post(
"/rent/add",
data={
"date": "2022-12-12",
"what": "december rent",
"payer": members_ids[0], #bob
"payed_for": members_ids, #bob and alice
"bill_type": "Expense",
"amount": "1000",
},
)
#check balance
balance = self.get_project("rent").balance
assert set(balance.values()), set([500 == -500])
#check paid
bob_paid = self.get_project("rent").full_balance[2][members_ids[0]]
alice_paid = self.get_project("rent").full_balance[2][members_ids[1]]
assert bob_paid == 1000
assert alice_paid == 0
# test reimbursement bill
self.client.post(
"/rent/add",
data={
"date": "2022-12-13",
"what": "reimbursement for rent",
"payer": members_ids[1], #alice
"payed_for": members_ids[0], #bob
"bill_type": "Reimbursement",
"amount": "500",
},
)
balance = self.get_project("rent").balance
assert set(balance.values()), set([0 == 0])
#check paid
bob_paid = self.get_project("rent").full_balance[2][members_ids[0]]
alice_paid = self.get_project("rent").full_balance[2][members_ids[1]]
assert bob_paid == 500
assert alice_paid == 500
def test_transfer_bill(self):
self.post_project("random")
# add two participants
self.client.post("/random/members/add", data={"name": "zorglub"})
self.client.post("/random/members/add", data={"name": "fred"})
members_ids = [m.id for m in self.get_project("random").members]
self.client.post(
"/random/add",
data={
"date": "2022-10-10",
"what": "Rent",
"payer": members_ids[0], #zorglub
"payed_for": members_ids, #zorglub + fred
"bill_type": "Expense",
"amount": "1000",
},
)
# test transfer bill (should not affect anything whatsoever)
self.client.post(
"/random/add",
data={
"date": "2022-10-10",
"what": "Transfer of 500 to fred",
"payer": members_ids[0], #zorglub
"payed_for": members_ids[1], #fred
"bill_type": "Transfer",
"amount": "500",
},
)
balance = self.get_project("random").balance
assert set(balance.values()), set([500 == -500])
def test_weighted_balance(self):
self.post_project("raclette")
@ -1158,9 +1032,7 @@ class TestBudget(IhatemoneyTestCase):
assert """<thead>
<tr>
<th>Project</th>
<th>Number of participants</th>""" in resp.data.decode(
"utf-8"
)
<th>Number of participants</th>""" in resp.data.decode("utf-8")
def test_dashboard_project_deletion(self):
self.post_project("raclette")
@ -1274,7 +1146,7 @@ class TestBudget(IhatemoneyTestCase):
assert re.search(re.compile(regex2, re.DOTALL), response.data.decode("utf-8"))
# Check monthly expenses again: it should have a single month and the correct amount
august = datetime.date(year=2011, month=8, day=1)
august = date(year=2011, month=8, day=1)
assert project.active_months_range() == [august]
assert dict(project.monthly_stats[2011]) == {8: 40.0}
@ -1291,11 +1163,11 @@ class TestBudget(IhatemoneyTestCase):
},
)
months = [
datetime.date(year=2011, month=12, day=1),
datetime.date(year=2011, month=11, day=1),
datetime.date(year=2011, month=10, day=1),
datetime.date(year=2011, month=9, day=1),
datetime.date(year=2011, month=8, day=1),
date(year=2011, month=12, day=1),
date(year=2011, month=11, day=1),
date(year=2011, month=10, day=1),
date(year=2011, month=9, day=1),
date(year=2011, month=8, day=1),
]
amounts_2011 = {
12: 30.0,
@ -1348,7 +1220,7 @@ class TestBudget(IhatemoneyTestCase):
"amount": "20",
},
)
months.append(datetime.date(year=2011, month=7, day=1))
months.append(date(year=2011, month=7, day=1))
amounts_2011[7] = 20.0
assert project.active_months_range() == months
assert dict(project.monthly_stats[2011]) == amounts_2011
@ -1365,7 +1237,7 @@ class TestBudget(IhatemoneyTestCase):
"amount": "30",
},
)
months.insert(0, datetime.date(year=2012, month=1, day=1))
months.insert(0, date(year=2012, month=1, day=1))
amounts_2012 = {1: 30.0}
assert project.active_months_range() == months
assert dict(project.monthly_stats[2011]) == amounts_2011
@ -1486,7 +1358,15 @@ class TestBudget(IhatemoneyTestCase):
count = 0
for t in transactions:
count += 1
self.client.get("/raclette/settle"+"/"+str(t["amount"])+"/"+str(t["ower"].id)+"/"+str(t["receiver"].id))
self.client.get(
"/raclette/settle"
+ "/"
+ str(t["amount"])
+ "/"
+ str(t["ower"].id)
+ "/"
+ str(t["receiver"].id)
)
temp_transactions = project.get_transactions_to_settle_bill()
# test if the one has disappeared
assert len(temp_transactions) == len(transactions) - count
@ -1990,7 +1870,6 @@ class TestBudget(IhatemoneyTestCase):
"""
Tests that the RSS feed output content is expected.
"""
with fake_time("2023-07-25 12:00:00"):
self.post_project("raclette", default_currency="EUR")
self.client.post("/raclette/members/add", data={"name": "george"})
self.client.post("/raclette/members/add", data={"name": "peter"})
@ -2005,7 +1884,7 @@ class TestBudget(IhatemoneyTestCase):
"payed_for": [1, 2, 3],
"amount": "12",
"original_currency": "EUR",
"bill_type": "Expense"
"bill_type": "Expense",
},
)
self.client.post(
@ -2017,7 +1896,7 @@ class TestBudget(IhatemoneyTestCase):
"payed_for": [1, 2],
"amount": "15",
"original_currency": "EUR",
"bill_type": "Expense"
"bill_type": "Expense",
},
)
self.client.post(
@ -2029,7 +1908,7 @@ class TestBudget(IhatemoneyTestCase):
"payed_for": [1, 2],
"amount": "10",
"original_currency": "EUR",
"bill_type": "Expense"
"bill_type": "Expense",
},
)
@ -2037,12 +1916,10 @@ class TestBudget(IhatemoneyTestCase):
token = project.generate_token("feed")
resp = self.client.get(f"/raclette/feed/{token}.xml")
expected_rss_content = f"""<?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>
content = resp.data.decode()
assert (
f"""<channel>
<title>I Hate Money raclette</title>
<description>Latest bills from raclette</description>
<atom:link href="http://localhost/raclette/feed/{token}.xml" rel="self" type="application/rss+xml" />
@ -2052,32 +1929,18 @@ class TestBudget(IhatemoneyTestCase):
<guid isPermaLink="false">1</guid>
<dc:creator>george</dc:creator>
<description>December 31, 2016 - george, peter, steven : 4.00</description>
<pubDate>Tue, 25 Jul 2023 00:00:00 +0000</pubDate>
</item>
<item>
<title>charcuterie - 15.00</title>
<guid isPermaLink="false">2</guid>
<dc:creator>peter</dc:creator>
<description>December 30, 2016 - george, peter : 7.50</description>
<pubDate>Tue, 25 Jul 2023 00:00:00 +0000</pubDate>
</item>
<item>
<title>vin blanc - 10.00</title>
<guid isPermaLink="false">3</guid>
<dc:creator>peter</dc:creator>
<description>December 29, 2016 - george, peter : 5.00</description>
<pubDate>Tue, 25 Jul 2023 00:00:00 +0000</pubDate>
</item>
</channel>
</rss>""" # noqa: E221, E222, E231, E501
assert resp.data.decode() == expected_rss_content
"""
in content
)
assert """<title>charcuterie - €15.00</title>""" in content
assert """<title>vin blanc - €10.00</title>""" in content
def test_rss_feed_history_disabled(self):
"""
Tests that RSS feeds is correctly rendered even if the project
history is disabled.
"""
with fake_time("2023-07-25 12:00:00"):
self.post_project("raclette", default_currency="EUR", project_history=False)
self.client.post("/raclette/members/add", data={"name": "george"})
self.client.post("/raclette/members/add", data={"name": "peter"})
@ -2092,7 +1955,7 @@ class TestBudget(IhatemoneyTestCase):
"payed_for": [1, 2, 3],
"amount": "12",
"original_currency": "EUR",
"bill_type": "Expense"
"bill_type": "Expense",
},
)
self.client.post(
@ -2104,7 +1967,7 @@ class TestBudget(IhatemoneyTestCase):
"payed_for": [1, 2],
"amount": "15",
"original_currency": "EUR",
"bill_type": "Expense"
"bill_type": "Expense",
},
)
self.client.post(
@ -2116,7 +1979,7 @@ class TestBudget(IhatemoneyTestCase):
"payed_for": [1, 2],
"amount": "10",
"original_currency": "EUR",
"bill_type": "Expense"
"bill_type": "Expense",
},
)
@ -2124,44 +1987,12 @@ class TestBudget(IhatemoneyTestCase):
token = project.generate_token("feed")
resp = self.client.get(f"/raclette/feed/{token}.xml")
expected_rss_content = f"""<?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 raclette</title>
<description>Latest bills from raclette</description>
<atom:link href="http://localhost/raclette/feed/{token}.xml" rel="self" type="application/rss+xml" />
<link>http://localhost/raclette/</link>
<item>
<title>fromage à raclette - 12.00</title>
<guid isPermaLink="false">1</guid>
<dc:creator>george</dc:creator>
<description>December 31, 2016 - george, peter, steven : 4.00</description>
<pubDate>Tue, 25 Jul 2023 00:00:00 +0000</pubDate>
</item>
<item>
<title>charcuterie - 15.00</title>
<guid isPermaLink="false">2</guid>
<dc:creator>peter</dc:creator>
<description>December 30, 2016 - george, peter : 7.50</description>
<pubDate>Tue, 25 Jul 2023 00:00:00 +0000</pubDate>
</item>
<item>
<title>vin blanc - 10.00</title>
<guid isPermaLink="false">3</guid>
<dc:creator>peter</dc:creator>
<description>December 29, 2016 - george, peter : 5.00</description>
<pubDate>Tue, 25 Jul 2023 00:00:00 +0000</pubDate>
</item>
</channel>
</rss>""" # noqa: E221, E222, E231, E501
assert resp.data.decode() == expected_rss_content
content = resp.data.decode()
assert """<title>charcuterie - €15.00</title>""" in content
assert """<title>vin blanc - €10.00</title>""" in content
def test_rss_if_modified_since_header(self):
# Project creation
with fake_time("2023-07-26 13:00:00"):
self.post_project("raclette")
self.client.post("/raclette/members/add", data={"name": "george"})
project = self.get_project("raclette")
@ -2169,22 +2000,33 @@ class TestBudget(IhatemoneyTestCase):
resp = self.client.get(f"/raclette/feed/{token}.xml")
assert resp.status_code == 200
assert resp.headers.get("Last-Modified") == "Wed, 26 Jul 2023 13:00:00 UTC"
assert "Last-Modified" in resp.headers.keys()
last_modified = resp.headers.get("Last-Modified")
# Get a date 1 hour before the last modified date
before = datetime.strptime(
last_modified, "%a, %d %b %Y %H:%M:%S %Z"
) - timedelta(hours=1)
before_str = before.strftime("%a, %d %b %Y %H:%M:%S %Z")
resp = self.client.get(
f"/raclette/feed/{token}.xml",
headers={"If-Modified-Since": "Tue, 26 Jul 2023 12:00:00 UTC"},
headers={"If-Modified-Since": before_str},
)
assert resp.status_code == 200
after = datetime.strptime(
last_modified, "%a, %d %b %Y %H:%M:%S %Z"
) + timedelta(hours=1)
after_str = after.strftime("%a, %d %b %Y %H:%M:%S %Z")
resp = self.client.get(
f"/raclette/feed/{token}.xml",
headers={"If-Modified-Since": "Tue, 26 Jul 2023 14:00:00 UTC"},
headers={"If-Modified-Since": after_str},
)
assert resp.status_code == 304
# Add bill
with fake_time("2023-07-27 13:00:00"):
self.login("raclette")
resp = self.client.post(
"/raclette/add",
@ -2195,7 +2037,7 @@ class TestBudget(IhatemoneyTestCase):
"payed_for": [1],
"amount": "12",
"original_currency": "XXX",
"bill_type": "Expense"
"bill_type": "Expense",
},
follow_redirects=True,
)
@ -2204,38 +2046,34 @@ class TestBudget(IhatemoneyTestCase):
resp = self.client.get(
f"/raclette/feed/{token}.xml",
headers={"If-Modified-Since": "Tue, 27 Jul 2023 12:00:00 UTC"},
headers={"If-Modified-Since": before_str},
)
assert resp.headers.get("Last-Modified") == "Thu, 27 Jul 2023 13:00:00 UTC"
assert resp.status_code == 200
resp = self.client.get(
f"/raclette/feed/{token}.xml",
headers={"If-Modified-Since": "Tue, 27 Jul 2023 14:00:00 UTC"},
headers={"If-Modified-Since": after_str},
)
assert resp.status_code == 304
def test_rss_etag_headers(self):
# Project creation
with fake_time("2023-07-26 13:00:00"):
self.post_project("raclette")
self.client.post("/raclette/members/add", data={"name": "george"})
project = self.get_project("raclette")
token = project.generate_token("feed")
resp = self.client.get(f"/raclette/feed/{token}.xml")
assert resp.headers.get("ETag") == build_etag(
project.id, "2023-07-26T13:00:00"
)
etag = resp.headers.get("ETag")
assert resp.status_code == 200
resp = self.client.get(
f"/raclette/feed/{token}.xml",
headers={
"If-None-Match": build_etag(project.id, "2023-07-26T12:00:00"),
"If-None-Match": etag,
},
)
assert resp.status_code == 200
assert resp.status_code == 304
resp = self.client.get(
f"/raclette/feed/{token}.xml",
@ -2243,10 +2081,9 @@ class TestBudget(IhatemoneyTestCase):
"If-None-Match": build_etag(project.id, "2023-07-26T13:00:00"),
},
)
assert resp.status_code == 304
assert resp.status_code == 200
# Add bill
with fake_time("2023-07-27 13:00:00"):
self.login("raclette")
resp = self.client.post(
"/raclette/add",
@ -2263,20 +2100,19 @@ class TestBudget(IhatemoneyTestCase):
)
assert resp.status_code == 200
assert "The bill has been added" in resp.data.decode()
etag = resp.headers.get("ETag")
resp = self.client.get(
f"/raclette/feed/{token}.xml",
headers={
"If-None-Match": build_etag(project.id, "2023-07-27T12:00:00"),
},
headers={"If-None-Match": etag},
)
assert resp.headers.get("ETag") == build_etag(project.id, "2023-07-27T13:00:00")
assert resp.status_code == 200
new_etag = resp.headers.get("ETag")
resp = self.client.get(
f"/raclette/feed/{token}.xml",
headers={
"If-None-Match": build_etag(project.id, "2023-07-27T13:00:00"),
"If-None-Match": new_etag,
},
)
assert resp.status_code == 304
@ -2364,7 +2200,7 @@ class TestBudget(IhatemoneyTestCase):
"payer": members_ids[1],
"payed_for": members_ids,
"amount": "25",
"bill_type": "Expense"
"bill_type": "Expense",
},
)
@ -2382,7 +2218,7 @@ class TestBudget(IhatemoneyTestCase):
"payer": members_ids_tartif[2],
"payed_for": members_ids_tartif,
"amount": "24",
"bill_type": "Expense"
"bill_type": "Expense",
},
)
@ -2417,7 +2253,7 @@ class TestBudget(IhatemoneyTestCase):
"payer": members_ids[1],
"payed_for": members_ids[1:],
"amount": "25",
"bill_type": "Expense"
"bill_type": "Expense",
},
)

View file

@ -1,6 +1,7 @@
from unittest.mock import MagicMock
from flask import Flask
from jinja2 import FileSystemBytecodeCache
import pytest
from ihatemoney.babel_utils import compile_catalogs
@ -13,11 +14,19 @@ 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):
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

View file

@ -3,6 +3,7 @@
DEBUG = False
SQLALCHEMY_DATABASE_URI = 'sqlite:///budget.db'
SQLACHEMY_ECHO = DEBUG
SITE_NAME = "I Hate Money"
SECRET_KEY = "supersecret"

View file

@ -560,7 +560,7 @@ class TestExport(IhatemoneyTestCase):
},
{
"date": "2016-12-31",
"what": "\xe0 raclette",
"what": "fromage \xe0 raclette",
"bill_type": "Expense",
"amount": 10.0,
"currency": "EUR",

View file

@ -3,13 +3,17 @@ import smtplib
import socket
from unittest.mock import MagicMock, patch
import pytest
from sqlalchemy import orm
from werkzeug.security import check_password_hash
from ihatemoney import models
from ihatemoney.currency_convertor import CurrencyConverter
from ihatemoney.manage import delete_project, generate_config, password_hash
from ihatemoney.manage import (
delete_project,
generate_config,
get_project_count,
password_hash,
)
from ihatemoney.run import load_configuration
from ihatemoney.tests.common.ihatemoney_testcase import BaseTestCase, IhatemoneyTestCase
@ -229,6 +233,65 @@ class TestModels(IhatemoneyTestCase):
pay_each_expected = 10 / 3
assert 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):
def test_creation_email_failure_smtp(self):
@ -401,9 +464,7 @@ class TestCurrencyConverter:
def test_failing_remote(self):
rates = {}
with patch("requests.Response.json", new=lambda _: {}), pytest.warns(
UserWarning
):
with patch("requests.Response.json", new=lambda _: {}):
# we need a non-patched converter, but it seems that MagickMock
# is mocking EVERY instance of the class method. Too bad.
rates = CurrencyConverter.get_rates(self.converter)

File diff suppressed because it is too large Load diff

View file

@ -782,7 +782,7 @@ msgstr ""
msgid "Documentation"
msgstr ""
msgid "Administation Dashboard"
msgid "Administration Dashboard"
msgstr ""
msgid "Legal information"

View file

@ -785,7 +785,7 @@ msgstr ""
msgid "Documentation"
msgstr ""
msgid "Administation Dashboard"
msgid "Administration Dashboard"
msgstr ""
msgid "Legal information"

View file

@ -1,18 +1,18 @@
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-07-29 14:24+0200\n"
"PO-Revision-Date: 2022-09-12 15:25+0000\n"
"Last-Translator: Maite Guix <maite.guix@gmail.com>\n"
"PO-Revision-Date: 2024-07-03 19:09+0000\n"
"Last-Translator: Quentin PAGÈS <quentinantonin@free.fr>\n"
"Language-Team: Catalan <https://hosted.weblate.org/projects/i-hate-money/"
"i-hate-money/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"
"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"
"Generated-By: Babel 2.9.0\n"
#, python-format
@ -222,7 +222,7 @@ msgid "{start_object}, {next_object}"
msgstr "{start_object}, {next_object}"
msgid "No Currency"
msgstr "Sense moneda"
msgstr "Cap moneda"
#. Form error with only one error
msgid "{prefix}: {error}"
@ -826,7 +826,7 @@ msgstr "Aplicació mòbil"
msgid "Documentation"
msgstr "Documentació"
msgid "Administation Dashboard"
msgid "Administration Dashboard"
msgstr "Panell d'administració"
msgid "Legal information"
@ -1082,4 +1082,3 @@ msgstr "Període"
#~ msgstr ""
#~ "Pots compartir directament l'enllaç següent"
#~ " a través del teu mitjà preferit"

View file

@ -800,7 +800,7 @@ msgstr "Mobilní aplikace"
msgid "Documentation"
msgstr "Dokumentace"
msgid "Administation Dashboard"
msgid "Administration Dashboard"
msgstr "Správcovský panel"
msgid "Legal information"

View file

@ -824,7 +824,7 @@ msgstr "Handy-Applikation"
msgid "Documentation"
msgstr "Dokumentation"
msgid "Administation Dashboard"
msgid "Administration Dashboard"
msgstr "Dashboard Administration"
msgid "Legal information"

View file

@ -811,7 +811,7 @@ msgstr ""
msgid "Documentation"
msgstr ""
msgid "Administation Dashboard"
msgid "Administration Dashboard"
msgstr ""
#, fuzzy

View file

@ -821,7 +821,7 @@ msgstr "Poŝaparata programo"
msgid "Documentation"
msgstr "Dokumentaro"
msgid "Administation Dashboard"
msgid "Administration Dashboard"
msgstr "Administra panelo"
#, fuzzy

View file

@ -818,7 +818,7 @@ msgstr "Aplicación móvil"
msgid "Documentation"
msgstr "Documentación"
msgid "Administation Dashboard"
msgid "Administration Dashboard"
msgstr "Panel de administración"
msgid "Legal information"

View file

@ -815,7 +815,7 @@ msgstr "Aplicación móvil"
msgid "Documentation"
msgstr "Documentación"
msgid "Administation Dashboard"
msgid "Administration Dashboard"
msgstr "Panel de administración"
msgid "Legal information"

View file

@ -1,18 +1,18 @@
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-07-29 14:24+0200\n"
"PO-Revision-Date: 2023-03-19 21:40+0000\n"
"Last-Translator: Sai Mohammad-Hossein Emami <emami@outlook.com>\n"
"PO-Revision-Date: 2024-05-23 03:01+0000\n"
"Last-Translator: Yamin Siahmargooei <yamin8000@yahoo.com>\n"
"Language-Team: Persian <https://hosted.weblate.org/projects/i-hate-money/"
"i-hate-money/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"
"Content-Type: text/plain; charset=utf-8\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"
#, python-format
@ -125,16 +125,16 @@ msgid "Reset password"
msgstr "بازنشانی گذرواژه"
msgid "When?"
msgstr ""
msgstr "چه زمانی؟"
msgid "What?"
msgstr "چی؟"
msgid "Who paid?"
msgstr ""
msgstr "چه کسی پرداخت کرد؟"
msgid "How much?"
msgstr ""
msgstr "چقدر؟"
msgid "Currency"
msgstr "واحد پولی"
@ -180,14 +180,14 @@ msgid "People to notify"
msgstr "افرادی که براشون نوتیفیکیشن ارسال میشه"
msgid "Send the invitations"
msgstr ""
msgstr "ارسال دعوت نامه ها"
#, python-format
msgid "The email %(email)s is not valid"
msgstr "ایمیل %(email)s نامعتبره"
msgid "Logout"
msgstr ""
msgstr "خروج"
msgid "Please check the email configuration of the server."
msgstr ""
@ -782,7 +782,7 @@ msgstr ""
msgid "Documentation"
msgstr ""
msgid "Administation Dashboard"
msgid "Administration Dashboard"
msgstr ""
msgid "Legal information"
@ -1092,4 +1092,3 @@ msgstr ""
#~ "them an email with the invitation "
#~ "link."
#~ msgstr ""

View file

@ -824,7 +824,7 @@ msgstr "Application mobile"
msgid "Documentation"
msgstr "Documentation"
msgid "Administation Dashboard"
msgid "Administration Dashboard"
msgstr "Panneau d'administration"
msgid "Legal information"

View file

@ -788,7 +788,7 @@ msgstr "יישום לנייד"
msgid "Documentation"
msgstr "דוקומנטציה"
msgid "Administation Dashboard"
msgid "Administration Dashboard"
msgstr ""
msgid "Legal information"

View file

@ -829,7 +829,7 @@ msgstr "मोबाइल एप्लीकेशन"
msgid "Documentation"
msgstr "प्रलेखन"
msgid "Administation Dashboard"
msgid "Administration Dashboard"
msgstr "व्यवस्थापन डैशबोर्ड"
#, fuzzy

File diff suppressed because it is too large Load diff

View file

@ -812,7 +812,7 @@ msgstr "Aplikasi Gawai"
msgid "Documentation"
msgstr "Dokumentasi"
msgid "Administation Dashboard"
msgid "Administration Dashboard"
msgstr "Dasbor Administrasi"
msgid "Legal information"

View file

@ -817,7 +817,7 @@ msgstr "Applicazione mobile"
msgid "Documentation"
msgstr "Documentazione"
msgid "Administation Dashboard"
msgid "Administration Dashboard"
msgstr "Cruscotto Amministrazione"
msgid "Legal information"

View file

@ -1,18 +1,18 @@
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-07-29 14:24+0200\n"
"PO-Revision-Date: 2020-11-11 16:28+0000\n"
"Last-Translator: Jwen921 <yangjingwen0921@gmail.com>\n"
"PO-Revision-Date: 2024-06-24 15:09+0000\n"
"Last-Translator: Khang Tran <tranchikhang@outlook.com>\n"
"Language-Team: Japanese <https://hosted.weblate.org/projects/i-hate-money/"
"i-hate-money/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"
"Content-Type: text/plain; charset=utf-8\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"
#, python-format
@ -27,19 +27,17 @@ msgstr "無効な入力です。数字と「+ - * / 」の演算子しか入力
msgid "Project name"
msgstr "プロジェクトの名前"
#, fuzzy
msgid "Current private code"
msgstr "暗証コード"
msgstr "現在の暗証コード"
msgid "Enter existing private code to edit project"
msgstr ""
msgstr "プロジェクトを編集するために、暗証コードを入力してください"
#, fuzzy
msgid "New private code"
msgstr "暗証コード"
msgstr "暗証コード"
msgid "Enter a new code if you want to change it"
msgstr ""
msgstr "変更するために、新しい暗証コードを入力してください"
msgid "Email"
msgstr "メールアドレス"
@ -54,15 +52,13 @@ msgid "Default Currency"
msgstr "初期設定にする通貨"
msgid "Setting a default currency enables currency conversion between bills"
msgstr ""
msgstr "明細通貨変換のため、デフォルトの通貨を設定してください"
#, fuzzy
msgid "Unknown error"
msgstr "未知のプロジェクト"
msgstr "不明エラー"
#, fuzzy
msgid "Invalid private code."
msgstr "暗証コード"
msgstr "無効な暗証コード"
msgid ""
"This project cannot be set to 'no currency' because it contains bills in "
@ -179,7 +175,7 @@ msgid "This project already have this participant"
msgstr "プロジェクトはすでにこのメンバーを含めています"
msgid "People to notify"
msgstr ""
msgstr "通知したい人"
msgid "Send the invitations"
msgstr "招待状を送る"
@ -194,11 +190,12 @@ msgstr "ログアウト"
msgid "Please check the email configuration of the server."
msgstr ""
#, fuzzy, python-format
#, python-format
msgid ""
"Please check the email configuration of the server or contact the "
"administrator: %(admin_email)s"
msgstr "申し訳ございませんが、招待メールを送ったとき、エラーが発生しました。メールアドレスを再度チェックするかまたは管理者に連絡ください。"
msgstr "申し訳ございませんが、エラーが発生しました。メールアドレスを再度チェックする"
"か、または管理者( %(admin_email)sに連絡ください"
#. List with two items only
msgid "{dual_object_0} and {dual_object_1}"
@ -271,7 +268,7 @@ msgid "Project settings have been changed successfully."
msgstr ""
msgid "Unable to parse CSV"
msgstr ""
msgstr "CSVを読み込むことができません"
#, python-format
msgid "Missing attribute: %(attribute)s"
@ -292,7 +289,7 @@ msgid "Error deleting project"
msgstr ""
msgid "Unable to logout"
msgstr ""
msgstr "ログアウトできません"
#, python-format
msgid "You have been invited to share your expenses for %(project)s"
@ -353,9 +350,8 @@ msgstr ""
msgid "Error deleting project history"
msgstr "プロジェクトの歴史を有効にする"
#, fuzzy
msgid "Deleted project history."
msgstr "プロジェクトの歴史を有効にする"
msgstr "プロジェクトの歴史を削除しました。"
#, fuzzy
msgid "Error deleting recorded IP addresses"
@ -574,9 +570,8 @@ msgstr ""
msgid "This project has history disabled. New actions won't appear below."
msgstr ""
#, fuzzy
msgid "You can enable history on the settings page."
msgstr "設定ページでIPアドレス記録を編集可能にすることができる"
msgstr "設定ページでIPアドレス記録を編集可能にすることができる"
msgid ""
"The table below reflects actions recorded prior to disabling project "
@ -622,13 +617,13 @@ msgstr "設定ページでIPアドレス記録を編集不可にすることが
msgid "From IP"
msgstr "IPから"
#, fuzzy, python-format
#, python-format
msgid "Project %(name)s added"
msgstr "プロジェクトの名前"
msgstr "プロジェクト%(name)sが作成されました"
#, fuzzy, python-format
#, python-format
msgid "Bill %(name)s added"
msgstr "明細が追加されました"
msgstr "%(name)s明細が追加されました"
#, python-format
msgid "Participant %(name)s added"
@ -641,9 +636,9 @@ msgstr "プロジェクトの私用コードが変更された"
msgid "Project renamed to %(new_project_name)s"
msgstr "プロジェクト名は%(new_project_name)s"
#, fuzzy, python-format
#, python-format
msgid "Project contact email changed to %(new_email)s"
msgstr "プロジェクトの連絡メールが…に変更された"
msgstr "プロジェクトの連絡メールが%(new_email)sに変更されました"
msgid "Project settings modified"
msgstr "プロジェクトの設定が修正された"
@ -681,9 +676,9 @@ msgstr "日付"
msgid "Amount in %(currency)s"
msgstr "%(currency)sでの金額"
#, fuzzy, python-format
#, python-format
msgid "Bill %(name)s modified"
msgstr "明細が変更されました"
msgstr "%(name)s明細が変更されました"
#, python-format
msgid "Participant %(name)s modified"
@ -697,17 +692,17 @@ msgstr "ユーザー%(name)sが既に取り除かれました"
msgid "Participant %(name)s removed"
msgstr "ユーザー%(name)sが既に取り除かれました"
#, fuzzy, python-format
#, python-format
msgid "Project %(name)s changed in an unknown way"
msgstr "未知の方法で変更された"
msgstr "プロジェクト%(name)sが不明な方法で変更されました"
#, fuzzy, python-format
#, python-format
msgid "Bill %(name)s changed in an unknown way"
msgstr "未知の方法で変更された"
msgstr "明細%(name)sが不明な方法で変更されました"
#, fuzzy, python-format
#, python-format
msgid "Participant %(name)s changed in an unknown way"
msgstr "未知の方法で変更された"
msgstr "参加者%(name)sが不明な方法で変更されました"
msgid "Nothing to list"
msgstr "表示できるものがない"
@ -802,7 +797,7 @@ msgstr "携帯アプリ"
msgid "Documentation"
msgstr "書類"
msgid "Administation Dashboard"
msgid "Administration Dashboard"
msgstr "管理ダッシュボード"
#, fuzzy
@ -904,14 +899,12 @@ msgstr ""
msgid "Send via Emails"
msgstr "メールで送る"
#, fuzzy
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 ""
"…を知らせたいメールアドレスのリストを特定する(カンマ区切り)\n"
"彼らにこの予算管理プロジェクトの作成をメールでお知らせします。"
msgstr "知らせたいメールアドレスのリスト(カンマ区切り)を指定してください。メールで"
"招待リンクを送信します。"
msgid "Share Identifier & code"
msgstr "名前とコードを共有する"
@ -926,9 +919,8 @@ msgstr ""
msgid "Identifier:"
msgstr "名前:"
#, fuzzy
msgid "Private code:"
msgstr "暗証コード"
msgstr "暗証コード"
msgid "the private code was defined when you created the project"
msgstr ""
@ -1109,4 +1101,3 @@ msgstr "期間"
#~ msgid "You can directly share the following link via your prefered medium"
#~ msgstr "好きの手段で以下のリンクを直接に共有できる"

View file

@ -793,7 +793,7 @@ msgstr ""
msgid "Documentation"
msgstr ""
msgid "Administation Dashboard"
msgid "Administration Dashboard"
msgstr ""
msgid "Legal information"

View file

@ -793,7 +793,7 @@ msgstr ""
msgid "Documentation"
msgstr ""
msgid "Administation Dashboard"
msgid "Administration Dashboard"
msgstr ""
msgid "Legal information"

View file

@ -855,7 +855,7 @@ msgstr "Mobilprogram"
msgid "Documentation"
msgstr "Dokumentasjon"
msgid "Administation Dashboard"
msgid "Administration Dashboard"
msgstr "Administrasjonsoversiktspanel"
#, fuzzy

View file

@ -814,7 +814,7 @@ msgstr "Mobiele app"
msgid "Documentation"
msgstr "Documentatie"
msgid "Administation Dashboard"
msgid "Administration Dashboard"
msgstr "Administratie-overzicht"
#, fuzzy

View file

@ -0,0 +1,932 @@
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"

View file

@ -812,7 +812,7 @@ msgstr "Aplikacja mobilna"
msgid "Documentation"
msgstr "Dokumentacja"
msgid "Administation Dashboard"
msgid "Administration Dashboard"
msgstr "Kokpit administracyjny"
msgid "Legal information"

View file

@ -1,18 +1,18 @@
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-07-29 14:24+0200\n"
"PO-Revision-Date: 2023-05-05 00:47+0000\n"
"Last-Translator: MurkBRA <anjo1077@gmail.com>\n"
"PO-Revision-Date: 2024-05-10 07:26+0000\n"
"Last-Translator: Gesiane Pajarinen <gesianef@hotmail.com>\n"
"Language-Team: Portuguese <https://hosted.weblate.org/projects/i-hate-money/"
"i-hate-money/pt/>\n"
"Language: pt\n"
"Language-Team: Portuguese <https://hosted.weblate.org/projects/i-hate-"
"money/i-hate-money/pt/>\n"
"Plural-Forms: nplurals=2; plural=n > 1\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.5.4-rc\n"
"Generated-By: Babel 2.9.0\n"
#, python-format
@ -34,7 +34,7 @@ msgid "Current private code"
msgstr "Código privado novo"
msgid "Enter existing private code to edit project"
msgstr ""
msgstr "Insira o código privado existente para editar o projeto"
msgid "New private code"
msgstr "Código privado novo"
@ -71,7 +71,7 @@ msgstr ""
" em várias moedas."
msgid "Compatible with Cospend"
msgstr ""
msgstr "Compatível com Cospend"
msgid "Project identifier"
msgstr "Identificador do projeto"
@ -192,7 +192,7 @@ msgid "Logout"
msgstr "Sair"
msgid "Please check the email configuration of the server."
msgstr ""
msgstr "Verifique a configuração de e-mail do servidor."
#, fuzzy, python-format
msgid ""
@ -279,14 +279,14 @@ msgid "Password successfully reset."
msgstr "Palavra-passe redefinida corretamente."
msgid "Project settings have been changed successfully."
msgstr ""
msgstr "As configurações do projeto foram alteradas com sucesso."
msgid "Unable to parse CSV"
msgstr ""
msgstr "Não foi possível processar o CSV"
#, python-format
msgid "Missing attribute: %(attribute)s"
msgstr ""
msgstr "Faltando o atributo: %(attribute)s"
msgid ""
"Cannot add bills in multiple currencies to a project without default "
@ -305,7 +305,7 @@ msgid "Error deleting project"
msgstr "Erro ao apagar o projeto"
msgid "Unable to logout"
msgstr ""
msgstr "Não é possível sair"
#, python-format
msgid "You have been invited to share your expenses for %(project)s"
@ -365,7 +365,7 @@ msgstr "A fatura foi modificada"
#, python-format
msgid "%(lang)s is not a supported language"
msgstr ""
msgstr "Não temos configuração para o idioma %(lang)s"
msgid "Error deleting project history"
msgstr "Erro ao apagar o histórico do projeto"
@ -476,7 +476,7 @@ msgid "Privacy Settings"
msgstr "Configurações de Privacidade"
msgid "Save changes"
msgstr ""
msgstr "Salvar as alterações"
msgid "This will remove all bills and participants in this project!"
msgstr "Isso removerá todas as faturas e os participantes deste projeto!"
@ -495,7 +495,7 @@ msgid "Add a bill"
msgstr "Adicionar uma fatura"
msgid "Simple operations are allowed, e.g. (18+36.2)/3"
msgstr ""
msgstr "Operações simples são permitidas, por ex. (18+36,2)/3"
msgid "Everyone"
msgstr "Todos"
@ -587,6 +587,7 @@ msgstr "Fatura %(name)s: removido %(owers_list_str)s da lista de proprietários"
msgid "This project has history disabled. New actions won't appear below."
msgstr ""
"Este projeto tem o histórico desativado. Novas ações não aparecerão abaixo."
#, fuzzy
msgid "You can enable history on the settings page."
@ -596,6 +597,8 @@ msgid ""
"The table below reflects actions recorded prior to disabling project "
"history."
msgstr ""
"A tabela abaixo contém as ações registradas antes da desativação do "
"histórico do projeto."
#, fuzzy
msgid "You can clear the project history to remove them."
@ -796,7 +799,7 @@ msgid "Settings"
msgstr "Configurações"
msgid "RSS Feed"
msgstr ""
msgstr "RSS Feed"
msgid "Other projects :"
msgstr "Outros projetos:"
@ -809,7 +812,7 @@ msgstr "Painel de controle"
#, python-format
msgid "Please retry after %(date)s."
msgstr ""
msgstr "Por favor, tente novamente após %(date)s."
msgid "Code"
msgstr "Código"
@ -820,7 +823,7 @@ msgstr "Aplicação Mobile"
msgid "Documentation"
msgstr "Documentação"
msgid "Administation Dashboard"
msgid "Administration Dashboard"
msgstr "Painel de Administração"
msgid "Legal information"
@ -905,7 +908,7 @@ msgid "Invite people to join this project"
msgstr "Convide pessoas para participar deste projeto"
msgid "Share an invitation link"
msgstr ""
msgstr "Compartilhe um link do convite"
msgid ""
"The easiest way to invite people is to give them the following invitation"
@ -913,12 +916,17 @@ msgid ""
" 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 ""
"A maneira mais fácil de convidar as pessoas é compartilhando o seguinte link "
"do convite.<br />Eles poderão acessar o projeto, gerenciar participantes, "
"adicionar/editar/excluir contas. No entanto, eles não terão acesso a "
"configurações importantes, como alterar o código privado ou excluir todo o "
"projeto."
msgid "Scan QR code"
msgstr ""
msgstr "Escanear o código QR"
msgid "Use a mobile device with a compatible app."
msgstr ""
msgstr "Use um telefone celular ou tablet com um aplicativo compatível."
msgid "Send via Emails"
msgstr "Enviar via E-mails"
@ -943,6 +951,11 @@ msgid ""
"to the full project, including changing settings such as the private code"
" or project email address, or even deleting the whole project."
msgstr ""
"Você pode compartilhar o identificador do projeto e o código privado por "
"qualquer meio de comunicação.<br />Qualquer pessoa com o código privado terá "
"acesso a todo o projeto, incluindo a alteração de configurações como o "
"código privado ou o endereço do e-mail, ou até mesmo a exclusão de todo o "
"projeto."
msgid "Identifier:"
msgstr "Identificador:"
@ -952,7 +965,7 @@ msgid "Private code:"
msgstr "Código privado"
msgid "the private code was defined when you created the project"
msgstr ""
msgstr "o código privado foi definido quando você criou o projeto"
msgid "Who pays?"
msgstr "Quem paga?"
@ -1140,4 +1153,3 @@ msgstr "Período"
#~ msgstr ""
#~ "Pode compartilhar diretamente o seguinte "
#~ "ligação através do seu meio preferido"

View file

@ -1,18 +1,18 @@
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-07-29 14:24+0200\n"
"PO-Revision-Date: 2023-05-05 00:47+0000\n"
"Last-Translator: MurkBRA <anjo1077@gmail.com>\n"
"PO-Revision-Date: 2024-05-10 07:26+0000\n"
"Last-Translator: Gesiane Pajarinen <gesianef@hotmail.com>\n"
"Language-Team: Portuguese (Brazil) <https://hosted.weblate.org/projects/"
"i-hate-money/i-hate-money/pt_BR/>\n"
"Language: pt_BR\n"
"Language-Team: Portuguese (Brazil) <https://hosted.weblate.org/projects/i"
"-hate-money/i-hate-money/pt_BR/>\n"
"Plural-Forms: nplurals=2; plural=n > 1\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.5.4-rc\n"
"Generated-By: Babel 2.9.0\n"
#, python-format
@ -23,24 +23,23 @@ msgid ""
"Not a valid amount or expression. Only numbers and + - * / operators are "
"accepted."
msgstr ""
"Expressão ou montante inválido. Apenas números e os operadores +=*/ são "
"Expressão ou quantia inválida. Apenas números e os operadores +=*/ são "
"aceitos."
msgid "Project name"
msgstr "Nome do projeto"
#, fuzzy
msgid "Current private code"
msgstr "Código privado novo"
msgstr "Código privado atual"
msgid "Enter existing private code to edit project"
msgstr ""
msgstr "Insira o código privado para editar o projeto"
msgid "New private code"
msgstr "Código privado novo"
msgstr "Novo código privado"
msgid "Enter a new code if you want to change it"
msgstr "Insira um novo código se quiser alterá-lo"
msgstr "Digite um novo código se quiser mudá-lo"
msgid "Email"
msgstr "E-mail"
@ -67,11 +66,11 @@ msgid ""
"This project cannot be set to 'no currency' because it contains bills in "
"multiple currencies."
msgstr ""
"O projeto não pode ser definido como 'sem moeda' porque contém contas em "
"várias moedas."
"Esse projeto não pode ser configurado como 'sem moeda' porque ele tem "
"faturas em moedas diferentes."
msgid "Compatible with Cospend"
msgstr ""
msgstr "Compatível com Cospend"
msgid "Project identifier"
msgstr "Identificador do projeto"
@ -148,7 +147,7 @@ msgid "A link to an external document, related to this bill"
msgstr "Link para um documento externo, relacionado à essa conta"
msgid "For whom?"
msgstr "Para quem?"
msgstr "Por quem?"
msgid "Submit"
msgstr "Enviar"
@ -173,10 +172,10 @@ msgid "Add"
msgstr "Adicionar"
msgid "The participant name is invalid"
msgstr "'%(name)s' não é um usuário válido"
msgstr "O nome do participante não é válido"
msgid "This project already have this participant"
msgstr "'%(name)s' já está no projeto"
msgstr "Este participante já está no projeto"
msgid "People to notify"
msgstr "Pessoas para notificar"
@ -192,16 +191,15 @@ msgid "Logout"
msgstr "Sair"
msgid "Please check the email configuration of the server."
msgstr ""
msgstr "Verifique a configuração de e-mail do servidor."
#, fuzzy, python-format
#, python-format
msgid ""
"Please check the email configuration of the server or contact the "
"administrator: %(admin_email)s"
msgstr ""
"Desculpe, houve um erro ao enviar os convites via e-mail. Por favor, "
"confira a configuração de email do servidor ou entre em contato com um "
"administrador."
"Verifique a configuração de e-mail do servidor ou entre em contato com o "
"administrador: %(admin_email)s"
#. List with two items only
msgid "{dual_object_0} and {dual_object_1}"
@ -230,18 +228,17 @@ msgstr "{prefix}: {error}"
msgid "{prefix}:<br />{errors}"
msgstr "{prefix}:<br />{errors}"
#, fuzzy
msgid "Too many failed login attempts."
msgstr "Muitas tentativas de login falhas, por favor, tente novamente mais tarde."
msgstr "Muitas tentativas de login falhas."
#, python-format
msgid "This admin password is not the right one. Only %(num)d attempts left."
msgstr ""
"Esta senha de administrador não é a correta. Apenas %(num)d tentativas "
"Esta senha de administrador não está correta. Apenas %(num)d tentativas "
"restantes."
msgid "Provided token is invalid"
msgstr "Token invalido"
msgstr "O token fornecido é inválido"
msgid "This private code is not the right one"
msgstr "Este código privado não é o correto"
@ -253,17 +250,15 @@ msgid ""
"We tried to send you an reminder email, but there was an error. You can "
"still use the project normally."
msgstr ""
"Nós tentamos te enviar um email de lembrete, mas aconteceu um erro. Você "
"pode continuar usando o projeto normalmente."
"Tentamos te enviar um email de lembrete, mas aconteceu um erro. Você pode "
"continuar usando o projeto normalmente."
#, fuzzy
msgid ""
"Sorry, there was an error while sending you an email with password reset "
"instructions."
msgstr ""
"Desculpe, houve um erro ao te enviar um email com as instruções de "
"redefinição de senha. Por favor, confira a configuração de e-mail do "
"servidor ou entre em contato com um administrador."
"redefinição de senha."
msgid "No token provided"
msgstr "Nenhum token fornecido"
@ -275,37 +270,36 @@ msgid "Unknown project"
msgstr "Projeto desconhecido"
msgid "Password successfully reset."
msgstr "Senha redefinida corretamente."
msgstr "Senha redefinida com sucesso."
msgid "Project settings have been changed successfully."
msgstr ""
msgstr "As configurações do projeto foram alteradas com sucesso."
msgid "Unable to parse CSV"
msgstr ""
msgstr "Não foi possível processar o CSV"
#, python-format
msgid "Missing attribute: %(attribute)s"
msgstr ""
msgstr "Faltando o atributo: %(attribute)s"
#, fuzzy
msgid ""
"Cannot add bills in multiple currencies to a project without default "
"currency"
msgstr ""
"Não é possível adicionar contas em várias moedas a um projeto sem moeda "
"Não é possível adicionar contas em várias moedas em um projeto sem moeda "
"padrão"
msgid "Project successfully uploaded"
msgstr "Projeto enviado corretamente"
msgstr "Projeto enviado com sucesso"
msgid "Project successfully deleted"
msgstr "Projeto deletado com sucesso"
msgid "Error deleting project"
msgstr "Erro ao excluir o projeto"
msgstr "Erro ao deletar o projeto"
msgid "Unable to logout"
msgstr ""
msgstr "Não é possível sair"
#, python-format
msgid "You have been invited to share your expenses for %(project)s"
@ -314,76 +308,73 @@ msgstr "Você foi convidado a compartilhar suas despesas com %(project)s"
msgid "Your invitations have been sent"
msgstr "Seus convites foram enviados"
#, fuzzy
msgid "Sorry, there was an error while trying to send the invitation emails."
msgstr ""
"Desculpe, houve um erro ao enviar os convites via e-mail. Por favor, "
"confira a configuração de email do servidor ou entre em contato com um "
"administrador."
msgstr "Desculpe, houve um erro ao enviar os convites via e-mail."
#, python-format
msgid "%(member)s has been added"
msgstr "%(member)s foram adicionados"
msgstr "%(member)s foi adicionado(a)"
msgid "Error activating participant"
msgstr "Erro ao ativar usuário"
msgstr "Erro ao ativar participante"
#, python-format
msgid "%(name)s is part of this project again"
msgstr "%(name)s faz parte deste projeto novamente"
msgid "Error removing participant"
msgstr "Erro ao remover usuário"
msgstr "Erro ao remover participante"
#, python-format
msgid ""
"Participant '%(name)s' has been deactivated. It will still appear in the "
"list until its balance reach zero."
msgstr ""
"Usuário '%(name)s' foi desativado. Ele continuará aparecendo na lista de "
"usuários até que seu saldo seja zero."
"O participante '%(name)s' foi desativado. Ele continuará aparecendo na lista "
"de usuários até que seu saldo seja zero."
#, fuzzy, python-format
#, python-format
msgid "Participant '%(name)s' has been removed"
msgstr "Usuário '%(name)s' foi removido"
msgstr "Participante '%(name)s' foi removido(a)"
#, fuzzy, python-format
#, python-format
msgid "Participant '%(name)s' has been modified"
msgstr "Usuário '%(name)s' foi removido"
msgstr "Participante '%(name)s' foi alterado(a)"
msgid "The bill has been added"
msgstr "A conta foi adicionada"
msgid "Error deleting bill"
msgstr "Erro ao excluir conta"
msgstr "Erro ao excluir a conta"
msgid "The bill has been deleted"
msgstr "A conta foi deletada"
msgid "The bill has been modified"
msgstr "A conta foi modificada"
msgstr "A conta foi alterada"
#, python-format
msgid "%(lang)s is not a supported language"
msgstr ""
msgstr "Não temos configuração para o idioma %(lang)s"
msgid "Error deleting project history"
msgstr "Erro ao deletar o histórico do projeto"
msgid "Deleted project history."
msgstr "Histórico do projeto apagado."
msgstr "Histórico do projeto deletado."
msgid "Error deleting recorded IP addresses"
msgstr "Erro ao excluir endereços IP salvos"
msgstr "Erro ao deletar endereços IP salvos"
msgid "Deleted recorded IP addresses in project history."
msgstr "Endereços IP salvos no histórico de projeto deletados."
msgstr "Endereços de IP registrados deletados no histórico do projeto."
msgid "Sorry, we were unable to find the page you've asked for."
msgstr "Desculpe, não foi possível encontrar a página que você solicitou."
msgid "The best thing to do is probably to get back to the main page."
msgstr "É provável que a melhor coisa a fazer seja voltar para a página inicial."
msgstr ""
"Provavelmente, a melhor coisa a fazer seja voltar para a página inicial."
msgid "Back to the list"
msgstr "Voltar para a lista"
@ -398,7 +389,7 @@ msgid "The project you are trying to access do not exist, do you want to"
msgstr "O projeto que você está tentando acessar não existe, você quer"
msgid "create it"
msgstr "Criar"
msgstr "criar"
msgid "?"
msgstr "?"
@ -410,7 +401,7 @@ msgid "Project"
msgstr "Projeto"
msgid "Number of participants"
msgstr "Número de usuários"
msgstr "Número de participantes"
msgid "Number of bills"
msgstr "Número de contas"
@ -428,29 +419,28 @@ msgid "edit"
msgstr "editar"
msgid "Delete project"
msgstr "Excluir projeto"
msgstr "Deletar projeto"
msgid "show"
msgstr "exibir"
msgstr "mostrar"
msgid "The Dashboard is currently deactivated."
msgstr "O Painel de Controle atualmente está desativado."
msgid "Download Mobile Application"
msgstr "Baixar o APP"
msgstr "Baixar o aplicativo"
msgid "Get it on"
msgstr "Pegue agora"
msgstr "Comece"
msgid "Edit project"
msgstr "Editar projeto"
#, fuzzy
msgid "Import project"
msgstr "Editar projeto"
msgstr "Importar projeto"
msgid "Download project's data"
msgstr "Baixar dados do projeto"
msgstr "Baixar os dados do projeto"
msgid "Bill items"
msgstr "Itens da conta"
@ -474,14 +464,13 @@ msgid "Privacy Settings"
msgstr "Configurações de Privacidade"
msgid "Save changes"
msgstr ""
msgstr "Salvar as alterações"
msgid "This will remove all bills and participants in this project!"
msgstr "Isso vai remover todas as contas e participantes desse projeto!"
msgstr "Isso vai remover todas as faturas e participantes desse projeto!"
#, fuzzy
msgid "Import previously exported project"
msgstr "Importar arquivo JSON exportado anteriormente"
msgstr "Importar projeto que foi exportado anteriormente"
msgid "Choose file"
msgstr "Escolher arquivo"
@ -493,7 +482,7 @@ msgid "Add a bill"
msgstr "Adicionar uma conta"
msgid "Simple operations are allowed, e.g. (18+36.2)/3"
msgstr ""
msgstr "Operações simples são permitidas, por ex. (18+36,2)/3"
msgid "Everyone"
msgstr "Todos"
@ -508,7 +497,7 @@ msgid "Add participant"
msgstr "Adicionar participante"
msgid "Edit this participant"
msgstr "Editar usuário"
msgstr "Editar participante"
msgid "john.doe@example.com, mary.moe@site.com"
msgstr "john.doe@example.com, mary.moe@site.com"
@ -517,22 +506,22 @@ msgid "Download"
msgstr "Baixar"
msgid "Disabled Project History"
msgstr "Histórico do Projeto Desativado"
msgstr "Histórico de Projeto Desativado"
msgid "Disabled Project History & IP Address Recording"
msgstr "Histórico do Projeto Desativado & Gravação de Endereço IP"
msgstr "Histórico de Projeto Desativado & Registro de Endereços de IP"
msgid "Enabled Project History"
msgstr "Histórico do Projeto Ativado"
msgstr "Histórico de Projeto Ativado"
msgid "Disabled IP Address Recording"
msgstr "Gravação do Endereço IP Desativada"
msgstr "Registro de Endereços de IP Desativados"
msgid "Enabled Project History & IP Address Recording"
msgstr "Histórico do Projeto Ativado & Gravação do Endereço IP"
msgstr "Histórico de Projeto Ativado & Registro de Endereços de IP"
msgid "Enabled IP Address Recording"
msgstr "Gravação do Endereço IP Ativada"
msgstr "Registro de Endereços de IP Ativado"
msgid "History Settings Changed"
msgstr "Configurações do Histórico Alteradas"
@ -554,13 +543,13 @@ msgid ""
" The rest of the project history will be unaffected. This "
"action cannot be undone."
msgstr ""
"Você tem certeza que deseja deletar todos os endereços IP gravados deste "
"projeto?\n"
" O resto do histórico do projeto não será afetado. Esta "
"ação não pode ser desfeita."
"Tem certeza que deseja deletar todos os endereços IP gravados neste projeto?"
"\n"
" O resto do histórico do projeto não será afetado. Esta ação "
"não pode ser desfeita."
msgid "Confirm deletion"
msgstr "Confirmar exclusão"
msgstr "Confirmar a exclusão"
msgid "Close"
msgstr "Fechar"
@ -577,27 +566,28 @@ msgstr ""
#, python-format
msgid "Bill %(name)s: added %(owers_list_str)s to owers list"
msgstr "Conta %(name)s: adicionou %(owers_list_str)s a lista de devedores"
msgstr "Conta %(name)s: adicionados %(owers_list_str)s à lista de proprietários"
#, python-format
msgid "Bill %(name)s: removed %(owers_list_str)s from owers list"
msgstr "Conta %(name)s: removeu %(owers_list_str)s da lista de devedores"
msgstr "Conta %(name)s: removidos %(owers_list_str)s da lista de proprietários"
msgid "This project has history disabled. New actions won't appear below."
msgstr ""
"Este projeto tem o histórico desativado. Novas ações não aparecerão abaixo."
#, fuzzy
msgid "You can enable history on the settings page."
msgstr "A gravação do endereço IP pode ser ativada na página de configurações"
msgstr "Você pode ativar o histórico na página de configurações."
msgid ""
"The table below reflects actions recorded prior to disabling project "
"history."
msgstr ""
"A tabela abaixo contém as ações registradas antes da desativação do "
"histórico do projeto."
#, fuzzy
msgid "You can clear the project history to remove them."
msgstr "Alguém provavelmente limpou o histórico do projeto."
msgstr "Você pode limpar o histórico do projeto para removê-los."
msgid ""
"Some entries below contain IP addresses, even though this project has IP "
@ -613,7 +603,7 @@ msgid "No IP Addresses to erase"
msgstr "Não há endereços IP para apagar"
msgid "Delete Stored IP Addresses"
msgstr "Deletar endereços IP salvos"
msgstr "Deletar Endereços IP Salvos"
msgid "No history to erase"
msgstr "Não há histórico para apagar"
@ -622,7 +612,7 @@ msgid "Clear Project History"
msgstr "Limpar Histórico do Projeto"
msgid "Time"
msgstr "Tempo"
msgstr "Data e hora"
msgid "Event"
msgstr "Evento"
@ -634,7 +624,7 @@ msgid "IP address recording can be disabled on the settings page"
msgstr "A gravação do endereço IP pode ser desativada na página de configurações"
msgid "From IP"
msgstr "Do IP"
msgstr "IP"
#, python-format
msgid "Project %(name)s added"
@ -646,7 +636,7 @@ msgstr "Conta %(name)s adicionada"
#, python-format
msgid "Participant %(name)s added"
msgstr "Usuário %(name)s adicionado"
msgstr "Participante %(name)s adicionado(a)"
msgid "Project private code changed"
msgstr "Código privado do projeto alterado"
@ -664,15 +654,15 @@ msgstr "Configurações do projeto alteradas"
#, python-format
msgid "Participant %(name)s deactivated"
msgstr "Usuário %(name)s desativado"
msgstr "Participante %(name)s desativado"
#, python-format
msgid "Participant %(name)s reactivated"
msgstr "Usuário %(name)s reativado"
msgstr "Participante %(name)s reativado"
#, python-format
msgid "Participant %(name)s renamed to %(new_name)s"
msgstr "Usuário %(name)s renomeado para %(new_name)s"
msgstr "Participante %(name)s renomeado para %(new_name)s"
#, python-format
msgid "Bill %(name)s renamed to %(new_description)s"
@ -680,7 +670,8 @@ msgstr "Conta %(name)s renomeada para %(new_description)s"
#, python-format
msgid "Participant %(name)s: weight changed from %(old_weight)s to %(new_weight)s"
msgstr "Usuário %(name)s: a carga mudou de %(old_weight)s para %(new_weight)s"
msgstr ""
"Participante %(name)s: o peso mudou de %(old_weight)s para %(new_weight)s"
msgid "Payer"
msgstr "Pagador"
@ -697,40 +688,40 @@ msgstr "Quantia em %(currency)s"
#, python-format
msgid "Bill %(name)s modified"
msgstr "Conta %(name)s modificada"
msgstr "Conta %(name)s alterada"
#, python-format
msgid "Participant %(name)s modified"
msgstr "Usuário %(name)s modificado"
msgstr "Participante %(name)s foi alterado(a)"
#, python-format
msgid "Bill %(name)s removed"
msgstr "Conta '%(name)s' foi removida"
#, fuzzy, python-format
#, python-format
msgid "Participant %(name)s removed"
msgstr "Usuário '%(name)s' foi removido"
msgstr "Participante '%(name)s' foi removido"
#, python-format
msgid "Project %(name)s changed in an unknown way"
msgstr "Projeto %(name)s modificado de maneira desconhecida"
msgstr "Projeto %(name)s alterado de maneira desconhecida"
#, python-format
msgid "Bill %(name)s changed in an unknown way"
msgstr "Conta %(name)s modificado de maneira desconhecida"
msgstr "Conta %(name)s alterada de maneira desconhecida"
#, python-format
msgid "Participant %(name)s changed in an unknown way"
msgstr "Participante %(name)s modificado de maneira desconhecida"
msgstr "Participante %(name)s alterado(a) de maneira desconhecida"
msgid "Nothing to list"
msgstr "Nada a listar"
msgstr "Nada para listar"
msgid "Someone probably cleared the project history."
msgstr "Alguém provavelmente limpou o histórico do projeto."
msgid "Manage your shared <br />expenses, easily"
msgstr "Modifique suas despesas <br />compartilhadas, facilmente"
msgstr "Gerencie suas despesas <br /> compartilhadas com facilidade"
msgid "Try out the demo"
msgstr "Experimente a demonstração"
@ -748,13 +739,13 @@ msgid "We can help!"
msgstr "Nós podemos ajudar!"
msgid "Log in to an existing project"
msgstr "Conecte-se em um projeto existente"
msgstr "Faça login em um projeto existente"
msgid "Log in"
msgstr "Conecte-se"
msgstr "Login"
msgid "can't remember your password?"
msgstr "não consegue se lembrar da sua senha?"
msgstr "não lembra sua senha?"
msgid "Create"
msgstr "Criar"
@ -773,13 +764,13 @@ msgid "Bills"
msgstr "Contas"
msgid "Settle"
msgstr "Estabelecer"
msgstr "Pagamentos"
msgid "Statistics"
msgstr "Estatísticas"
msgid "Languages"
msgstr "Linguagens"
msgstr "Idiomas"
msgid "Projects"
msgstr "Projetos"
@ -794,7 +785,7 @@ msgid "Settings"
msgstr "Configurações"
msgid "RSS Feed"
msgstr ""
msgstr "Feed RSS"
msgid "Other projects :"
msgstr "Outros projetos:"
@ -807,18 +798,18 @@ msgstr "Painel de controle"
#, python-format
msgid "Please retry after %(date)s."
msgstr ""
msgstr "Por favor, tente novamente após %(date)s."
msgid "Code"
msgstr "Código"
msgid "Mobile Application"
msgstr "Aplicação Mobile"
msgstr "Aplicativo"
msgid "Documentation"
msgstr "Documentação"
msgid "Administation Dashboard"
msgid "Administration Dashboard"
msgstr "Painel de Administração"
msgid "Legal information"
@ -847,7 +838,7 @@ msgid "Older bills"
msgstr "Contas mais antigas"
msgid "You should start by adding participants"
msgstr "Você deveria começar adicionando pessoas"
msgstr "Comece adicionando participantes"
msgid "Add a new bill"
msgstr "Adicionar uma nova conta"
@ -872,13 +863,11 @@ msgstr "Sem contas"
msgid "Nothing to list yet."
msgstr "Nada para listar ainda."
#, fuzzy
msgid "Add your first bill"
msgstr "adicionar uma conta"
msgstr "Adicione sua primeira conta"
#, fuzzy
msgid "Add the first participant"
msgstr "Editar usuário"
msgstr "Adicione o primeiro participante"
msgid "Password reminder"
msgstr "Lembrete de senha"
@ -903,7 +892,7 @@ msgid "Invite people to join this project"
msgstr "Convide pessoas para participar deste projeto"
msgid "Share an invitation link"
msgstr ""
msgstr "Compartilhe um link do convite"
msgid ""
"The easiest way to invite people is to give them the following invitation"
@ -911,26 +900,29 @@ msgid ""
" 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 ""
"A maneira mais fácil de convidar as pessoas é enviando o seguinte link para "
"convite.<br />Eles poderão acessar o projeto, gerenciar participantes, "
"adicionar/editar/excluir contas. No entanto, eles não terão acesso às "
"configurações importantes, como alterar o código privado ou excluir todo o "
"projeto."
msgid "Scan QR code"
msgstr ""
msgstr "Escaneie o QR code"
msgid "Use a mobile device with a compatible app."
msgstr ""
msgstr "Use um telefone celular ou tablet com um aplicativo compatível."
msgid "Send via Emails"
msgstr "Enviar via E-mails"
#, fuzzy
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 ""
"Especifica uma lista de endereços de email (separados por vírgula) que "
"você quer notificar acerca da\n"
" criação deste projeto de gestão de saldo e nós iremos "
"enviar um email por si."
"Especifique uma lista de e-mails (separados por vírgula) das pessoas que "
"você deseja notificar sobre a criação deste projeto. Enviaremos um e-mail "
"para eles com o link do convite."
msgid "Share Identifier & code"
msgstr "Compartilhar Identificador & código"
@ -941,16 +933,19 @@ msgid ""
"to the full project, including changing settings such as the private code"
" or project email address, or even deleting the whole project."
msgstr ""
"Você pode compartilhar o identificador do projeto e o código privado por "
"qualquer meio de comunicação.<br />Qualquer pessoa com o código privado terá "
"acesso a todo o projeto e poderá alterar as configurações como o código "
"privado ou o e-mail, ou até mesmo a exclusão de todo o projeto."
msgid "Identifier:"
msgstr "Identificador:"
#, fuzzy
msgid "Private code:"
msgstr "Código privado"
msgstr "Código privado:"
msgid "the private code was defined when you created the project"
msgstr ""
msgstr "o código privado foi definido quando você criou o projeto"
msgid "Who pays?"
msgstr "Quem paga?"
@ -1140,4 +1135,3 @@ msgstr "Período"
#~ "Você pode compartilhar diretamente o "
#~ "seguinte link através do seu meio "
#~ "preferido"

View file

@ -816,7 +816,7 @@ msgstr "Мобильное приложение"
msgid "Documentation"
msgstr "Документация"
msgid "Administation Dashboard"
msgid "Administration Dashboard"
msgstr "Панель инструментов администратора"
msgid "Legal information"

View file

@ -783,7 +783,7 @@ msgstr "Mobilna Aplikacija"
msgid "Documentation"
msgstr "Dokumentacija"
msgid "Administation Dashboard"
msgid "Administration Dashboard"
msgstr ""
msgid "Legal information"

View file

@ -818,7 +818,7 @@ msgstr "Mobilapplikation"
msgid "Documentation"
msgstr "Dokumentation"
msgid "Administation Dashboard"
msgid "Administration Dashboard"
msgstr "Översiktspanel för administration"
msgid "Legal information"

View file

@ -809,7 +809,7 @@ msgstr ""
msgid "Documentation"
msgstr ""
msgid "Administation Dashboard"
msgid "Administration Dashboard"
msgstr ""
msgid "Legal information"

View file

@ -1,18 +1,18 @@
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-07-29 14:24+0200\n"
"PO-Revision-Date: 2022-10-04 14:19+0000\n"
"Last-Translator: Sharan J <sharanjs1999@gmail.com>\n"
"PO-Revision-Date: 2024-05-13 07:00+0000\n"
"Last-Translator: Harshini K <harshinikondepudi@gmail.com>\n"
"Language-Team: Telugu <https://hosted.weblate.org/projects/i-hate-money/"
"i-hate-money/te/>\n"
"Language: te\n"
"Language-Team: Telugu <https://hosted.weblate.org/projects/i-hate-money/i"
"-hate-money/te/>\n"
"Plural-Forms: nplurals=2; plural=n != 1\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.5.5-dev\n"
"Generated-By: Babel 2.9.0\n"
#, python-format
@ -35,7 +35,7 @@ msgid "Current private code"
msgstr "కొత్త ప్రైవేట్ కోడ్"
msgid "Enter existing private code to edit project"
msgstr ""
msgstr "ప్రాజెక్ట్‌ను సవరించడానికి ఇప్పటికీ ఉన్న ప్రైవేట్ కోడ్ ని ఎంటర్ చేయండి"
msgid "New private code"
msgstr "కొత్త ప్రైవేట్ కోడ్"
@ -75,7 +75,7 @@ msgstr ""
" చెయ్యడం కుదరదు."
msgid "Compatible with Cospend"
msgstr ""
msgstr "కాస్పెండ్ తో అనుకూలమైనది"
#, fuzzy
msgid "Project identifier"
@ -136,16 +136,16 @@ msgid "Reset password"
msgstr "పాస్ వర్డ్ రీసెట్ చేయి"
msgid "When?"
msgstr ""
msgstr "ఎప్పుడు?"
msgid "What?"
msgstr "ఏమిటి?"
msgid "Who paid?"
msgstr ""
msgstr "ఎవరు చెల్లించారు?"
msgid "How much?"
msgstr ""
msgstr "ఎంత?"
msgid "Currency"
msgstr "కరెన్సీ"
@ -817,7 +817,7 @@ msgstr ""
msgid "Documentation"
msgstr ""
msgid "Administation Dashboard"
msgid "Administration Dashboard"
msgstr ""
msgid "Legal information"
@ -1063,4 +1063,3 @@ msgstr ""
#~ "them an email with the invitation "
#~ "link."
#~ msgstr ""

View file

@ -778,7 +778,7 @@ msgstr ""
msgid "Documentation"
msgstr ""
msgid "Administation Dashboard"
msgid "Administration Dashboard"
msgstr ""
msgid "Legal information"

View file

@ -811,7 +811,7 @@ msgstr "Telefon Uygulaması"
msgid "Documentation"
msgstr "Belgelendirme"
msgid "Administation Dashboard"
msgid "Administration Dashboard"
msgstr "Yönetici Gösterge Paneli"
msgid "Legal information"

View file

@ -791,7 +791,7 @@ msgstr ""
msgid "Documentation"
msgstr ""
msgid "Administation Dashboard"
msgid "Administration Dashboard"
msgstr ""
msgid "Legal information"

View file

@ -776,7 +776,7 @@ msgstr ""
msgid "Documentation"
msgstr ""
msgid "Administation Dashboard"
msgid "Administration Dashboard"
msgstr ""
msgid "Legal information"

View file

@ -775,7 +775,7 @@ msgstr ""
msgid "Documentation"
msgstr ""
msgid "Administation Dashboard"
msgid "Administration Dashboard"
msgstr ""
msgid "Legal information"

View file

@ -779,7 +779,7 @@ msgstr "手机软件"
msgid "Documentation"
msgstr "文件"
msgid "Administation Dashboard"
msgid "Administration Dashboard"
msgstr "管理面板"
msgid "Legal information"

View file

@ -84,7 +84,6 @@ def flash_email_error(error_message, category="danger"):
class Redirect303(HTTPException, RoutingException):
"""Raise if the map requests a redirect. This is for example the case if
`strict_slashes` are activated and an url that requires a trailing slash.
@ -102,7 +101,6 @@ class Redirect303(HTTPException, RoutingException):
class PrefixedWSGI(object):
"""
Wrap the application in this middleware and configure the
front-end server to add these headers, to let you quietly bind

View file

@ -8,6 +8,7 @@ Basically, this blueprint takes care of the authentication and provides
some shortcuts to make your life better when coding (see `pull_project`
and `add_project_id` for a quick overview)
"""
import datetime
from functools import wraps
import hashlib
@ -136,6 +137,11 @@ def set_show_admin_dashboard_link(endpoint, values):
g.logout_form = LogoutForm()
@main.context_processor
def add_template_variables():
return {"SITE_NAME": current_app.config.get("SITE_NAME")}
@main.url_value_preprocessor
def pull_project(endpoint, values):
"""When a request contains a project_id value, transform it directly
@ -852,8 +858,6 @@ def settle_bill():
@main.route("/<project_id>/settle/<amount>/<int:ower_id>/<int:payer_id>")
def settle(amount, ower_id, payer_id):
# FIXME: Test this bill belongs to this project !
new_reinbursement = Bill(
amount=float(amount),
date=datetime.datetime.today(),
@ -868,6 +872,7 @@ def settle(amount, ower_id, payer_id):
db.session.add(new_reinbursement)
db.session.commit()
flash(_("Settlement bill has been successfully added"), category="success")
return redirect(url_for(".settle_bill"))

View file

@ -5,6 +5,7 @@ build-backend = "hatchling.build"
[project]
name = "ihatemoney"
version = "6.2.0.dev0"
requires-python = ">=3.8"
description = "A simple shared budget manager web application."
readme = "README.md"
license = {file = "LICENSE"}
@ -15,7 +16,6 @@ keywords = ["web", "budget"]
classifiers = [
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
@ -27,11 +27,12 @@ classifiers = [
dependencies = [
"blinker>=1.4,<2",
"cachetools>=4.1,<5",
"cachetools>=4.1,<6",
"debts>=0.5,<1",
"email_validator>=1.0,<3",
"Flask>=2,<4",
"Flask-Babel>=1.0,<4",
"Flask-Cors>=3.0.8,<4",
"Flask-Cors>=3.0.8,<6",
"Flask-Limiter>=2.6,<3",
"Flask-Mail>=0.9.1,<1",
"Flask-Migrate>=2.5.3,<5", # Not following semantic versioning (e.g. https://github.com/miguelgrinberg/flask-migrate/commit/1af28ba273de6c88544623b8dc02dd539340294b)
@ -39,41 +40,34 @@ dependencies = [
"Flask-SQLAlchemy>=2.4,<3",
"Flask-Talisman>=0.8,<2",
"Flask-WTF>=0.14.3,<2",
"WTForms>=2.3.1,<3.2",
"Flask>=2,<3",
"Werkzeug>=2,<3",
"itsdangerous>=2,<3",
"Jinja2>=3,<4",
"qrcode>=7.1,<8",
"requests>=2.25,<3",
"SQLAlchemy-Continuum>=1.3.12,<2",
"SQLAlchemy>=1.3.0,<1.5", # New 1.4 changes API, see #728
"python-dateutil",
]
"qrcode>=7.1,<9",
"requests>=2.25,<3",
"SQLAlchemy>=1.3.0,<1.5",
"SQLAlchemy-Continuum>=1.3.12,<2", # New 1.4 changes API, see #728
"Werkzeug>=2,<3",
"WTForms>=2.3.3,<3.3",]
[project.optional-dependencies]
database = [
# Python 3.11 support starts in 2.9.2
"psycopg2-binary>=2.9.2,<3",
"PyMySQL>=0.9,<1.1",
"psycopg2-binary>=2.9.2,<2.9.11",
"PyMySQL>=0.9,<1.2",
]
dev = [
"black==23.3.0",
"flake8==5.0.4",
"ruff==0.8.4",
"isort==5.11.5",
"vermin==1.5.2",
"vermin==1.6.0",
"pytest>=6.2.5",
"pytest-flask>=1.2.0",
"pytest-libfaketime>=0.1.2",
"tox>=3.14.6",
"zest.releaser>=6.20.1",
]
doc = [
"Sphinx>=7.0.1,<8",
"Sphinx>=7.0.1,<9",
"docutils==0.20.1",
"myst-parser>=2,<3",
"myst-parser>=2,<5",
]
[project.urls]
@ -88,7 +82,13 @@ ihatemoney = "ihatemoney.manage:cli"
[tool.hatch.build.hooks.custom]
dependencies = [
# Babel is needed to compile translations catalogs at package build time
"Babel>=2.13.1"
"Babel>=2.13.1",
# Babel 2.14 does not directly depend on setuptools
# https://github.com/python-babel/babel/blob/40e60a1f6cf178d9f57fcc14f157ea1b2ab77361/CHANGES.rst?plain=1#L22-L24
# and neither python 3.12 due to PEP 632
# https://peps.python.org/pep-0632/
"setuptools; python_version>='3.12'",
]
[tool.hatch.build]
@ -101,3 +101,7 @@ include = [
"README.md",
"SECURITY.md",
]
[tool.ruff]
exclude = ["ihatemoney/migrations"]

35
tox.ini
View file

@ -1,35 +0,0 @@
[tox]
isolated_build = true
envlist = py312,py311,py310,py39,py38,py37,lint_docs
skip_missing_interpreters = True
[testenv]
passenv = TESTING_SQLALCHEMY_DATABASE_URI
commands =
python --version
py.test --pyargs ihatemoney.tests {posargs}
deps =
-e.[database,dev]
# To be sure we are importing ihatemoney pkg from pip-installed version
changedir = /tmp
[testenv:lint_docs]
commands =
black --check --target-version=py37 .
isort --check --profile black .
flake8 ihatemoney
vermin --no-tips --violations -t=3.7- .
sphinx-build -a -n -b html -d docs/_build/doctrees docs docs/_build/html
deps =
-e.[dev,doc]
changedir = {toxinidir}
[flake8]
exclude = migrations
max_line_length = 100
extend-ignore =
# See https://github.com/PyCQA/pycodestyle/issues/373
E203,

1767
uv.lock Normal file

File diff suppressed because it is too large Load diff