Release 1.2.1: iCloud sync improvements + ASO multilingual metadata

iCloud sync:
- Force viewContext.refreshAllObjects() on remote change notifications so
  data from other devices is picked up immediately without app restart
- Call refreshFromCloudKit() on foreground to merge any changes made while
  the device was inactive
- Wait up to 10s for initial CloudKit sync on launch before showing onboarding
  (shows "Checking iCloud..." during the wait)
- New OnboardingICloudCheckView: shown on fresh installs with iCloud available,
  lets user restore from iCloud before starting onboarding from scratch

Localization:
- Added de, fr, it, ja, pt-BR lproj folders
- New iCloud onboarding strings in en + es-ES (+ button literals)

ASO metadata (fastlane):
- Updated en-US: new subtitle, keywords, description (fixed "no cloud sync"
  claim), promotional text, release notes
- Added full metadata for es-ES, de-DE, fr-FR, it, ja, pt-BR (63 files total)
- All keyword fields validated ≤100 Unicode chars

Infrastructure:
- Gemfile + Gemfile.lock for fastlane
- Scripts/archive_and_upload_appstore.sh for CI/CD

Version: 1.2.1 (build 7)

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
alexandrev-tibco
2026-03-25 11:54:35 +01:00
parent 7cb5f92cf4
commit 10f6d0ca20
108 changed files with 4069 additions and 238 deletions
+3
View File
@@ -0,0 +1,3 @@
source "https://rubygems.org"
gem "fastlane"
+338
View File
@@ -0,0 +1,338 @@
GEM
remote: https://rubygems.org/
specs:
CFPropertyList (3.0.8)
abbrev (0.1.2)
addressable (2.8.9)
public_suffix (>= 2.0.2, < 8.0)
artifactory (3.0.17)
atomos (0.1.3)
aws-eventstream (1.4.0)
aws-partitions (1.1229.0)
aws-sdk-core (3.244.0)
aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.992.0)
aws-sigv4 (~> 1.9)
base64
bigdecimal
jmespath (~> 1, >= 1.6.1)
logger
aws-sdk-kms (1.123.0)
aws-sdk-core (~> 3, >= 3.244.0)
aws-sigv4 (~> 1.5)
aws-sdk-s3 (1.217.0)
aws-sdk-core (~> 3, >= 3.244.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.5)
aws-sigv4 (1.12.1)
aws-eventstream (~> 1, >= 1.0.2)
babosa (1.0.4)
base64 (0.2.0)
benchmark (0.5.0)
bigdecimal (4.0.1)
claide (1.1.0)
colored (1.2)
colored2 (3.1.2)
commander (4.6.0)
highline (~> 2.0.0)
csv (3.3.5)
declarative (0.0.20)
digest-crc (0.7.0)
rake (>= 12.0.0, < 14.0.0)
domain_name (0.6.20240107)
dotenv (2.8.1)
emoji_regex (3.2.3)
excon (0.112.0)
faraday (1.10.5)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
faraday-excon (~> 1.1)
faraday-httpclient (~> 1.0)
faraday-multipart (~> 1.0)
faraday-net_http (~> 1.0)
faraday-net_http_persistent (~> 1.0)
faraday-patron (~> 1.0)
faraday-rack (~> 1.0)
faraday-retry (~> 1.0)
ruby2_keywords (>= 0.0.4)
faraday-cookie_jar (0.0.8)
faraday (>= 0.8.0)
http-cookie (>= 1.0.0)
faraday-em_http (1.0.0)
faraday-em_synchrony (1.0.1)
faraday-excon (1.1.0)
faraday-httpclient (1.0.1)
faraday-multipart (1.2.0)
multipart-post (~> 2.0)
faraday-net_http (1.0.2)
faraday-net_http_persistent (1.2.0)
faraday-patron (1.0.0)
faraday-rack (1.0.0)
faraday-retry (1.0.4)
faraday_middleware (1.2.1)
faraday (~> 1.0)
fastimage (2.4.1)
fastlane (2.232.2)
CFPropertyList (>= 2.3, < 4.0.0)
abbrev (~> 0.1.2)
addressable (>= 2.8, < 3.0.0)
artifactory (~> 3.0)
aws-sdk-s3 (~> 1.197)
babosa (>= 1.0.3, < 2.0.0)
base64 (~> 0.2.0)
benchmark (>= 0.1.0)
bundler (>= 1.17.3, < 5.0.0)
colored (~> 1.2)
commander (~> 4.6)
csv (~> 3.3)
dotenv (>= 2.1.1, < 3.0.0)
emoji_regex (>= 0.1, < 4.0)
excon (>= 0.71.0, < 1.0.0)
faraday (~> 1.0)
faraday-cookie_jar (~> 0.0.6)
faraday_middleware (~> 1.0)
fastimage (>= 2.1.0, < 3.0.0)
fastlane-sirp (>= 1.0.0)
gh_inspector (>= 1.1.2, < 2.0.0)
google-apis-androidpublisher_v3 (~> 0.3)
google-apis-playcustomapp_v1 (~> 0.1)
google-cloud-env (>= 1.6.0, <= 2.1.1)
google-cloud-storage (~> 1.31)
highline (~> 2.0)
http-cookie (~> 1.0.5)
json (< 3.0.0)
jwt (>= 2.1.0, < 3)
logger (>= 1.6, < 2.0)
mini_magick (>= 4.9.4, < 5.0.0)
multipart-post (>= 2.0.0, < 3.0.0)
mutex_m (~> 0.3.0)
naturally (~> 2.2)
nkf (~> 0.2.0)
optparse (>= 0.1.1, < 1.0.0)
ostruct (>= 0.1.0)
plist (>= 3.1.0, < 4.0.0)
rubyzip (>= 2.0.0, < 3.0.0)
security (= 0.1.5)
simctl (~> 1.6.3)
terminal-notifier (>= 2.0.0, < 3.0.0)
terminal-table (~> 3)
tty-screen (>= 0.6.3, < 1.0.0)
tty-spinner (>= 0.8.0, < 1.0.0)
word_wrap (~> 1.0.0)
xcodeproj (>= 1.13.0, < 2.0.0)
xcpretty (~> 0.4.1)
xcpretty-travis-formatter (>= 0.0.3, < 2.0.0)
fastlane-sirp (1.0.0)
sysrandom (~> 1.0)
gh_inspector (1.1.3)
google-apis-androidpublisher_v3 (0.97.0)
google-apis-core (>= 0.15.0, < 2.a)
google-apis-core (0.18.0)
addressable (~> 2.5, >= 2.5.1)
googleauth (~> 1.9)
httpclient (>= 2.8.3, < 3.a)
mini_mime (~> 1.0)
mutex_m
representable (~> 3.0)
retriable (>= 2.0, < 4.a)
google-apis-iamcredentials_v1 (0.26.0)
google-apis-core (>= 0.15.0, < 2.a)
google-apis-playcustomapp_v1 (0.17.0)
google-apis-core (>= 0.15.0, < 2.a)
google-apis-storage_v1 (0.61.0)
google-apis-core (>= 0.15.0, < 2.a)
google-cloud-core (1.8.0)
google-cloud-env (>= 1.0, < 3.a)
google-cloud-errors (~> 1.0)
google-cloud-env (2.1.1)
faraday (>= 1.0, < 3.a)
google-cloud-errors (1.6.0)
google-cloud-storage (1.58.0)
addressable (~> 2.8)
digest-crc (~> 0.4)
google-apis-core (>= 0.18, < 2)
google-apis-iamcredentials_v1 (~> 0.18)
google-apis-storage_v1 (>= 0.42)
google-cloud-core (~> 1.6)
googleauth (~> 1.9)
mini_mime (~> 1.0)
googleauth (1.11.2)
faraday (>= 1.0, < 3.a)
google-cloud-env (~> 2.1)
jwt (>= 1.4, < 3.0)
multi_json (~> 1.11)
os (>= 0.9, < 2.0)
signet (>= 0.16, < 2.a)
highline (2.0.3)
http-cookie (1.0.8)
domain_name (~> 0.5)
httpclient (2.9.0)
mutex_m
jmespath (1.6.2)
json (2.19.2)
jwt (2.10.2)
base64
logger (1.7.0)
mini_magick (4.13.2)
mini_mime (1.1.5)
multi_json (1.19.1)
multipart-post (2.4.1)
mutex_m (0.3.0)
nanaimo (0.4.0)
naturally (2.3.0)
nkf (0.2.0)
optparse (0.8.1)
os (1.1.4)
ostruct (0.6.3)
plist (3.7.2)
public_suffix (7.0.5)
rake (13.3.1)
representable (3.2.0)
declarative (< 0.1.0)
trailblazer-option (>= 0.1.1, < 0.2.0)
uber (< 0.2.0)
retriable (3.4.1)
rexml (3.4.4)
rouge (3.28.0)
ruby2_keywords (0.0.5)
rubyzip (2.4.1)
security (0.1.5)
signet (0.21.0)
addressable (~> 2.8)
faraday (>= 0.17.5, < 3.a)
jwt (>= 1.5, < 4.0)
multi_json (~> 1.10)
simctl (1.6.10)
CFPropertyList
naturally
sysrandom (1.0.5)
terminal-notifier (2.0.0)
terminal-table (3.0.2)
unicode-display_width (>= 1.1.1, < 3)
trailblazer-option (0.1.2)
tty-cursor (0.7.1)
tty-screen (0.8.2)
tty-spinner (0.9.3)
tty-cursor (~> 0.7)
uber (0.1.0)
unicode-display_width (2.6.0)
word_wrap (1.0.0)
xcodeproj (1.27.0)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)
colored2 (~> 3.1)
nanaimo (~> 0.4.0)
rexml (>= 3.3.6, < 4.0)
xcpretty (0.4.1)
rouge (~> 3.28.0)
xcpretty-travis-formatter (1.0.1)
xcpretty (~> 0.2, >= 0.0.7)
PLATFORMS
arm64-darwin-25
ruby
DEPENDENCIES
fastlane
CHECKSUMS
CFPropertyList (3.0.8) sha256=2c99d0d980536d3d7ab252f7bd59ac8be50fbdd1ff487c98c949bb66bb114261
abbrev (0.1.2) sha256=ad1b4eaaaed4cb722d5684d63949e4bde1d34f2a95e20db93aecfe7cbac74242
addressable (2.8.9) sha256=cc154fcbe689711808a43601dee7b980238ce54368d23e127421753e46895485
artifactory (3.0.17) sha256=3023d5c964c31674090d655a516f38ca75665c15084140c08b7f2841131af263
atomos (0.1.3) sha256=7d43b22f2454a36bace5532d30785b06de3711399cb1c6bf932573eda536789f
aws-eventstream (1.4.0) sha256=116bf85c436200d1060811e6f5d2d40c88f65448f2125bc77ffce5121e6e183b
aws-partitions (1.1229.0) sha256=4cdba3093cc518e1ffe9f0f35050953cb5fb4a79797e4c928ab1cd3ed369407b
aws-sdk-core (3.244.0) sha256=3e458c078b0c5bdee95bc370c3a483374b3224cf730c1f9f0faf849a5d9a18ea
aws-sdk-kms (1.123.0) sha256=d405f37e82f8fa32045ca8980be266c0b45b37aaf2012afe0254321a1e811f20
aws-sdk-s3 (1.217.0) sha256=6ea709272c666888b14e9c62345abd9a6a967759ae13667c28f01fde6823c24b
aws-sigv4 (1.12.1) sha256=6973ff95cb0fd0dc58ba26e90e9510a2219525d07620c8babeb70ef831826c00
babosa (1.0.4) sha256=18dea450f595462ed7cb80595abd76b2e535db8c91b350f6c4b3d73986c5bc99
base64 (0.2.0) sha256=0f25e9b21a02a0cc0cea8ef92b2041035d39350946e8789c562b2d1a3da01507
benchmark (0.5.0) sha256=465df122341aedcb81a2a24b4d3bd19b6c67c1530713fd533f3ff034e419236c
bigdecimal (4.0.1) sha256=8b07d3d065a9f921c80ceaea7c9d4ae596697295b584c296fe599dd0ad01c4a7
claide (1.1.0) sha256=6d3c5c089dde904d96aa30e73306d0d4bd444b1accb9b3125ce14a3c0183f82e
colored (1.2) sha256=9d82b47ac589ce7f6cab64b1f194a2009e9fd00c326a5357321f44afab2c1d2c
colored2 (3.1.2) sha256=b13c2bd7eeae2cf7356a62501d398e72fde78780bd26aec6a979578293c28b4a
commander (4.6.0) sha256=7d1ddc3fccae60cc906b4131b916107e2ef0108858f485fdda30610c0f2913d9
csv (3.3.5) sha256=6e5134ac3383ef728b7f02725d9872934f523cb40b961479f69cf3afa6c8e73f
declarative (0.0.20) sha256=8021dd6cb17ab2b61233c56903d3f5a259c5cf43c80ff332d447d395b17d9ff9
digest-crc (0.7.0) sha256=64adc23a26a241044cbe6732477ca1b3c281d79e2240bcff275a37a5a0d78c07
domain_name (0.6.20240107) sha256=5f693b2215708476517479bf2b3802e49068ad82167bcd2286f899536a17d933
dotenv (2.8.1) sha256=c5944793349ae03c432e1780a2ca929d60b88c7d14d52d630db0508c3a8a17d8
emoji_regex (3.2.3) sha256=ecd8be856b7691406c6bf3bb3a5e55d6ed683ffab98b4aa531bb90e1ddcc564b
excon (0.112.0) sha256=daf9ac3a4c2fc9aa48383a33da77ecb44fa395111e973084d5c52f6f214ae0f0
faraday (1.10.5) sha256=b144f1d2b045652fa820b5f532723e1643cc28b93dae911d784e5c5f88e8f6ed
faraday-cookie_jar (0.0.8) sha256=0140605823f8cc63c7028fccee486aaed8e54835c360cffc1f7c8c07c4299dbb
faraday-em_http (1.0.0) sha256=7a3d4c7079789121054f57e08cd4ef7e40ad1549b63101f38c7093a9d6c59689
faraday-em_synchrony (1.0.1) sha256=bf3ce45dcf543088d319ab051f80985ea6d294930635b7a0b966563179f81750
faraday-excon (1.1.0) sha256=b055c842376734d7f74350fe8611542ae2000c5387348d9ba9708109d6e40940
faraday-httpclient (1.0.1) sha256=4c8ff1f0973ff835be8d043ef16aaf54f47f25b7578f6d916deee8399a04d33b
faraday-multipart (1.2.0) sha256=7d89a949693714176f612323ca13746a2ded204031a6ba528adee788694ef757
faraday-net_http (1.0.2) sha256=63992efea42c925a20818cf3c0830947948541fdcf345842755510d266e4c682
faraday-net_http_persistent (1.2.0) sha256=0b0cbc8f03dab943c3e1cc58d8b7beb142d9df068b39c718cd83e39260348335
faraday-patron (1.0.0) sha256=dc2cd7b340bb3cc8e36bcb9e6e7eff43d134b6d526d5f3429c7a7680ddd38fa7
faraday-rack (1.0.0) sha256=ef60ec969a2bb95b8dbf24400155aee64a00fc8ba6c6a4d3968562bcc92328c0
faraday-retry (1.0.4) sha256=dc659233777fabf96c69c2ffe56c0a5d2c102af90321a42cc6c90157bcd716aa
faraday_middleware (1.2.1) sha256=d45b78c8ee864c4783fbc276f845243d4a7918a67301c052647bacabec0529e9
fastimage (2.4.1) sha256=c64bebd46b6fd8943ab70c1e6e85ff728f970f2e48f92ecd249b6bc3a540ad20
fastlane (2.232.2) sha256=978689f60f0fc3d54699de86ef12be4eda9f5b52217c1798965257c390d2b112
fastlane-sirp (1.0.0) sha256=66478f25bcd039ec02ccf65625373fca29646fa73d655eb533c915f106c5e641
gh_inspector (1.1.3) sha256=04cca7171b87164e053aa43147971d3b7f500fcb58177698886b48a9fc4a1939
google-apis-androidpublisher_v3 (0.97.0) sha256=0f3859844872ec09b64dde3bff6dee84458eb61d664337402adcbb4ac912322a
google-apis-core (0.18.0) sha256=96b057816feeeab448139ed5b5c78eab7fc2a9d8958f0fbc8217dedffad054ee
google-apis-iamcredentials_v1 (0.26.0) sha256=3ff70a10a1d6cddf2554e95b7c5df2c26afdeaeb64100048a355194da19e48a3
google-apis-playcustomapp_v1 (0.17.0) sha256=d5bc90b705f3f862bab4998086449b0abe704ee1685a84821daa90ca7fa95a78
google-apis-storage_v1 (0.61.0) sha256=b330e599b58e6a01533c189525398d6dbdbaf101ffb0c60145940b57e1c982e8
google-cloud-core (1.8.0) sha256=e572edcbf189cfcab16590628a516cec3f4f63454b730e59f0b36575120281cf
google-cloud-env (2.1.1) sha256=cf4bb8c7d517ee1ea692baedf06e0b56ce68007549d8d5a66481aa9f97f46999
google-cloud-errors (1.6.0) sha256=1da8476dd706ad04b9d32e3c4b90d07d3463b37d6407cb56d41342ea7647d0a1
google-cloud-storage (1.58.0) sha256=1bedc07a9c75af169e1ede1dd306b9f941f9ffa9e7095d0364c0803c468fdffd
googleauth (1.11.2) sha256=7e6bacaeed7aea3dd66dcea985266839816af6633e9f5983c3c2e0e40a44731e
highline (2.0.3) sha256=2ddd5c127d4692721486f91737307236fe005352d12a4202e26c48614f719479
http-cookie (1.0.8) sha256=b14fe0445cf24bf9ae098633e9b8d42e4c07c3c1f700672b09fbfe32ffd41aa6
httpclient (2.9.0) sha256=4b645958e494b2f86c2f8a2f304c959baa273a310e77a2931ddb986d83e498c8
jmespath (1.6.2) sha256=238d774a58723d6c090494c8879b5e9918c19485f7e840f2c1c7532cf84ebcb1
json (2.19.2) sha256=e7e1bd318b2c37c4ceee2444841c86539bc462e81f40d134cf97826cb14e83cf
jwt (2.10.2) sha256=31e1ee46f7359883d5e622446969fe9c118c3da87a0b1dca765ce269c3a0c4f4
logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203
mini_magick (4.13.2) sha256=71d6258e0e8a3d04a9a0a09784d5d857b403a198a51dd4f882510435eb95ddd9
mini_mime (1.1.5) sha256=8681b7e2e4215f2a159f9400b5816d85e9d8c6c6b491e96a12797e798f8bccef
multi_json (1.19.1) sha256=7aefeff8f2c854bf739931a238e4aea64592845e0c0395c8a7d2eea7fdd631b7
multipart-post (2.4.1) sha256=9872d03a8e552020ca096adadbf5e3cb1cd1cdd6acd3c161136b8a5737cdb4a8
mutex_m (0.3.0) sha256=cfcb04ac16b69c4813777022fdceda24e9f798e48092a2b817eb4c0a782b0751
nanaimo (0.4.0) sha256=faf069551bab17f15169c1f74a1c73c220657e71b6e900919897a10d991d0723
naturally (2.3.0) sha256=459923cf76c2e6613048301742363200c3c7e4904c324097d54a67401e179e01
nkf (0.2.0) sha256=fbc151bda025451f627fafdfcb3f4f13d0b22ae11f58c6d3a2939c76c5f5f126
optparse (0.8.1) sha256=42bea10d53907ccff4f080a69991441d611fbf8733b60ed1ce9ee365ce03bd1a
os (1.1.4) sha256=57816d6a334e7bd6aed048f4b0308226c5fb027433b67d90a9ab435f35108d3f
ostruct (0.6.3) sha256=95a2ed4a4bd1d190784e666b47b2d3f078e4a9efda2fccf18f84ddc6538ed912
plist (3.7.2) sha256=d37a4527cc1116064393df4b40e1dbbc94c65fa9ca2eec52edf9a13616718a42
public_suffix (7.0.5) sha256=1a8bb08f1bbea19228d3bed6e5ed908d1cb4f7c2726d18bd9cadf60bc676f623
rake (13.3.1) sha256=8c9e89d09f66a26a01264e7e3480ec0607f0c497a861ef16063604b1b08eb19c
representable (3.2.0) sha256=cc29bf7eebc31653586849371a43ffe36c60b54b0a6365b5f7d95ec34d1ebace
retriable (3.4.1) sha256=fb3f114b7d492121c158c01f3d5152b5a615c5b70d5877d0bc08c7ec3725c3bc
rexml (3.4.4) sha256=19e0a2c3425dfbf2d4fc1189747bdb2f849b6c5e74180401b15734bc97b5d142
rouge (3.28.0) sha256=0d6de482c7624000d92697772ab14e48dca35629f8ddf3f4b21c99183fd70e20
ruby2_keywords (0.0.5) sha256=ffd13740c573b7301cf7a2e61fc857b2a8e3d3aff32545d6f8300d8bae10e3ef
rubyzip (2.4.1) sha256=8577c88edc1fde8935eb91064c5cb1aef9ad5494b940cf19c775ee833e075615
security (0.1.5) sha256=3a977a0eca7706e804c96db0dd9619e0a94969fe3aac9680fcfc2bf9b8a833b7
signet (0.21.0) sha256=d617e9fbf24928280d39dcfefba9a0372d1c38187ffffd0a9283957a10a8cd5b
simctl (1.6.10) sha256=b99077f4d13ad81eace9f86bf5ba4df1b0b893a4d1b368bd3ed59b5b27f9236b
sysrandom (1.0.5) sha256=5ac1ac3c2ec64ef76ac91018059f541b7e8f437fbda1ccddb4f2c56a9ccf1e75
terminal-notifier (2.0.0) sha256=7a0d2b2212ab9835c07f4b2e22a94cff64149dba1eed203c04835f7991078cea
terminal-table (3.0.2) sha256=f951b6af5f3e00203fb290a669e0a85c5dd5b051b3b023392ccfd67ba5abae91
trailblazer-option (0.1.2) sha256=20e4f12ea4e1f718c8007e7944ca21a329eee4eed9e0fa5dde6e8ad8ac4344a3
tty-cursor (0.7.1) sha256=79534185e6a777888d88628b14b6a1fdf5154a603f285f80b1753e1908e0bf48
tty-screen (0.8.2) sha256=c090652115beae764336c28802d633f204fb84da93c6a968aa5d8e319e819b50
tty-spinner (0.9.3) sha256=0e036f047b4ffb61f2aa45f5a770ec00b4d04130531558a94bfc5b192b570542
uber (0.1.0) sha256=5beeb407ff807b5db994f82fa9ee07cfceaa561dad8af20be880bc67eba935dc
unicode-display_width (2.6.0) sha256=12279874bba6d5e4d2728cef814b19197dbb10d7a7837a869bab65da943b7f5a
word_wrap (1.0.0) sha256=f556d4224c812e371000f12a6ee8102e0daa724a314c3f246afaad76d82accc7
xcodeproj (1.27.0) sha256=8cc7a73b4505c227deab044dce118ede787041c702bc47636856a2e566f854d3
xcpretty (0.4.1) sha256=b14c50e721f6589ee3d6f5353e2c2cfcd8541fa1ea16d6c602807dd7327f3892
xcpretty-travis-formatter (1.0.1) sha256=aacc332f17cb7b2cba222994e2adc74223db88724fe76341483ad3098e232f93
BUNDLED WITH
4.0.8
+16 -16
View File
@@ -431,7 +431,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = PortfolioJournal/PortfolioJournalDebug.entitlements; CODE_SIGN_ENTITLEMENTS = PortfolioJournal/PortfolioJournalDebug.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 5; CURRENT_PROJECT_VERSION = 7;
DEVELOPMENT_ASSET_PATHS = PortfolioJournal/Assets.xcassets; DEVELOPMENT_ASSET_PATHS = PortfolioJournal/Assets.xcassets;
DEVELOPMENT_TEAM = 2825Q76T7H; DEVELOPMENT_TEAM = 2825Q76T7H;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
@@ -449,7 +449,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.2.0; MARKETING_VERSION = 1.2.1;
PRODUCT_BUNDLE_IDENTIFIER = com.alexandrevazquez.PortfolioJournal; PRODUCT_BUNDLE_IDENTIFIER = com.alexandrevazquez.PortfolioJournal;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
STRING_CATALOG_GENERATE_SYMBOLS = YES; STRING_CATALOG_GENERATE_SYMBOLS = YES;
@@ -469,7 +469,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = PortfolioJournal/PortfolioJournal.entitlements; CODE_SIGN_ENTITLEMENTS = PortfolioJournal/PortfolioJournal.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 5; CURRENT_PROJECT_VERSION = 7;
DEVELOPMENT_ASSET_PATHS = PortfolioJournal/Assets.xcassets; DEVELOPMENT_ASSET_PATHS = PortfolioJournal/Assets.xcassets;
DEVELOPMENT_TEAM = 2825Q76T7H; DEVELOPMENT_TEAM = 2825Q76T7H;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
@@ -487,7 +487,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.2.0; MARKETING_VERSION = 1.2.1;
PRODUCT_BUNDLE_IDENTIFIER = com.alexandrevazquez.PortfolioJournal; PRODUCT_BUNDLE_IDENTIFIER = com.alexandrevazquez.PortfolioJournal;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
STRING_CATALOG_GENERATE_SYMBOLS = YES; STRING_CATALOG_GENERATE_SYMBOLS = YES;
@@ -626,7 +626,7 @@
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
CODE_SIGN_ENTITLEMENTS = PortfolioJournalWidgetExtension.entitlements; CODE_SIGN_ENTITLEMENTS = PortfolioJournalWidgetExtension.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 5; CURRENT_PROJECT_VERSION = 7;
DEVELOPMENT_ASSET_PATHS = PortfolioJournalWidget/Assets.xcassets; DEVELOPMENT_ASSET_PATHS = PortfolioJournalWidget/Assets.xcassets;
DEVELOPMENT_TEAM = 2825Q76T7H; DEVELOPMENT_TEAM = 2825Q76T7H;
GENERATE_INFOPLIST_FILE = NO; GENERATE_INFOPLIST_FILE = NO;
@@ -639,7 +639,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.2.0; MARKETING_VERSION = 1.2.1;
PRODUCT_BUNDLE_IDENTIFIER = com.alexandrevazquez.PortfolioJournal.PortfolioJournalWidget; PRODUCT_BUNDLE_IDENTIFIER = com.alexandrevazquez.PortfolioJournal.PortfolioJournalWidget;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
@@ -659,7 +659,7 @@
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
CODE_SIGN_ENTITLEMENTS = PortfolioJournalWidgetExtension.entitlements; CODE_SIGN_ENTITLEMENTS = PortfolioJournalWidgetExtension.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 5; CURRENT_PROJECT_VERSION = 7;
DEVELOPMENT_ASSET_PATHS = PortfolioJournalWidget/Assets.xcassets; DEVELOPMENT_ASSET_PATHS = PortfolioJournalWidget/Assets.xcassets;
DEVELOPMENT_TEAM = 2825Q76T7H; DEVELOPMENT_TEAM = 2825Q76T7H;
GENERATE_INFOPLIST_FILE = NO; GENERATE_INFOPLIST_FILE = NO;
@@ -672,7 +672,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.2.0; MARKETING_VERSION = 1.2.1;
PRODUCT_BUNDLE_IDENTIFIER = com.alexandrevazquez.PortfolioJournal.PortfolioJournalWidget; PRODUCT_BUNDLE_IDENTIFIER = com.alexandrevazquez.PortfolioJournal.PortfolioJournalWidget;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
@@ -690,7 +690,7 @@
buildSettings = { buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)"; BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 5; CURRENT_PROJECT_VERSION = 7;
DEVELOPMENT_TEAM = 2825Q76T7H; DEVELOPMENT_TEAM = 2825Q76T7H;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 17.6; IPHONEOS_DEPLOYMENT_TARGET = 17.6;
@@ -699,7 +699,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@loader_path/Frameworks", "@loader_path/Frameworks",
); );
MARKETING_VERSION = 1.2.0; MARKETING_VERSION = 1.2.1;
PRODUCT_BUNDLE_IDENTIFIER = com.alexandrevazquez.PortfolioJournalTests; PRODUCT_BUNDLE_IDENTIFIER = com.alexandrevazquez.PortfolioJournalTests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_EMIT_LOC_STRINGS = NO;
@@ -714,7 +714,7 @@
buildSettings = { buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)"; BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 5; CURRENT_PROJECT_VERSION = 7;
DEVELOPMENT_TEAM = 2825Q76T7H; DEVELOPMENT_TEAM = 2825Q76T7H;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 17.6; IPHONEOS_DEPLOYMENT_TARGET = 17.6;
@@ -723,7 +723,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@loader_path/Frameworks", "@loader_path/Frameworks",
); );
MARKETING_VERSION = 1.2.0; MARKETING_VERSION = 1.2.1;
PRODUCT_BUNDLE_IDENTIFIER = com.alexandrevazquez.PortfolioJournalTests; PRODUCT_BUNDLE_IDENTIFIER = com.alexandrevazquez.PortfolioJournalTests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_EMIT_LOC_STRINGS = NO;
@@ -737,7 +737,7 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 5; CURRENT_PROJECT_VERSION = 7;
DEVELOPMENT_TEAM = 2825Q76T7H; DEVELOPMENT_TEAM = 2825Q76T7H;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 17.6; IPHONEOS_DEPLOYMENT_TARGET = 17.6;
@@ -746,7 +746,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@loader_path/Frameworks", "@loader_path/Frameworks",
); );
MARKETING_VERSION = 1.2.0; MARKETING_VERSION = 1.2.1;
PRODUCT_BUNDLE_IDENTIFIER = com.alexandrevazquez.PortfolioJournalUITests; PRODUCT_BUNDLE_IDENTIFIER = com.alexandrevazquez.PortfolioJournalUITests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_EMIT_LOC_STRINGS = NO;
@@ -760,7 +760,7 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 5; CURRENT_PROJECT_VERSION = 7;
DEVELOPMENT_TEAM = 2825Q76T7H; DEVELOPMENT_TEAM = 2825Q76T7H;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 17.6; IPHONEOS_DEPLOYMENT_TARGET = 17.6;
@@ -769,7 +769,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@loader_path/Frameworks", "@loader_path/Frameworks",
); );
MARKETING_VERSION = 1.2.0; MARKETING_VERSION = 1.2.1;
PRODUCT_BUNDLE_IDENTIFIER = com.alexandrevazquez.PortfolioJournalUITests; PRODUCT_BUNDLE_IDENTIFIER = com.alexandrevazquez.PortfolioJournalUITests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_EMIT_LOC_STRINGS = NO;
+33 -1
View File
@@ -14,16 +14,29 @@ struct ContentView: View {
@Environment(\.scenePhase) private var scenePhase @Environment(\.scenePhase) private var scenePhase
@State private var isUnlocked = false @State private var isUnlocked = false
@State private var resolvedOnboardingCompleted: Bool? @State private var resolvedOnboardingCompleted: Bool?
@State private var iCloudCheckDone = false
@State private var loadingMessageKey: LocalizedStringKey = "loading_data"
private var lockEnabled: Bool { private var lockEnabled: Bool {
faceIdEnabled || pinEnabled faceIdEnabled || pinEnabled
} }
/// True when a fresh install with iCloud available and sync not yet enabled.
/// Only relevant before onboarding is completed.
private var needsICloudCheck: Bool {
guard resolvedOnboardingCompleted == false else { return false }
guard !UserDefaults.standard.bool(forKey: "cloudSyncEnabled") else { return false }
return FileManager.default.ubiquityIdentityToken != nil
}
var body: some View { var body: some View {
ZStack { ZStack {
Group { Group {
if !isReadyForContent { if !isReadyForContent {
AppLaunchLoadingView(messageKey: "loading_data") AppLaunchLoadingView(messageKey: loadingMessageKey)
} else if needsICloudCheck && !iCloudCheckDone {
// Fresh install with iCloud available: ask before showing onboarding
OnboardingICloudCheckView(onSkip: { iCloudCheckDone = true })
} else if resolvedOnboardingCompleted == false { } else if resolvedOnboardingCompleted == false {
OnboardingView(onboardingCompleted: $onboardingCompleted) OnboardingView(onboardingCompleted: $onboardingCompleted)
} else { } else {
@@ -78,12 +91,31 @@ struct ContentView: View {
try? await Task.sleep(nanoseconds: 50_000_000) // 50ms try? await Task.sleep(nanoseconds: 50_000_000) // 50ms
} }
// If CloudKit is enabled and no local data yet, wait briefly for the
// initial iCloud sync so data from other devices appears before we
// decide whether to show onboarding.
if UserDefaults.standard.bool(forKey: "cloudSyncEnabled") && !hasExistingData() {
await MainActor.run { loadingMessageKey = "checking_icloud" }
await waitForInitialCloudKitSync(timeout: 10)
await MainActor.run { loadingMessageKey = "loading_data" }
}
// Resolve onboarding state on main thread // Resolve onboarding state on main thread
await MainActor.run { await MainActor.run {
syncOnboardingState() syncOnboardingState()
} }
} }
/// Polls for existing data up to `timeout` seconds, returning as soon as
/// any data appears. Used to wait for the initial CloudKit sync on launch.
private func waitForInitialCloudKitSync(timeout: TimeInterval) async {
let deadline = Date().addingTimeInterval(timeout)
while Date() < deadline {
if hasExistingData() { return }
try? await Task.sleep(nanoseconds: 300_000_000) // poll every 300ms
}
}
private func syncOnboardingState() { private func syncOnboardingState() {
let settings = AppSettings.getOrCreate(in: coreDataStack.viewContext) let settings = AppSettings.getOrCreate(in: coreDataStack.viewContext)
var resolved = settings.onboardingCompleted || onboardingCompleted var resolved = settings.onboardingCompleted || onboardingCompleted
@@ -34,6 +34,10 @@ struct PortfolioJournalApp: App {
.onChange(of: scenePhase) { _, newPhase in .onChange(of: scenePhase) { _, newPhase in
if newPhase == .active { if newPhase == .active {
coreDataStack.refreshWidgetData() coreDataStack.refreshWidgetData()
// Re-read all Core Data objects from the persistent store so that
// iCloud changes made on other devices while this device was inactive
// are reflected immediately without waiting for a remote-change notification.
coreDataStack.refreshFromCloudKit()
} else if newPhase == .background { } else if newPhase == .background {
guard iapService.isPremium else { return } guard iapService.isPremium else { return }
guard UserDefaults.standard.bool(forKey: "backupsEnabled") else { return } guard UserDefaults.standard.bool(forKey: "backupsEnabled") else { return }
+19 -5
View File
@@ -258,12 +258,26 @@ class CoreDataStack: ObservableObject {
// MARK: - Remote Change Handling // MARK: - Remote Change Handling
@objc private func processRemoteChanges(_ notification: Notification) { @objc private func processRemoteChanges(_ notification: Notification) {
// Process remote changes on main context
DispatchQueue.main.async { [weak self] in DispatchQueue.main.async { [weak self] in
self?.objectWillChange.send() guard let self else { return }
// Ensure changes are persisted to disk before refreshing widget // Force viewContext to re-read all objects from the persistent store,
self?.save() // which now contains the changes CloudKit just synced from other devices.
self?.refreshWidgetData() // This triggers NSManagedObjectContextObjectsDidChange so all repositories
// automatically re-fetch and push fresh data to ViewModels.
self.viewContext.refreshAllObjects()
self.objectWillChange.send()
self.save()
self.refreshWidgetData()
}
}
/// Forces an immediate re-read of all Core Data objects from the persistent store.
/// Call this when the app returns to the foreground so any iCloud changes made on
/// other devices (while this device was inactive) are picked up right away.
func refreshFromCloudKit() {
guard Self.cloudKitEnabled else { return }
viewContext.perform { [weak self] in
self?.viewContext.refreshAllObjects()
} }
} }
+1 -1
View File
@@ -30,7 +30,7 @@
</dict> </dict>
</array> </array>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>3</string> <string>$(CURRENT_PROJECT_VERSION)</string>
<key>GADApplicationIdentifier</key> <key>GADApplicationIdentifier</key>
<string>ca-app-pub-1549720748100858~9632507420</string> <string>ca-app-pub-1549720748100858~9632507420</string>
<key>GADDelayAppMeasurementInit</key> <key>GADDelayAppMeasurementInit</key>
@@ -0,0 +1,294 @@
"app_name" = "Portfolio Journal";
"ok" = "OK";
"cancel" = "Abbrechen";
"save" = "Speichern";
"delete" = "Löschen";
"edit" = "Bearbeiten";
"add" = "Hinzufügen";
"done" = "Fertig";
"close" = "Schließen";
"continue" = "Weiter";
"skip" = "Überspringen";
"error" = "Fehler";
"success" = "Erfolg";
"loading" = "Lädt...";
"loading_data" = "Deine Daten werden geladen...";
"tab_dashboard" = "Start";
"tab_sources" = "Quellen";
"tab_charts" = "Charts";
"tab_settings" = "Einstellungen";
"dashboard_title" = "Start";
"total_portfolio_value" = "Gesamtwert des Portfolios";
"today" = "heute";
"returns" = "Rendite";
"by_category" = "Nach Kategorie";
"pending_updates" = "Ausstehende Updates";
"see_all" = "Alle anzeigen";
"sources_title" = "Quellen";
"add_source" = "Quelle hinzufügen";
"source_name" = "Quellenname";
"select_category" = "Kategorie auswählen";
"initial_value" = "Anfangswert";
"initial_value_optional" = "Anfangswert (optional)";
"reminder_frequency" = "Erinnerungsintervall";
"source_limit_warning" = "Quellenlimit erreicht. Upgrade auf Premium für unbegrenzte Quellen.";
"no_sources" = "Keine Investmentquellen";
"no_sources_message" = "Füge deine erste Investmentquelle hinzu, um dein Portfolio zu verfolgen.";
"add_snapshot" = "Snapshot hinzufügen";
"edit_snapshot" = "Snapshot bearbeiten";
"snapshot_date" = "Datum";
"snapshot_value" = "Wert";
"snapshot_contribution" = "Beitrag";
"contribution_optional" = "Beitrag (optional)";
"notes" = "Notizen";
"notes_optional" = "Notizen (optional)";
"previous_value" = "Vorheriger Wert: %@";
"change_from_previous" = "Änderung zum vorherigen Wert";
"charts_title" = "Charts";
"evolution" = "Entwicklung";
"allocation" = "Allokation";
"performance" = "Performance";
"drawdown" = "Drawdown";
"volatility" = "Volatilität";
"prediction" = "Prognose";
"portfolio_evolution" = "Portfolioentwicklung";
"asset_allocation" = "Asset-Allokation";
"performance_by_category" = "Performance nach Kategorie";
"drawdown_analysis" = "Drawdown-Analyse";
"prediction_12_month" = "12-Monats-Prognose";
"not_enough_data" = "Nicht genügend Daten";
"cagr" = "CAGR";
"twr" = "TWR";
"max_drawdown" = "Max. Drawdown";
"sharpe_ratio" = "Sharpe Ratio";
"win_rate" = "Trefferquote";
"avg_monthly" = "Monatsdurchschnitt";
"best_month" = "Bester Monat";
"worst_month" = "Schlechtester Monat";
"premium" = "Premium";
"upgrade_to_premium" = "Auf Premium upgraden";
"unlock_full_potential" = "Volles Potenzial freischalten";
"one_time_purchase" = "Einmalkauf";
"includes_family_sharing" = "Inklusive Familienfreigabe";
"upgrade_now" = "Jetzt upgraden";
"restore_purchases" = "Käufe wiederherstellen";
"premium_active" = "Premium aktiv";
"premium_feature" = "Premium-Funktion";
"unlock" = "Freischalten";
"feature_unlimited_sources" = "Unbegrenzte Quellen";
"feature_unlimited_sources_desc" = "Verfolge so viele Investments, wie du möchtest";
"feature_full_history" = "Vollständige Historie";
"feature_full_history_desc" = "Greife auf deine komplette Investmenthistorie zu";
"feature_advanced_charts" = "Erweiterte Charts";
"feature_advanced_charts_desc" = "5 Arten detaillierter Analysecharts";
"feature_predictions" = "Prognosen";
"feature_predictions_desc" = "KI-gestützte 12-Monats-Prognosen";
"feature_export" = "Daten exportieren";
"feature_export_desc" = "Export nach CSV und JSON";
"feature_no_ads" = "Keine Werbung";
"feature_no_ads_desc" = "Dauerhaft werbefrei";
"paywall_benefit_history_title" = "Deine vollständige Historie";
"paywall_benefit_history_subtitle" = "Jeder Snapshot, Beitrag und Gewinn seit Tag eins";
"paywall_benefit_charts_title" = "Charts, die Muster zeigen";
"paywall_benefit_charts_subtitle" = "Allokation, Drawdown, Performance, alles an einem Ort";
"paywall_benefit_forecasts_title" = "12-Monats-Prognosen";
"paywall_benefit_forecasts_subtitle" = "Sieh, wohin sich dein Portfolio wahrscheinlich entwickelt";
"paywall_benefit_noads_title" = "Keine Werbung, jemals";
"paywall_benefit_noads_subtitle" = "Klares, fokussiertes Erlebnis ohne Ablenkung";
"settings_title" = "Einstellungen";
"subscription" = "Abo";
"notifications" = "Benachrichtigungen";
"default_reminder_time" = "Standard-Erinnerungszeit";
"data" = "Daten";
"export_data" = "Daten exportieren";
"total_sources" = "Anzahl Quellen";
"total_snapshots" = "Anzahl Snapshots";
"storage_used" = "Verwendeter Speicher";
"about" = "Info";
"version" = "Version";
"privacy_policy" = "Datenschutzerklärung";
"terms_of_service" = "Nutzungsbedingungen";
"support" = "Support";
"rate_app" = "App bewerten";
"danger_zone" = "Gefahrenzone";
"reset_all_data" = "Alle Daten zurücksetzen";
"reset_confirmation" = "Dadurch werden alle Investmentdaten dauerhaft gelöscht. Diese Aktion kann nicht rückgängig gemacht werden.";
"frequency_monthly" = "Monatlich";
"frequency_quarterly" = "Vierteljährlich";
"frequency_semiannual" = "Halbjährlich";
"frequency_annual" = "Jährlich";
"frequency_custom" = "Benutzerdefiniert";
"frequency_never" = "Nie";
"every_n_months" = "Alle %d Monate";
"category_stocks" = "Aktien";
"category_bonds" = "Anleihen";
"category_real_estate" = "Immobilien";
"category_crypto" = "Krypto";
"category_cash" = "Cash";
"category_etfs" = "ETFs";
"category_retirement" = "Altersvorsorge";
"category_other" = "Sonstiges";
"uncategorized" = "Nicht kategorisiert";
"time_1m" = "1M";
"time_3m" = "3M";
"time_6m" = "6M";
"time_1y" = "1J";
"time_all" = "Alle";
"export_format" = "Format auswählen";
"export_csv" = "CSV";
"export_csv_desc" = "Kompatibel mit Excel und Google Sheets";
"export_json" = "JSON";
"export_json_desc" = "Vollständige Datenstruktur für Backups";
"onboarding_track_title" = "Verfolge deine Investments";
"onboarding_track_desc" = "Behalte alle deine Investmentquellen an einem Ort im Blick. Aktien, Anleihen, Immobilien, Krypto und mehr.";
"onboarding_visualize_title" = "Visualisiere dein Wachstum";
"onboarding_visualize_desc" = "Schöne Charts zeigen die Entwicklung, Allokation und Performance deines Portfolios im Zeitverlauf.";
"onboarding_reminders_title" = "Verpasse nie ein Update";
"onboarding_reminders_desc" = "Setze Erinnerungen, um deine Investments regelmäßig zu verfolgen. Monatlich, vierteljährlich oder individuell.";
"onboarding_sync_title" = "Überall synchron";
"onboarding_sync_desc" = "Deine Daten synchronisieren automatisch via iCloud auf all deinen Apple-Geräten.";
"get_started" = "Loslegen";
"onboarding_clarity_title" = "Wisse genau, wo du stehst";
"onboarding_clarity_desc" = "Sieh dein Gesamtvermögen, echte Renditen und Allokation, immer aktuell.";
"onboarding_habit_title" = "5 Minuten pro Monat reichen";
"onboarding_habit_desc" = "Erfasse deine Werte einmal im Monat. Portfolio Journal übernimmt die Berechnungen und zeigt deinen Fortschritt.";
"onboarding_calm_title" = "Ignoriere den Lärm. Verfolge den Trend.";
"onboarding_calm_desc" = "Tägliche Schwankungen erzählen nicht die echte Geschichte. Dein Wachstum über Monate und Jahre schon.";
"onboarding_goals_title" = "Erreiche deine finanziellen Ziele";
"onboarding_goals_desc" = "Setze Ziele, verfolge Meilensteine und sieh genau, wie weit du gekommen bist.";
"error_generic" = "Ein Fehler ist aufgetreten. Bitte versuche es erneut.";
"error_no_purchases" = "Keine Käufe zum Wiederherstellen gefunden";
"error_purchase_failed" = "Kauf fehlgeschlagen: %@";
"error_export_failed" = "Export fehlgeschlagen. Bitte versuche es erneut.";
"placeholder_source_name" = "z. B. Vanguard 401k";
"placeholder_value" = "0.00";
"placeholder_notes" = "Notizen hinzufügen...";
"mood_energized_title" = "Voller Energie";
"mood_confident_title" = "Selbstsicher";
"mood_balanced_title" = "Ausgeglichen";
"mood_cautious_title" = "Vorsichtig";
"mood_stressed_title" = "Gestresst";
"mood_energized_detail" = "Fühle mich unschlagbar";
"mood_confident_detail" = "Auf Kurs und gelassen";
"mood_balanced_detail" = "Ruhig und geduldig";
"mood_cautious_detail" = "Beobachte die Bewegungen";
"mood_stressed_detail" = "Brauche einen Reset";
"achievement_streak_3_title" = "3-Monats-Serie";
"achievement_streak_3_detail" = "Du hast drei Monate in Folge pünktlich eingecheckt.";
"achievement_streak_6_title" = "Halbjahres-Serie";
"achievement_streak_6_detail" = "Sechs pünktliche Check-ins in Folge.";
"achievement_streak_12_title" = "Ein Jahr Momentum";
"achievement_streak_12_detail" = "Ein ganzes Jahr ohne Fristversäumnis.";
"achievement_perfect_on_time_title" = "Nie zu spät";
"achievement_perfect_on_time_detail" = "Jeder Check-in kam vor der Frist an.";
"achievement_clutch_finish_title" = "Last-Minute-Erfolg";
"achievement_clutch_finish_detail" = "Mit wenigen Stunden Puffer, aber noch rechtzeitig abgegeben.";
"achievement_early_bird_title" = "Frühstarter";
"achievement_early_bird_detail" = "Im Schnitt beendest du deine Check-ins mit viel Zeit übrig.";
"achievements_title" = "Erfolge";
"achievements_view_all" = "Alle Erfolge anzeigen";
"achievements_nav_title" = "Erfolge";
"achievements_progress_title" = "Fortschritt";
"achievements_unlocked_title" = "Freigeschaltet";
"achievements_unlocked_empty" = "Schließe Check-ins ab, um Erfolge freizuschalten.";
"achievements_locked_title" = "Gesperrt";
"achievements_locked_empty" = "Alle Erfolge freigeschaltet. Gute Arbeit.";
"rating_accessibility" = "Bewertung %d von 5";
"achievements_unlocked_count" = "%d von %d freigeschaltet";
"last_check_in" = "Letzter Check-in: %@";
"next_check_in" = "Nächster Check-in: %@";
"on_time_rate" = "%@ pünktlich";
"on_time_count" = "%d/%d pünktlich";
"tightest_finish" = "Knappster Abschluss: %@ vor der Frist.";
"date_today" = "Heute";
"date_yesterday" = "Gestern";
"date_never" = "Nie";
"calendar_event_title" = "%@: Monatlicher Check-in";
"calendar_event_notes" = "Öffne %@ und erledige deinen monatlichen Check-in.";
"checkin_enjoying_dialog_title" = "Wie sehr gefällt dir Portfolio Journal?";
"checkin_enjoying_dialog_message" = "Glückwunsch zu deinem neuen Erfolg. Dein Feedback hilft uns, besser zu werden.";
"not_now" = "Nicht jetzt";
"rating_1_star" = "1 Stern";
"rating_n_stars" = "%d Sterne";
"app_store_review_title" = "Möchtest du eine Bewertung im App Store hinterlassen?";
"app_store_review_message" = "Danke für die 5 Sterne. Das hilft anderen Investoren sehr, die App zu entdecken.";
"write_review" = "Bewertung schreiben";
"save_1_snapshot" = "1 Snapshot speichern";
"save_n_snapshots" = "%d Snapshots speichern";
"checkin_update_month" = "%@ aktualisieren";
"checkin_start_new" = "Starten";
"Home" = "Start";
"Sources" = "Quellen";
"Charts" = "Charts";
"Settings" = "Einstellungen";
"Journal" = "Journal";
"Search monthly notes" = "Monatliche Notizen suchen";
"Monthly Check-ins" = "Monatliche Check-ins";
"No monthly notes yet." = "Noch keine monatlichen Notizen.";
"No matching notes." = "Keine passenden Notizen.";
"Jump to month" = "Zum Monat springen";
"Today" = "Heute";
"Mood not set" = "Stimmung nicht gesetzt";
"No rating" = "Keine Bewertung";
"No note yet." = "Noch keine Notiz.";
"Monthly Note" = "Monatliche Notiz";
"Open Full Note" = "Vollständige Notiz öffnen";
"Duplicate Previous" = "Vorherigen duplizieren";
"Save" = "Speichern";
"Monthly Check-in" = "Monatlicher Check-in";
"This Month" = "Diesen Monat";
"No check-in yet this month" = "Diesen Monat noch kein Check-in";
"Start your first check-in anytime." = "Starte deinen ersten Check-in jederzeit.";
"Mark Check-in Complete" = "Check-in als abgeschlossen markieren";
"Editing stays open. New check-ins unlock after 70% of the month." = "Die Bearbeitung bleibt offen. Neue Check-ins werden nach 70 % des Monats freigeschaltet.";
"Momentum & Streaks" = "Momentum & Serien";
"Log a check-in to start a streak" = "Erfasse einen Check-in, um eine Serie zu starten";
"Streak" = "Serie";
"On-time in a row" = "Pünktlich in Folge";
"Best" = "Best";
"Personal best" = "Persönlicher Bestwert";
"Avg early" = "Ø Puffer";
"vs deadline" = "vs. Frist";
"On-time score" = "Pünktlichkeitsscore";
"Achievements" = "Erfolge";
"View all achievements" = "Alle Erfolge anzeigen";
"Monthly Pulse" = "Monatlicher Puls";
"Optional" = "Optional";
"Rate this month" = "Diesen Monat bewerten";
"How did it feel?" = "Wie hat es sich angefühlt?";
"Monthly Summary" = "Monatsübersicht";
"Starting" = "Start";
"Ending" = "Ende";
"Contributions" = "Beiträge";
"Net Performance" = "Nettoperformance";
"Update Sources" = "Quellen aktualisieren";
"Add sources to start your monthly check-in." = "Füge Quellen hinzu, um deinen monatlichen Check-in zu starten.";
"Updated this cycle" = "In diesem Zyklus aktualisiert";
"Needs update" = "Benötigt Update";
"Snapshot Notes" = "Snapshot-Notizen";
"No snapshot notes for this month." = "Keine Snapshot-Notizen in diesem Monat.";
"Source" = "Quelle";
"Your full portfolio,\nfully clear" = "Dein komplettes Portfolio,\nvöllig klar";
"One payment. Every feature. Forever." = "Eine Zahlung. Jede Funktion. Für immer.";
"Get Full Access" = "Vollen Zugriff erhalten";
"Restore Purchases" = "Käufe wiederherstellen";
"Payment charged to your Apple ID account." = "Die Zahlung wird deinem Apple-ID-Konto belastet.";
"Terms" = "Bedingungen";
"Privacy" = "Datenschutz";
"· one-time · Family Sharing" = "· einmalig · Familienfreigabe";
"Full access, one payment" = "Voller Zugriff, eine Zahlung";
"Unlimited sources, advanced charts & more" = "Unbegrenzte Quellen, erweiterte Charts und mehr";
"See full access" = "Vollen Zugriff ansehen";
"Batch Update" = "Sammelupdate";
"Current value" = "Aktueller Wert";
"Contribution this period (optional)" = "Beitrag in diesem Zeitraum (optional)";
"Include Contribution" = "Beitrag einbeziehen";
"New capital added" = "Neues Kapital hinzugefügt";
"Contribution (Optional)" = "Beitrag (optional)";
"Track new capital added to separate it from investment growth." = "Erfasse neues Kapital, um es vom Investmentwachstum zu trennen.";
"Monthly Highlights" = "Monatliche Highlights";
"Best Performer" = "Bester Performer";
"Worst Performer" = "Schlechtester Performer";
"Best Contributor" = "Größter Beitrag";
"Update Check-in" = "Check-in aktualisieren";
"Completed %@" = "Abgeschlossen %@";
@@ -197,6 +197,13 @@
"onboarding_goals_title" = "Reach your financial goals"; "onboarding_goals_title" = "Reach your financial goals";
"onboarding_goals_desc" = "Set targets, track milestones, and see exactly how far you've come."; "onboarding_goals_desc" = "Set targets, track milestones, and see exactly how far you've come.";
// iCloud check before onboarding (1.2.1)
"icloud_check_title" = "Already use Portfolio Journal?";
"icloud_check_description" = "If you have data on another device, enable iCloud to restore it here.";
"icloud_enabled_title" = "iCloud Enabled";
"icloud_enabled_description" = "Close the app and reopen it. Your data will load automatically from iCloud.";
"checking_icloud" = "Checking iCloud...";
// MARK: - Errors // MARK: - Errors
"error_generic" = "An error occurred. Please try again."; "error_generic" = "An error occurred. Please try again.";
"error_no_purchases" = "No purchases found to restore"; "error_no_purchases" = "No purchases found to restore";
@@ -255,3 +262,21 @@
"date_never" = "Never"; "date_never" = "Never";
"calendar_event_title" = "%@: Monthly Check-in"; "calendar_event_title" = "%@: Monthly Check-in";
"calendar_event_notes" = "Open %@ and complete your monthly check-in."; "calendar_event_notes" = "Open %@ and complete your monthly check-in.";
// MARK: - Satisfaction & Review Dialogs
"checkin_enjoying_dialog_title" = "How much are you enjoying Portfolio Journal?";
"checkin_enjoying_dialog_message" = "Congrats on your new achievement! Your feedback helps us improve.";
"not_now" = "Not Now";
"rating_1_star" = "1 Star";
"rating_n_stars" = "%d Stars";
"app_store_review_title" = "Would you like to leave an App Store review?";
"app_store_review_message" = "Thanks for the 5 stars. It really helps other investors discover the app.";
"write_review" = "Write a Review";
// MARK: - Batch Update Actions
"save_1_snapshot" = "Save 1 Snapshot";
"save_n_snapshots" = "Save %d Snapshots";
// MARK: - Monthly Check-in Card
"checkin_update_month" = "Update %@";
"checkin_start_new" = "Start";
@@ -124,6 +124,16 @@
"rating_accessibility" = "Valoración %d de 5"; "rating_accessibility" = "Valoración %d de 5";
"achievements_unlocked_count" = "%d de %d desbloqueados"; "achievements_unlocked_count" = "%d de %d desbloqueados";
// MARK: - Satisfaction & Review Dialogs
"checkin_enjoying_dialog_title" = "¿Cuánto disfrutas Portfolio Journal?";
"checkin_enjoying_dialog_message" = "¡Felicidades por tu nuevo logro! Tu opinión nos ayuda a mejorar.";
"not_now" = "Ahora no";
"rating_1_star" = "1 estrella";
"rating_n_stars" = "%d estrellas";
"app_store_review_title" = "¿Te gustaría dejar una reseña en el App Store?";
"app_store_review_message" = "Gracias por las 5 estrellas. Ayuda mucho a que otros inversores descubran la app.";
"write_review" = "Escribir reseña";
// MARK: - Paywall (1.2.0) // MARK: - Paywall (1.2.0)
"Your full portfolio,\nfully clear" = "Tu cartera completa,\ntotalmente clara"; "Your full portfolio,\nfully clear" = "Tu cartera completa,\ntotalmente clara";
"One payment. Every feature. Forever." = "Un pago. Todas las funciones. Para siempre."; "One payment. Every feature. Forever." = "Un pago. Todas las funciones. Para siempre.";
@@ -157,10 +167,27 @@
"onboarding_goals_title" = "Alcanza tus metas financieras"; "onboarding_goals_title" = "Alcanza tus metas financieras";
"onboarding_goals_desc" = "Define objetivos, sigue los hitos y ve exactamente hasta dónde has llegado."; "onboarding_goals_desc" = "Define objetivos, sigue los hitos y ve exactamente hasta dónde has llegado.";
// iCloud check antes del onboarding (1.2.1)
"icloud_check_title" = "¿Ya usas Portfolio Journal?";
"icloud_check_description" = "Si tienes datos en otro dispositivo, activa iCloud para restaurarlos aquí.";
"icloud_enabled_title" = "iCloud activado";
"icloud_enabled_description" = "Cierra la app y ábrela de nuevo. Tus datos se cargarán automáticamente desde iCloud.";
"checking_icloud" = "Comprobando iCloud...";
// Literales de botones (SwiftUI auto-lookup)
"Restore from iCloud" = "Restaurar desde iCloud";
"Start Fresh" = "Empezar desde cero";
"Got it" = "Entendido";
// MARK: - Batch Update (1.2.0) // MARK: - Batch Update (1.2.0)
"Batch Update" = "Actualización en bloque"; "Batch Update" = "Actualización en bloque";
"Current value" = "Valor actual"; "Current value" = "Valor actual";
"Contribution this period (optional)" = "Aportación en este periodo (opcional)"; "Contribution this period (optional)" = "Aportación en este periodo (opcional)";
"save_1_snapshot" = "Guardar 1 snapshot";
"save_n_snapshots" = "Guardar %d snapshots";
// MARK: - Monthly Check-in Card
"checkin_update_month" = "Actualizar %@";
"checkin_start_new" = "Iniciar";
// MARK: - Contribution (1.2.0) // MARK: - Contribution (1.2.0)
"Include Contribution" = "Incluir aportación"; "Include Contribution" = "Incluir aportación";
@@ -0,0 +1,294 @@
"app_name" = "Portfolio Journal";
"ok" = "OK";
"cancel" = "Annuler";
"save" = "Enregistrer";
"delete" = "Supprimer";
"edit" = "Modifier";
"add" = "Ajouter";
"done" = "Terminé";
"close" = "Fermer";
"continue" = "Continuer";
"skip" = "Passer";
"error" = "Erreur";
"success" = "Succès";
"loading" = "Chargement...";
"loading_data" = "Chargement de vos données...";
"tab_dashboard" = "Accueil";
"tab_sources" = "Sources";
"tab_charts" = "Graphiques";
"tab_settings" = "Réglages";
"dashboard_title" = "Accueil";
"total_portfolio_value" = "Valeur totale du portefeuille";
"today" = "aujourd'hui";
"returns" = "Rendements";
"by_category" = "Par catégorie";
"pending_updates" = "Mises à jour en attente";
"see_all" = "Voir tout";
"sources_title" = "Sources";
"add_source" = "Ajouter une source";
"source_name" = "Nom de la source";
"select_category" = "Sélectionner une catégorie";
"initial_value" = "Valeur initiale";
"initial_value_optional" = "Valeur initiale (optionnelle)";
"reminder_frequency" = "Fréquence des rappels";
"source_limit_warning" = "Limite de sources atteinte. Passez à Premium pour des sources illimitées.";
"no_sources" = "Aucune source d'investissement";
"no_sources_message" = "Ajoutez votre première source d'investissement pour commencer à suivre votre portefeuille.";
"add_snapshot" = "Ajouter un snapshot";
"edit_snapshot" = "Modifier le snapshot";
"snapshot_date" = "Date";
"snapshot_value" = "Valeur";
"snapshot_contribution" = "Contribution";
"contribution_optional" = "Contribution (optionnelle)";
"notes" = "Notes";
"notes_optional" = "Notes (optionnelles)";
"previous_value" = "Précédent : %@";
"change_from_previous" = "Variation par rapport au précédent";
"charts_title" = "Graphiques";
"evolution" = "Évolution";
"allocation" = "Allocation";
"performance" = "Performance";
"drawdown" = "Drawdown";
"volatility" = "Volatilité";
"prediction" = "Prévision";
"portfolio_evolution" = "Évolution du portefeuille";
"asset_allocation" = "Allocation des actifs";
"performance_by_category" = "Performance par catégorie";
"drawdown_analysis" = "Analyse du drawdown";
"prediction_12_month" = "Prévision sur 12 mois";
"not_enough_data" = "Pas assez de données";
"cagr" = "CAGR";
"twr" = "TWR";
"max_drawdown" = "Drawdown max";
"sharpe_ratio" = "Ratio de Sharpe";
"win_rate" = "Taux de réussite";
"avg_monthly" = "Moyenne mensuelle";
"best_month" = "Meilleur mois";
"worst_month" = "Pire mois";
"premium" = "Premium";
"upgrade_to_premium" = "Passer à Premium";
"unlock_full_potential" = "Débloquez tout le potentiel";
"one_time_purchase" = "Achat unique";
"includes_family_sharing" = "Inclut le partage familial";
"upgrade_now" = "Passer maintenant";
"restore_purchases" = "Restaurer les achats";
"premium_active" = "Premium actif";
"premium_feature" = "Fonction Premium";
"unlock" = "Débloquer";
"feature_unlimited_sources" = "Sources illimitées";
"feature_unlimited_sources_desc" = "Suivez autant d'investissements que vous le souhaitez";
"feature_full_history" = "Historique complet";
"feature_full_history_desc" = "Accédez à tout votre historique d'investissement";
"feature_advanced_charts" = "Graphiques avancés";
"feature_advanced_charts_desc" = "5 types de graphiques analytiques détaillés";
"feature_predictions" = "Prévisions";
"feature_predictions_desc" = "Prévisions IA sur 12 mois";
"feature_export" = "Exporter les données";
"feature_export_desc" = "Export vers CSV et JSON";
"feature_no_ads" = "Sans publicité";
"feature_no_ads_desc" = "Expérience sans publicité à vie";
"paywall_benefit_history_title" = "Votre historique complet";
"paywall_benefit_history_subtitle" = "Chaque snapshot, contribution et gain depuis le premier jour";
"paywall_benefit_charts_title" = "Des graphiques qui révèlent des tendances";
"paywall_benefit_charts_subtitle" = "Allocation, drawdown, performance, le tout au même endroit";
"paywall_benefit_forecasts_title" = "Prévisions à 12 mois";
"paywall_benefit_forecasts_subtitle" = "Voyez où votre portefeuille pourrait aller";
"paywall_benefit_noads_title" = "Aucune pub, jamais";
"paywall_benefit_noads_subtitle" = "Une expérience propre et centrée, sans distractions";
"settings_title" = "Réglages";
"subscription" = "Abonnement";
"notifications" = "Notifications";
"default_reminder_time" = "Heure de rappel par défaut";
"data" = "Données";
"export_data" = "Exporter les données";
"total_sources" = "Nombre total de sources";
"total_snapshots" = "Nombre total de snapshots";
"storage_used" = "Stockage utilisé";
"about" = "À propos";
"version" = "Version";
"privacy_policy" = "Politique de confidentialité";
"terms_of_service" = "Conditions d'utilisation";
"support" = "Support";
"rate_app" = "Noter l'app";
"danger_zone" = "Zone de danger";
"reset_all_data" = "Réinitialiser toutes les données";
"reset_confirmation" = "Cela supprimera définitivement toutes vos données d'investissement. Cette action est irréversible.";
"frequency_monthly" = "Mensuel";
"frequency_quarterly" = "Trimestriel";
"frequency_semiannual" = "Semestriel";
"frequency_annual" = "Annuel";
"frequency_custom" = "Personnalisé";
"frequency_never" = "Jamais";
"every_n_months" = "Tous les %d mois";
"category_stocks" = "Actions";
"category_bonds" = "Obligations";
"category_real_estate" = "Immobilier";
"category_crypto" = "Crypto";
"category_cash" = "Espèces";
"category_etfs" = "ETF";
"category_retirement" = "Retraite";
"category_other" = "Autre";
"uncategorized" = "Non catégorisé";
"time_1m" = "1M";
"time_3m" = "3M";
"time_6m" = "6M";
"time_1y" = "1A";
"time_all" = "Tout";
"export_format" = "Sélectionner le format";
"export_csv" = "CSV";
"export_csv_desc" = "Compatible avec Excel et Google Sheets";
"export_json" = "JSON";
"export_json_desc" = "Structure complète pour sauvegarde";
"onboarding_track_title" = "Suivez vos investissements";
"onboarding_track_desc" = "Surveillez toutes vos sources d'investissement au même endroit. Actions, obligations, immobilier, crypto et plus.";
"onboarding_visualize_title" = "Visualisez votre croissance";
"onboarding_visualize_desc" = "De beaux graphiques montrent l'évolution, l'allocation et la performance de votre portefeuille dans le temps.";
"onboarding_reminders_title" = "Ne manquez jamais une mise à jour";
"onboarding_reminders_desc" = "Définissez des rappels pour suivre régulièrement vos investissements. Mensuels, trimestriels ou personnalisés.";
"onboarding_sync_title" = "Synchronisez partout";
"onboarding_sync_desc" = "Vos données se synchronisent automatiquement via iCloud sur tous vos appareils Apple.";
"get_started" = "Commencer";
"onboarding_clarity_title" = "Sachez exactement où vous en êtes";
"onboarding_clarity_desc" = "Voyez votre patrimoine total, vos rendements réels et votre allocation, toujours à jour.";
"onboarding_habit_title" = "5 minutes par mois suffisent";
"onboarding_habit_desc" = "Saisissez vos valeurs une fois par mois. Portfolio Journal s'occupe des calculs et montre vos progrès.";
"onboarding_calm_title" = "Ignorez le bruit. Suivez la tendance.";
"onboarding_calm_desc" = "Les variations quotidiennes ne racontent pas la vraie histoire. Votre croissance sur des mois et des années, si.";
"onboarding_goals_title" = "Atteignez vos objectifs financiers";
"onboarding_goals_desc" = "Fixez des objectifs, suivez les étapes et voyez exactement jusqu'où vous êtes arrivé.";
"error_generic" = "Une erreur s'est produite. Veuillez réessayer.";
"error_no_purchases" = "Aucun achat à restaurer";
"error_purchase_failed" = "Achat échoué : %@";
"error_export_failed" = "L'export a échoué. Veuillez réessayer.";
"placeholder_source_name" = "ex. Vanguard 401k";
"placeholder_value" = "0.00";
"placeholder_notes" = "Ajouter des notes...";
"mood_energized_title" = "En feu";
"mood_confident_title" = "Confiant";
"mood_balanced_title" = "Stable";
"mood_cautious_title" = "Prudent";
"mood_stressed_title" = "Stressé";
"mood_energized_detail" = "Je me sens imbattable";
"mood_confident_detail" = "Sur la bonne voie et serein";
"mood_balanced_detail" = "Calme et patient";
"mood_cautious_detail" = "J'observe les mouvements";
"mood_stressed_detail" = "Besoin de souffler";
"achievement_streak_3_title" = "Série de 3 mois";
"achievement_streak_3_detail" = "Vous avez gardé vos check-ins à l'heure pendant trois mois d'affilée.";
"achievement_streak_6_title" = "Série de six mois";
"achievement_streak_6_detail" = "Six check-ins consécutifs à l'heure.";
"achievement_streak_12_title" = "Une année d'élan";
"achievement_streak_12_detail" = "Une année complète sans manquer la date limite.";
"achievement_perfect_on_time_title" = "Jamais en retard";
"achievement_perfect_on_time_detail" = "Chaque check-in a été envoyé avant la date limite.";
"achievement_clutch_finish_title" = "Final serré";
"achievement_clutch_finish_detail" = "Envoyé avec quelques heures d'avance, mais toujours à temps.";
"achievement_early_bird_title" = "Lève-tôt";
"achievement_early_bird_detail" = "En moyenne, vous terminez avec une belle marge.";
"achievements_title" = "Succès";
"achievements_view_all" = "Voir tous les succès";
"achievements_nav_title" = "Succès";
"achievements_progress_title" = "Progression";
"achievements_unlocked_title" = "Débloqués";
"achievements_unlocked_empty" = "Complétez des check-ins pour débloquer des succès.";
"achievements_locked_title" = "Verrouillés";
"achievements_locked_empty" = "Tous les succès sont débloqués. Beau travail.";
"rating_accessibility" = "Note %d sur 5";
"achievements_unlocked_count" = "%d sur %d débloqués";
"last_check_in" = "Dernier check-in : %@";
"next_check_in" = "Prochain check-in : %@";
"on_time_rate" = "%@ à l'heure";
"on_time_count" = "%d/%d à l'heure";
"tightest_finish" = "Fin la plus serrée : %@ avant la date limite.";
"date_today" = "Aujourd'hui";
"date_yesterday" = "Hier";
"date_never" = "Jamais";
"calendar_event_title" = "%@: Check-in mensuel";
"calendar_event_notes" = "Ouvrez %@ et terminez votre check-in mensuel.";
"checkin_enjoying_dialog_title" = "À quel point appréciez-vous Portfolio Journal ?";
"checkin_enjoying_dialog_message" = "Félicitations pour votre nouveau succès. Votre avis nous aide à nous améliorer.";
"not_now" = "Pas maintenant";
"rating_1_star" = "1 étoile";
"rating_n_stars" = "%d étoiles";
"app_store_review_title" = "Souhaitez-vous laisser un avis sur l'App Store ?";
"app_store_review_message" = "Merci pour les 5 étoiles. Cela aide vraiment d'autres investisseurs à découvrir l'app.";
"write_review" = "Écrire un avis";
"save_1_snapshot" = "Enregistrer 1 snapshot";
"save_n_snapshots" = "Enregistrer %d snapshots";
"checkin_update_month" = "Mettre à jour %@";
"checkin_start_new" = "Commencer";
"Home" = "Accueil";
"Sources" = "Sources";
"Charts" = "Graphiques";
"Settings" = "Réglages";
"Journal" = "Journal";
"Search monthly notes" = "Rechercher des notes mensuelles";
"Monthly Check-ins" = "Check-ins mensuels";
"No monthly notes yet." = "Aucune note mensuelle pour l'instant.";
"No matching notes." = "Aucune note correspondante.";
"Jump to month" = "Aller au mois";
"Today" = "Aujourd'hui";
"Mood not set" = "Humeur non définie";
"No rating" = "Aucune note";
"No note yet." = "Pas encore de note.";
"Monthly Note" = "Note mensuelle";
"Open Full Note" = "Ouvrir la note complète";
"Duplicate Previous" = "Dupliquer le précédent";
"Save" = "Enregistrer";
"Monthly Check-in" = "Check-in mensuel";
"This Month" = "Ce mois-ci";
"No check-in yet this month" = "Aucun check-in ce mois-ci";
"Start your first check-in anytime." = "Commencez votre premier check-in quand vous voulez.";
"Mark Check-in Complete" = "Marquer le check-in comme terminé";
"Editing stays open. New check-ins unlock after 70% of the month." = "L'édition reste ouverte. Les nouveaux check-ins se débloquent après 70 % du mois.";
"Momentum & Streaks" = "Élan et séries";
"Log a check-in to start a streak" = "Enregistrez un check-in pour démarrer une série";
"Streak" = "Série";
"On-time in a row" = "À l'heure d'affilée";
"Best" = "Meilleur";
"Personal best" = "Meilleur record";
"Avg early" = "Marge moyenne";
"vs deadline" = "vs date limite";
"On-time score" = "Score de ponctualité";
"Achievements" = "Succès";
"View all achievements" = "Voir tous les succès";
"Monthly Pulse" = "Pouls mensuel";
"Optional" = "Optionnel";
"Rate this month" = "Notez ce mois";
"How did it feel?" = "Comment cela s'est-il passé ?";
"Monthly Summary" = "Résumé mensuel";
"Starting" = "Départ";
"Ending" = "Fin";
"Contributions" = "Contributions";
"Net Performance" = "Performance nette";
"Update Sources" = "Mettre à jour les sources";
"Add sources to start your monthly check-in." = "Ajoutez des sources pour commencer votre check-in mensuel.";
"Updated this cycle" = "Mis à jour sur ce cycle";
"Needs update" = "Nécessite une mise à jour";
"Snapshot Notes" = "Notes des snapshots";
"No snapshot notes for this month." = "Aucune note de snapshot pour ce mois.";
"Source" = "Source";
"Your full portfolio,\nfully clear" = "Votre portefeuille complet,\nen toute clarté";
"One payment. Every feature. Forever." = "Un paiement. Toutes les fonctionnalités. Pour toujours.";
"Get Full Access" = "Obtenir l'accès complet";
"Restore Purchases" = "Restaurer les achats";
"Payment charged to your Apple ID account." = "Le paiement sera facturé à votre compte Apple ID.";
"Terms" = "Conditions";
"Privacy" = "Confidentialité";
"· one-time · Family Sharing" = "· achat unique · Partage familial";
"Full access, one payment" = "Accès complet, un paiement";
"Unlimited sources, advanced charts & more" = "Sources illimitées, graphiques avancés et plus";
"See full access" = "Voir l'accès complet";
"Batch Update" = "Mise à jour groupée";
"Current value" = "Valeur actuelle";
"Contribution this period (optional)" = "Contribution sur cette période (optionnelle)";
"Include Contribution" = "Inclure une contribution";
"New capital added" = "Nouveau capital ajouté";
"Contribution (Optional)" = "Contribution (optionnelle)";
"Track new capital added to separate it from investment growth." = "Suivez le nouveau capital ajouté pour le distinguer de la croissance de l'investissement.";
"Monthly Highlights" = "Temps forts du mois";
"Best Performer" = "Meilleure performance";
"Worst Performer" = "Pire performance";
"Best Contributor" = "Meilleur contributeur";
"Update Check-in" = "Mettre à jour le check-in";
"Completed %@" = "Terminé %@";
@@ -0,0 +1,294 @@
"app_name" = "Portfolio Journal";
"ok" = "OK";
"cancel" = "Annulla";
"save" = "Salva";
"delete" = "Elimina";
"edit" = "Modifica";
"add" = "Aggiungi";
"done" = "Fatto";
"close" = "Chiudi";
"continue" = "Continua";
"skip" = "Salta";
"error" = "Errore";
"success" = "Successo";
"loading" = "Caricamento...";
"loading_data" = "Caricamento dei tuoi dati...";
"tab_dashboard" = "Home";
"tab_sources" = "Fonti";
"tab_charts" = "Grafici";
"tab_settings" = "Impostazioni";
"dashboard_title" = "Home";
"total_portfolio_value" = "Valore totale del portafoglio";
"today" = "oggi";
"returns" = "Rendimenti";
"by_category" = "Per categoria";
"pending_updates" = "Aggiornamenti in sospeso";
"see_all" = "Vedi tutto";
"sources_title" = "Fonti";
"add_source" = "Aggiungi fonte";
"source_name" = "Nome fonte";
"select_category" = "Seleziona categoria";
"initial_value" = "Valore iniziale";
"initial_value_optional" = "Valore iniziale (opzionale)";
"reminder_frequency" = "Frequenza promemoria";
"source_limit_warning" = "Limite di fonti raggiunto. Passa a Premium per fonti illimitate.";
"no_sources" = "Nessuna fonte di investimento";
"no_sources_message" = "Aggiungi la tua prima fonte di investimento per iniziare a monitorare il portafoglio.";
"add_snapshot" = "Aggiungi snapshot";
"edit_snapshot" = "Modifica snapshot";
"snapshot_date" = "Data";
"snapshot_value" = "Valore";
"snapshot_contribution" = "Contributo";
"contribution_optional" = "Contributo (opzionale)";
"notes" = "Note";
"notes_optional" = "Note (opzionali)";
"previous_value" = "Precedente: %@";
"change_from_previous" = "Variazione rispetto al precedente";
"charts_title" = "Grafici";
"evolution" = "Evoluzione";
"allocation" = "Allocazione";
"performance" = "Performance";
"drawdown" = "Drawdown";
"volatility" = "Volatilità";
"prediction" = "Previsione";
"portfolio_evolution" = "Evoluzione del portafoglio";
"asset_allocation" = "Allocazione degli asset";
"performance_by_category" = "Performance per categoria";
"drawdown_analysis" = "Analisi del drawdown";
"prediction_12_month" = "Previsione a 12 mesi";
"not_enough_data" = "Dati insufficienti";
"cagr" = "CAGR";
"twr" = "TWR";
"max_drawdown" = "Drawdown massimo";
"sharpe_ratio" = "Indice di Sharpe";
"win_rate" = "Tasso di successo";
"avg_monthly" = "Media mensile";
"best_month" = "Mese migliore";
"worst_month" = "Mese peggiore";
"premium" = "Premium";
"upgrade_to_premium" = "Passa a Premium";
"unlock_full_potential" = "Sblocca tutto il potenziale";
"one_time_purchase" = "Acquisto una tantum";
"includes_family_sharing" = "Include la condivisione in famiglia";
"upgrade_now" = "Aggiorna ora";
"restore_purchases" = "Ripristina acquisti";
"premium_active" = "Premium attivo";
"premium_feature" = "Funzione Premium";
"unlock" = "Sblocca";
"feature_unlimited_sources" = "Fonti illimitate";
"feature_unlimited_sources_desc" = "Monitora tutti gli investimenti che vuoi";
"feature_full_history" = "Cronologia completa";
"feature_full_history_desc" = "Accedi a tutta la tua cronologia di investimento";
"feature_advanced_charts" = "Grafici avanzati";
"feature_advanced_charts_desc" = "5 tipi di grafici analitici dettagliati";
"feature_predictions" = "Previsioni";
"feature_predictions_desc" = "Previsioni a 12 mesi basate su IA";
"feature_export" = "Esporta dati";
"feature_export_desc" = "Esporta in CSV e JSON";
"feature_no_ads" = "Niente pubblicità";
"feature_no_ads_desc" = "Esperienza senza pubblicità per sempre";
"paywall_benefit_history_title" = "La tua cronologia completa";
"paywall_benefit_history_subtitle" = "Ogni snapshot, contributo e guadagno dal primo giorno";
"paywall_benefit_charts_title" = "Grafici che rivelano schemi";
"paywall_benefit_charts_subtitle" = "Allocazione, drawdown, performance: tutto in un unico posto";
"paywall_benefit_forecasts_title" = "Previsioni a 12 mesi";
"paywall_benefit_forecasts_subtitle" = "Guarda dove probabilmente andrà il tuo portafoglio";
"paywall_benefit_noads_title" = "Niente pubblicità, mai";
"paywall_benefit_noads_subtitle" = "Esperienza pulita e focalizzata, senza distrazioni";
"settings_title" = "Impostazioni";
"subscription" = "Abbonamento";
"notifications" = "Notifiche";
"default_reminder_time" = "Ora promemoria predefinita";
"data" = "Dati";
"export_data" = "Esporta dati";
"total_sources" = "Fonti totali";
"total_snapshots" = "Snapshot totali";
"storage_used" = "Spazio utilizzato";
"about" = "Info";
"version" = "Versione";
"privacy_policy" = "Informativa sulla privacy";
"terms_of_service" = "Termini di servizio";
"support" = "Supporto";
"rate_app" = "Valuta l'app";
"danger_zone" = "Area pericolosa";
"reset_all_data" = "Reimposta tutti i dati";
"reset_confirmation" = "Questo eliminerà definitivamente tutti i dati di investimento. L'azione non può essere annullata.";
"frequency_monthly" = "Mensile";
"frequency_quarterly" = "Trimestrale";
"frequency_semiannual" = "Semestrale";
"frequency_annual" = "Annuale";
"frequency_custom" = "Personalizzata";
"frequency_never" = "Mai";
"every_n_months" = "Ogni %d mese/i";
"category_stocks" = "Azioni";
"category_bonds" = "Obbligazioni";
"category_real_estate" = "Immobiliare";
"category_crypto" = "Cripto";
"category_cash" = "Liquidità";
"category_etfs" = "ETF";
"category_retirement" = "Pensione";
"category_other" = "Altro";
"uncategorized" = "Senza categoria";
"time_1m" = "1M";
"time_3m" = "3M";
"time_6m" = "6M";
"time_1y" = "1A";
"time_all" = "Tutto";
"export_format" = "Seleziona formato";
"export_csv" = "CSV";
"export_csv_desc" = "Compatibile con Excel e Google Sheets";
"export_json" = "JSON";
"export_json_desc" = "Struttura dati completa per backup";
"onboarding_track_title" = "Monitora i tuoi investimenti";
"onboarding_track_desc" = "Controlla tutte le tue fonti di investimento in un unico posto. Azioni, obbligazioni, immobiliare, cripto e altro.";
"onboarding_visualize_title" = "Visualizza la tua crescita";
"onboarding_visualize_desc" = "Bellissimi grafici mostrano l'evoluzione, l'allocazione e la performance del portafoglio nel tempo.";
"onboarding_reminders_title" = "Non perdere mai un aggiornamento";
"onboarding_reminders_desc" = "Imposta promemoria per monitorare regolarmente i tuoi investimenti. Mensili, trimestrali o personalizzati.";
"onboarding_sync_title" = "Sincronizza ovunque";
"onboarding_sync_desc" = "I tuoi dati si sincronizzano automaticamente tramite iCloud su tutti i tuoi dispositivi Apple.";
"get_started" = "Inizia";
"onboarding_clarity_title" = "Sai esattamente a che punto sei";
"onboarding_clarity_desc" = "Vedi il tuo patrimonio totale, i rendimenti reali e l'allocazione, sempre aggiornati.";
"onboarding_habit_title" = "Bastano 5 minuti al mese";
"onboarding_habit_desc" = "Registra i tuoi valori una volta al mese. Portfolio Journal fa i calcoli e mostra i tuoi progressi.";
"onboarding_calm_title" = "Ignora il rumore. Segui il trend.";
"onboarding_calm_desc" = "Le oscillazioni giornaliere non raccontano la storia vera. La tua crescita in mesi e anni sì.";
"onboarding_goals_title" = "Raggiungi i tuoi obiettivi finanziari";
"onboarding_goals_desc" = "Imposta obiettivi, segui le tappe e vedi esattamente quanto hai fatto.";
"error_generic" = "Si è verificato un errore. Riprova.";
"error_no_purchases" = "Nessun acquisto da ripristinare";
"error_purchase_failed" = "Acquisto non riuscito: %@";
"error_export_failed" = "Esportazione non riuscita. Riprova.";
"placeholder_source_name" = "es. Vanguard 401k";
"placeholder_value" = "0.00";
"placeholder_notes" = "Aggiungi note...";
"mood_energized_title" = "Carico";
"mood_confident_title" = "Fiducioso";
"mood_balanced_title" = "Equilibrato";
"mood_cautious_title" = "Cauto";
"mood_stressed_title" = "Stressato";
"mood_energized_detail" = "Mi sento imbattibile";
"mood_confident_detail" = "In carreggiata e lucido";
"mood_balanced_detail" = "Calmo e paziente";
"mood_cautious_detail" = "Osservo i movimenti";
"mood_stressed_detail" = "Ho bisogno di resettare";
"achievement_streak_3_title" = "Serie di 3 mesi";
"achievement_streak_3_detail" = "Hai mantenuto i check-in puntuali per tre mesi di fila.";
"achievement_streak_6_title" = "Serie di mezzo anno";
"achievement_streak_6_detail" = "Sei check-in consecutivi puntuali.";
"achievement_streak_12_title" = "Un anno di slancio";
"achievement_streak_12_detail" = "Un anno intero senza perdere la scadenza.";
"achievement_perfect_on_time_title" = "Mai in ritardo";
"achievement_perfect_on_time_detail" = "Ogni check-in è arrivato prima della scadenza.";
"achievement_clutch_finish_title" = "Finale al limite";
"achievement_clutch_finish_detail" = "Inviato con poche ore di margine ma comunque in tempo.";
"achievement_early_bird_title" = "Mattiniero";
"achievement_early_bird_detail" = "In media completi tutto con molto margine.";
"achievements_title" = "Obiettivi";
"achievements_view_all" = "Vedi tutti gli obiettivi";
"achievements_nav_title" = "Obiettivi";
"achievements_progress_title" = "Progressi";
"achievements_unlocked_title" = "Sbloccati";
"achievements_unlocked_empty" = "Completa i check-in per sbloccare obiettivi.";
"achievements_locked_title" = "Bloccati";
"achievements_locked_empty" = "Tutti gli obiettivi sbloccati. Ottimo lavoro.";
"rating_accessibility" = "Valutazione %d su 5";
"achievements_unlocked_count" = "%d su %d sbloccati";
"last_check_in" = "Ultimo check-in: %@";
"next_check_in" = "Prossimo check-in: %@";
"on_time_rate" = "%@ puntuale";
"on_time_count" = "%d/%d in tempo";
"tightest_finish" = "Chiusura più tirata: %@ prima della scadenza.";
"date_today" = "Oggi";
"date_yesterday" = "Ieri";
"date_never" = "Mai";
"calendar_event_title" = "%@: Check-in mensile";
"calendar_event_notes" = "Apri %@ e completa il tuo check-in mensile.";
"checkin_enjoying_dialog_title" = "Quanto ti piace Portfolio Journal?";
"checkin_enjoying_dialog_message" = "Congratulazioni per il tuo nuovo obiettivo. Il tuo feedback ci aiuta a migliorare.";
"not_now" = "Non ora";
"rating_1_star" = "1 stella";
"rating_n_stars" = "%d stelle";
"app_store_review_title" = "Vuoi lasciare una recensione sull'App Store?";
"app_store_review_message" = "Grazie per le 5 stelle. Aiuta davvero altri investitori a scoprire l'app.";
"write_review" = "Scrivi una recensione";
"save_1_snapshot" = "Salva 1 snapshot";
"save_n_snapshots" = "Salva %d snapshot";
"checkin_update_month" = "Aggiorna %@";
"checkin_start_new" = "Inizia";
"Home" = "Home";
"Sources" = "Fonti";
"Charts" = "Grafici";
"Settings" = "Impostazioni";
"Journal" = "Diario";
"Search monthly notes" = "Cerca note mensili";
"Monthly Check-ins" = "Check-in mensili";
"No monthly notes yet." = "Ancora nessuna nota mensile.";
"No matching notes." = "Nessuna nota corrispondente.";
"Jump to month" = "Vai al mese";
"Today" = "Oggi";
"Mood not set" = "Stato d'animo non impostato";
"No rating" = "Nessuna valutazione";
"No note yet." = "Ancora nessuna nota.";
"Monthly Note" = "Nota mensile";
"Open Full Note" = "Apri nota completa";
"Duplicate Previous" = "Duplica precedente";
"Save" = "Salva";
"Monthly Check-in" = "Check-in mensile";
"This Month" = "Questo mese";
"No check-in yet this month" = "Nessun check-in questo mese";
"Start your first check-in anytime." = "Inizia il tuo primo check-in quando vuoi.";
"Mark Check-in Complete" = "Segna check-in come completato";
"Editing stays open. New check-ins unlock after 70% of the month." = "La modifica resta aperta. I nuovi check-in si sbloccano dopo il 70 % del mese.";
"Momentum & Streaks" = "Slancio e serie";
"Log a check-in to start a streak" = "Registra un check-in per iniziare una serie";
"Streak" = "Serie";
"On-time in a row" = "In tempo di fila";
"Best" = "Migliore";
"Personal best" = "Record personale";
"Avg early" = "Anticipo medio";
"vs deadline" = "vs scadenza";
"On-time score" = "Punteggio puntualità";
"Achievements" = "Obiettivi";
"View all achievements" = "Vedi tutti gli obiettivi";
"Monthly Pulse" = "Polso mensile";
"Optional" = "Opzionale";
"Rate this month" = "Valuta questo mese";
"How did it feel?" = "Come ti sei sentito?";
"Monthly Summary" = "Riepilogo mensile";
"Starting" = "Inizio";
"Ending" = "Fine";
"Contributions" = "Contributi";
"Net Performance" = "Performance netta";
"Update Sources" = "Aggiorna fonti";
"Add sources to start your monthly check-in." = "Aggiungi fonti per iniziare il tuo check-in mensile.";
"Updated this cycle" = "Aggiornato in questo ciclo";
"Needs update" = "Da aggiornare";
"Snapshot Notes" = "Note degli snapshot";
"No snapshot notes for this month." = "Nessuna nota snapshot per questo mese.";
"Source" = "Fonte";
"Your full portfolio,\nfully clear" = "Il tuo intero portafoglio,\ntutto chiaro";
"One payment. Every feature. Forever." = "Un pagamento. Ogni funzione. Per sempre.";
"Get Full Access" = "Ottieni accesso completo";
"Restore Purchases" = "Ripristina acquisti";
"Payment charged to your Apple ID account." = "Il pagamento verrà addebitato sul tuo account Apple ID.";
"Terms" = "Termini";
"Privacy" = "Privacy";
"· one-time · Family Sharing" = "· una tantum · In famiglia";
"Full access, one payment" = "Accesso completo, un solo pagamento";
"Unlimited sources, advanced charts & more" = "Fonti illimitate, grafici avanzati e altro";
"See full access" = "Vedi accesso completo";
"Batch Update" = "Aggiornamento in blocco";
"Current value" = "Valore attuale";
"Contribution this period (optional)" = "Contributo di questo periodo (opzionale)";
"Include Contribution" = "Includi contributo";
"New capital added" = "Nuovo capitale aggiunto";
"Contribution (Optional)" = "Contributo (opzionale)";
"Track new capital added to separate it from investment growth." = "Tieni traccia del nuovo capitale aggiunto per separarlo dalla crescita dell'investimento.";
"Monthly Highlights" = "Punti salienti del mese";
"Best Performer" = "Miglior performance";
"Worst Performer" = "Peggior performance";
"Best Contributor" = "Maggior contributore";
"Update Check-in" = "Aggiorna check-in";
"Completed %@" = "Completato %@";
@@ -0,0 +1,294 @@
"app_name" = "Portfolio Journal";
"ok" = "OK";
"cancel" = "キャンセル";
"save" = "保存";
"delete" = "削除";
"edit" = "編集";
"add" = "追加";
"done" = "完了";
"close" = "閉じる";
"continue" = "続ける";
"skip" = "スキップ";
"error" = "エラー";
"success" = "成功";
"loading" = "読み込み中...";
"loading_data" = "データを読み込み中...";
"tab_dashboard" = "ホーム";
"tab_sources" = "ソース";
"tab_charts" = "チャート";
"tab_settings" = "設定";
"dashboard_title" = "ホーム";
"total_portfolio_value" = "ポートフォリオ合計額";
"today" = "今日";
"returns" = "リターン";
"by_category" = "カテゴリ別";
"pending_updates" = "未更新";
"see_all" = "すべて表示";
"sources_title" = "ソース";
"add_source" = "ソースを追加";
"source_name" = "ソース名";
"select_category" = "カテゴリを選択";
"initial_value" = "初期金額";
"initial_value_optional" = "初期金額(任意)";
"reminder_frequency" = "リマインダー頻度";
"source_limit_warning" = "ソース数の上限に達しました。Premium にアップグレードすると無制限になります。";
"no_sources" = "投資ソースがありません";
"no_sources_message" = "最初の投資ソースを追加してポートフォリオの追跡を始めましょう。";
"add_snapshot" = "スナップショットを追加";
"edit_snapshot" = "スナップショットを編集";
"snapshot_date" = "日付";
"snapshot_value" = "金額";
"snapshot_contribution" = "追加資金";
"contribution_optional" = "追加資金(任意)";
"notes" = "メモ";
"notes_optional" = "メモ(任意)";
"previous_value" = "前回: %@";
"change_from_previous" = "前回からの変化";
"charts_title" = "チャート";
"evolution" = "推移";
"allocation" = "配分";
"performance" = "パフォーマンス";
"drawdown" = "ドローダウン";
"volatility" = "ボラティリティ";
"prediction" = "予測";
"portfolio_evolution" = "ポートフォリオ推移";
"asset_allocation" = "資産配分";
"performance_by_category" = "カテゴリ別パフォーマンス";
"drawdown_analysis" = "ドローダウン分析";
"prediction_12_month" = "12か月予測";
"not_enough_data" = "データが不足しています";
"cagr" = "CAGR";
"twr" = "TWR";
"max_drawdown" = "最大ドローダウン";
"sharpe_ratio" = "シャープレシオ";
"win_rate" = "勝率";
"avg_monthly" = "月平均";
"best_month" = "最高の月";
"worst_month" = "最悪の月";
"premium" = "Premium";
"upgrade_to_premium" = "Premium にアップグレード";
"unlock_full_potential" = "すべての機能を解放";
"one_time_purchase" = "買い切り";
"includes_family_sharing" = "ファミリー共有対応";
"upgrade_now" = "今すぐアップグレード";
"restore_purchases" = "購入を復元";
"premium_active" = "Premium 有効";
"premium_feature" = "Premium 機能";
"unlock" = "ロック解除";
"feature_unlimited_sources" = "無制限のソース";
"feature_unlimited_sources_desc" = "好きなだけ投資を追跡できます";
"feature_full_history" = "完全な履歴";
"feature_full_history_desc" = "投資履歴をすべて確認できます";
"feature_advanced_charts" = "高度なチャート";
"feature_advanced_charts_desc" = "詳細分析チャート 5 種類";
"feature_predictions" = "予測";
"feature_predictions_desc" = "AI による12か月予測";
"feature_export" = "データを書き出し";
"feature_export_desc" = "CSV と JSON に書き出し";
"feature_no_ads" = "広告なし";
"feature_no_ads_desc" = "ずっと広告なしで使えます";
"paywall_benefit_history_title" = "完全な履歴";
"paywall_benefit_history_subtitle" = "初日からのすべての snapshot、追加資金、利益を確認";
"paywall_benefit_charts_title" = "傾向が見えるチャート";
"paywall_benefit_charts_subtitle" = "配分、ドローダウン、パフォーマンスを一か所で確認";
"paywall_benefit_forecasts_title" = "12か月予測";
"paywall_benefit_forecasts_subtitle" = "ポートフォリオの行き先を見通せます";
"paywall_benefit_noads_title" = "広告は一切なし";
"paywall_benefit_noads_subtitle" = "気が散らない、集中できる体験";
"settings_title" = "設定";
"subscription" = "サブスクリプション";
"notifications" = "通知";
"default_reminder_time" = "デフォルトの通知時刻";
"data" = "データ";
"export_data" = "データを書き出し";
"total_sources" = "ソース合計";
"total_snapshots" = "スナップショット合計";
"storage_used" = "使用ストレージ";
"about" = "このアプリについて";
"version" = "バージョン";
"privacy_policy" = "プライバシーポリシー";
"terms_of_service" = "利用規約";
"support" = "サポート";
"rate_app" = "アプリを評価";
"danger_zone" = "危険ゾーン";
"reset_all_data" = "すべてのデータをリセット";
"reset_confirmation" = "すべての投資データが完全に削除されます。この操作は元に戻せません。";
"frequency_monthly" = "毎月";
"frequency_quarterly" = "四半期ごと";
"frequency_semiannual" = "半年ごと";
"frequency_annual" = "毎年";
"frequency_custom" = "カスタム";
"frequency_never" = "なし";
"every_n_months" = "%d か月ごと";
"category_stocks" = "株式";
"category_bonds" = "債券";
"category_real_estate" = "不動産";
"category_crypto" = "暗号資産";
"category_cash" = "現金";
"category_etfs" = "ETF";
"category_retirement" = "退職資産";
"category_other" = "その他";
"uncategorized" = "未分類";
"time_1m" = "1M";
"time_3m" = "3M";
"time_6m" = "6M";
"time_1y" = "1Y";
"time_all" = "すべて";
"export_format" = "形式を選択";
"export_csv" = "CSV";
"export_csv_desc" = "Excel、Google Sheets に対応";
"export_json" = "JSON";
"export_json_desc" = "バックアップ用の完全なデータ構造";
"onboarding_track_title" = "投資をまとめて管理";
"onboarding_track_desc" = "株式、債券、不動産、暗号資産など、すべての投資ソースを一か所で追跡できます。";
"onboarding_visualize_title" = "成長を可視化";
"onboarding_visualize_desc" = "美しいチャートでポートフォリオの推移、配分、パフォーマンスを確認できます。";
"onboarding_reminders_title" = "更新を忘れない";
"onboarding_reminders_desc" = "毎月、四半期、またはカスタムで、定期的に投資を記録するためのリマインダーを設定できます。";
"onboarding_sync_title" = "どこでも同期";
"onboarding_sync_desc" = "データは iCloud で自動同期され、すべての Apple デバイスで利用できます。";
"get_started" = "始める";
"onboarding_clarity_title" = "今の立ち位置がすぐ分かる";
"onboarding_clarity_desc" = "総資産、実質リターン、配分をいつでも最新の状態で確認できます。";
"onboarding_habit_title" = "月に5分で十分";
"onboarding_habit_desc" = "月に一度金額を入力するだけ。計算は Portfolio Journal が行い、進捗を見せてくれます。";
"onboarding_calm_title" = "ノイズを無視して、流れを見る。";
"onboarding_calm_desc" = "日々の値動きは本当の物語ではありません。数か月、数年の成長こそが大切です。";
"onboarding_goals_title" = "お金の目標に近づく";
"onboarding_goals_desc" = "目標を設定し、節目を追跡し、どこまで進んだかを把握できます。";
"error_generic" = "エラーが発生しました。もう一度お試しください。";
"error_no_purchases" = "復元できる購入が見つかりません";
"error_purchase_failed" = "購入に失敗しました: %@";
"error_export_failed" = "書き出しに失敗しました。もう一度お試しください。";
"placeholder_source_name" = "例: Vanguard 401k";
"placeholder_value" = "0.00";
"placeholder_notes" = "メモを追加...";
"mood_energized_title" = "絶好調";
"mood_confident_title" = "自信あり";
"mood_balanced_title" = "安定";
"mood_cautious_title" = "慎重";
"mood_stressed_title" = "ストレス";
"mood_energized_detail" = "無敵な気分";
"mood_confident_detail" = "順調で落ち着いている";
"mood_balanced_detail" = "冷静で辛抱強い";
"mood_cautious_detail" = "動きを見守っている";
"mood_stressed_detail" = "一度リセットしたい";
"achievement_streak_3_title" = "3か月連続";
"achievement_streak_3_detail" = "3か月連続で期限内にチェックインしました。";
"achievement_streak_6_title" = "半年連続";
"achievement_streak_6_detail" = "6回連続で期限内にチェックインしました。";
"achievement_streak_12_title" = "1年の勢い";
"achievement_streak_12_detail" = "1年間、一度も期限を逃しませんでした。";
"achievement_perfect_on_time_title" = "一度も遅れなし";
"achievement_perfect_on_time_detail" = "すべてのチェックインが期限前に完了しました。";
"achievement_clutch_finish_title" = "ギリギリ成功";
"achievement_clutch_finish_detail" = "残り数時間で提出、それでも期限内。";
"achievement_early_bird_title" = "早め派";
"achievement_early_bird_detail" = "平均するとかなり余裕を持って終えています。";
"achievements_title" = "実績";
"achievements_view_all" = "すべての実績を見る";
"achievements_nav_title" = "実績";
"achievements_progress_title" = "進捗";
"achievements_unlocked_title" = "解除済み";
"achievements_unlocked_empty" = "チェックインを完了して実績を解除しましょう。";
"achievements_locked_title" = "未解除";
"achievements_locked_empty" = "すべての実績を解除しました。すばらしいです。";
"rating_accessibility" = "評価 %d / 5";
"achievements_unlocked_count" = "%d / %d を解除";
"last_check_in" = "前回のチェックイン: %@";
"next_check_in" = "次回のチェックイン: %@";
"on_time_rate" = "%@ が期限内";
"on_time_count" = "%d/%d が期限内";
"tightest_finish" = "最もギリギリだった完了: 締切の %@ 前。";
"date_today" = "今日";
"date_yesterday" = "昨日";
"date_never" = "なし";
"calendar_event_title" = "%@: 月次チェックイン";
"calendar_event_notes" = "%@ を開いて月次チェックインを完了してください。";
"checkin_enjoying_dialog_title" = "Portfolio Journal をどのくらい気に入っていますか?";
"checkin_enjoying_dialog_message" = "新しい実績おめでとうございます。ご意見は改善に役立ちます。";
"not_now" = "今はしない";
"rating_1_star" = "1つ星";
"rating_n_stars" = "%dつ星";
"app_store_review_title" = "App Store にレビューを残しますか?";
"app_store_review_message" = "5つ星ありがとうございます。ほかの投資家がアプリを見つけやすくなります。";
"write_review" = "レビューを書く";
"save_1_snapshot" = "1件の snapshot を保存";
"save_n_snapshots" = "%d件の snapshot を保存";
"checkin_update_month" = "%@ を更新";
"checkin_start_new" = "開始";
"Home" = "ホーム";
"Sources" = "ソース";
"Charts" = "チャート";
"Settings" = "設定";
"Journal" = "ジャーナル";
"Search monthly notes" = "月次メモを検索";
"Monthly Check-ins" = "月次チェックイン";
"No monthly notes yet." = "まだ月次メモがありません。";
"No matching notes." = "一致するメモがありません。";
"Jump to month" = "月へ移動";
"Today" = "今日";
"Mood not set" = "気分未設定";
"No rating" = "評価なし";
"No note yet." = "まだメモがありません。";
"Monthly Note" = "月次メモ";
"Open Full Note" = "全文を開く";
"Duplicate Previous" = "前回を複製";
"Save" = "保存";
"Monthly Check-in" = "月次チェックイン";
"This Month" = "今月";
"No check-in yet this month" = "今月はまだチェックインがありません";
"Start your first check-in anytime." = "いつでも最初のチェックインを始められます。";
"Mark Check-in Complete" = "チェックインを完了にする";
"Editing stays open. New check-ins unlock after 70% of the month." = "編集は開いたままです。新しいチェックインは月の70%経過後に解放されます。";
"Momentum & Streaks" = "勢いと連続記録";
"Log a check-in to start a streak" = "チェックインを記録して連続記録を始めましょう";
"Streak" = "連続記録";
"On-time in a row" = "連続期限内";
"Best" = "最高";
"Personal best" = "自己ベスト";
"Avg early" = "平均余裕";
"vs deadline" = "締切比";
"On-time score" = "期限内スコア";
"Achievements" = "実績";
"View all achievements" = "すべての実績を見る";
"Monthly Pulse" = "月次パルス";
"Optional" = "任意";
"Rate this month" = "今月を評価";
"How did it feel?" = "どんな気分でしたか?";
"Monthly Summary" = "月次サマリー";
"Starting" = "開始";
"Ending" = "終了";
"Contributions" = "追加資金";
"Net Performance" = "純パフォーマンス";
"Update Sources" = "ソースを更新";
"Add sources to start your monthly check-in." = "月次チェックインを始めるにはソースを追加してください。";
"Updated this cycle" = "このサイクルで更新済み";
"Needs update" = "更新が必要";
"Snapshot Notes" = "スナップショットのメモ";
"No snapshot notes for this month." = "今月のスナップショットメモはありません。";
"Source" = "ソース";
"Your full portfolio,\nfully clear" = "あなたのポートフォリオ全体を、\nもっと明確に";
"One payment. Every feature. Forever." = "一度の支払い。すべての機能を。ずっと。";
"Get Full Access" = "フルアクセスを取得";
"Restore Purchases" = "購入を復元";
"Payment charged to your Apple ID account." = "料金は Apple ID アカウントに請求されます。";
"Terms" = "利用規約";
"Privacy" = "プライバシー";
"· one-time · Family Sharing" = "· 買い切り · ファミリー共有";
"Full access, one payment" = "フルアクセスを一度の支払いで";
"Unlimited sources, advanced charts & more" = "無制限のソース、高度なチャートなど";
"See full access" = "フルアクセスを見る";
"Batch Update" = "一括更新";
"Current value" = "現在の金額";
"Contribution this period (optional)" = "今回の追加資金(任意)";
"Include Contribution" = "追加資金を含める";
"New capital added" = "新たに追加した資金";
"Contribution (Optional)" = "追加資金(任意)";
"Track new capital added to separate it from investment growth." = "投資の成長と分けるため、新たに追加した資金を記録します。";
"Monthly Highlights" = "今月のハイライト";
"Best Performer" = "最高のパフォーマー";
"Worst Performer" = "最も低調だったもの";
"Best Contributor" = "最大の貢献元";
"Update Check-in" = "チェックインを更新";
"Completed %@" = "%@ に完了";
@@ -0,0 +1,294 @@
"app_name" = "Portfolio Journal";
"ok" = "OK";
"cancel" = "Cancelar";
"save" = "Salvar";
"delete" = "Excluir";
"edit" = "Editar";
"add" = "Adicionar";
"done" = "Concluído";
"close" = "Fechar";
"continue" = "Continuar";
"skip" = "Pular";
"error" = "Erro";
"success" = "Sucesso";
"loading" = "Carregando...";
"loading_data" = "Carregando seus dados...";
"tab_dashboard" = "Início";
"tab_sources" = "Fontes";
"tab_charts" = "Gráficos";
"tab_settings" = "Ajustes";
"dashboard_title" = "Início";
"total_portfolio_value" = "Valor total da carteira";
"today" = "hoje";
"returns" = "Retornos";
"by_category" = "Por categoria";
"pending_updates" = "Atualizações pendentes";
"see_all" = "Ver tudo";
"sources_title" = "Fontes";
"add_source" = "Adicionar fonte";
"source_name" = "Nome da fonte";
"select_category" = "Selecionar categoria";
"initial_value" = "Valor inicial";
"initial_value_optional" = "Valor inicial (opcional)";
"reminder_frequency" = "Frequência do lembrete";
"source_limit_warning" = "Limite de fontes atingido. Faça upgrade para Premium para fontes ilimitadas.";
"no_sources" = "Nenhuma fonte de investimento";
"no_sources_message" = "Adicione sua primeira fonte de investimento para começar a acompanhar sua carteira.";
"add_snapshot" = "Adicionar snapshot";
"edit_snapshot" = "Editar snapshot";
"snapshot_date" = "Data";
"snapshot_value" = "Valor";
"snapshot_contribution" = "Contribuição";
"contribution_optional" = "Contribuição (opcional)";
"notes" = "Notas";
"notes_optional" = "Notas (opcionais)";
"previous_value" = "Anterior: %@";
"change_from_previous" = "Mudança em relação ao anterior";
"charts_title" = "Gráficos";
"evolution" = "Evolução";
"allocation" = "Alocação";
"performance" = "Desempenho";
"drawdown" = "Drawdown";
"volatility" = "Volatilidade";
"prediction" = "Previsão";
"portfolio_evolution" = "Evolução da carteira";
"asset_allocation" = "Alocação de ativos";
"performance_by_category" = "Desempenho por categoria";
"drawdown_analysis" = "Análise de drawdown";
"prediction_12_month" = "Previsão de 12 meses";
"not_enough_data" = "Dados insuficientes";
"cagr" = "CAGR";
"twr" = "TWR";
"max_drawdown" = "Drawdown máximo";
"sharpe_ratio" = "Índice de Sharpe";
"win_rate" = "Taxa de acerto";
"avg_monthly" = "Média mensal";
"best_month" = "Melhor mês";
"worst_month" = "Pior mês";
"premium" = "Premium";
"upgrade_to_premium" = "Fazer upgrade para Premium";
"unlock_full_potential" = "Desbloqueie todo o potencial";
"one_time_purchase" = "Compra única";
"includes_family_sharing" = "Inclui Compartilhamento Familiar";
"upgrade_now" = "Fazer upgrade agora";
"restore_purchases" = "Restaurar compras";
"premium_active" = "Premium ativo";
"premium_feature" = "Recurso Premium";
"unlock" = "Desbloquear";
"feature_unlimited_sources" = "Fontes ilimitadas";
"feature_unlimited_sources_desc" = "Acompanhe quantos investimentos quiser";
"feature_full_history" = "Histórico completo";
"feature_full_history_desc" = "Acesse todo o seu histórico de investimentos";
"feature_advanced_charts" = "Gráficos avançados";
"feature_advanced_charts_desc" = "5 tipos de gráficos analíticos detalhados";
"feature_predictions" = "Previsões";
"feature_predictions_desc" = "Previsões de 12 meses com IA";
"feature_export" = "Exportar dados";
"feature_export_desc" = "Exporte para CSV e JSON";
"feature_no_ads" = "Sem anúncios";
"feature_no_ads_desc" = "Experiência sem anúncios para sempre";
"paywall_benefit_history_title" = "Seu histórico completo";
"paywall_benefit_history_subtitle" = "Cada snapshot, contribuição e ganho desde o primeiro dia";
"paywall_benefit_charts_title" = "Gráficos que revelam padrões";
"paywall_benefit_charts_subtitle" = "Alocação, drawdown, desempenho: tudo em um só lugar";
"paywall_benefit_forecasts_title" = "Previsões de 12 meses";
"paywall_benefit_forecasts_subtitle" = "Veja para onde sua carteira provavelmente está indo";
"paywall_benefit_noads_title" = "Sem anúncios, nunca";
"paywall_benefit_noads_subtitle" = "Experiência limpa e focada, sem distrações";
"settings_title" = "Ajustes";
"subscription" = "Assinatura";
"notifications" = "Notificações";
"default_reminder_time" = "Horário padrão do lembrete";
"data" = "Dados";
"export_data" = "Exportar dados";
"total_sources" = "Total de fontes";
"total_snapshots" = "Total de snapshots";
"storage_used" = "Armazenamento usado";
"about" = "Sobre";
"version" = "Versão";
"privacy_policy" = "Política de privacidade";
"terms_of_service" = "Termos de serviço";
"support" = "Suporte";
"rate_app" = "Avaliar app";
"danger_zone" = "Zona de perigo";
"reset_all_data" = "Redefinir todos os dados";
"reset_confirmation" = "Isso excluirá permanentemente todos os seus dados de investimento. Esta ação não pode ser desfeita.";
"frequency_monthly" = "Mensal";
"frequency_quarterly" = "Trimestral";
"frequency_semiannual" = "Semestral";
"frequency_annual" = "Anual";
"frequency_custom" = "Personalizada";
"frequency_never" = "Nunca";
"every_n_months" = "A cada %d mês(es)";
"category_stocks" = "Ações";
"category_bonds" = "Títulos";
"category_real_estate" = "Imóveis";
"category_crypto" = "Cripto";
"category_cash" = "Caixa";
"category_etfs" = "ETFs";
"category_retirement" = "Aposentadoria";
"category_other" = "Outro";
"uncategorized" = "Sem categoria";
"time_1m" = "1M";
"time_3m" = "3M";
"time_6m" = "6M";
"time_1y" = "1A";
"time_all" = "Tudo";
"export_format" = "Selecionar formato";
"export_csv" = "CSV";
"export_csv_desc" = "Compatível com Excel e Google Sheets";
"export_json" = "JSON";
"export_json_desc" = "Estrutura completa para backup";
"onboarding_track_title" = "Acompanhe seus investimentos";
"onboarding_track_desc" = "Monitore todas as suas fontes de investimento em um só lugar. Ações, títulos, imóveis, cripto e mais.";
"onboarding_visualize_title" = "Visualize seu crescimento";
"onboarding_visualize_desc" = "Belos gráficos mostram a evolução, alocação e desempenho da sua carteira ao longo do tempo.";
"onboarding_reminders_title" = "Nunca perca uma atualização";
"onboarding_reminders_desc" = "Defina lembretes para acompanhar seus investimentos regularmente. Mensal, trimestral ou personalizado.";
"onboarding_sync_title" = "Sincronize em todos os lugares";
"onboarding_sync_desc" = "Seus dados sincronizam automaticamente via iCloud em todos os seus dispositivos Apple.";
"get_started" = "Começar";
"onboarding_clarity_title" = "Saiba exatamente onde você está";
"onboarding_clarity_desc" = "Veja seu patrimônio total, retornos reais e alocação, sempre atualizados.";
"onboarding_habit_title" = "5 minutos por mês são suficientes";
"onboarding_habit_desc" = "Registre seus valores uma vez por mês. O Portfolio Journal faz as contas e mostra seu progresso.";
"onboarding_calm_title" = "Ignore o ruído. Acompanhe a tendência.";
"onboarding_calm_desc" = "Oscilações diárias não contam a história real. Seu crescimento ao longo de meses e anos conta.";
"onboarding_goals_title" = "Alcance seus objetivos financeiros";
"onboarding_goals_desc" = "Defina metas, acompanhe marcos e veja exatamente o quanto você avançou.";
"error_generic" = "Ocorreu um erro. Tente novamente.";
"error_no_purchases" = "Nenhuma compra encontrada para restaurar";
"error_purchase_failed" = "Compra falhou: %@";
"error_export_failed" = "A exportação falhou. Tente novamente.";
"placeholder_source_name" = "ex.: Vanguard 401k";
"placeholder_value" = "0.00";
"placeholder_notes" = "Adicionar notas...";
"mood_energized_title" = "A mil";
"mood_confident_title" = "Confiante";
"mood_balanced_title" = "Estável";
"mood_cautious_title" = "Cauteloso";
"mood_stressed_title" = "Estressado";
"mood_energized_detail" = "Me sentindo imbatível";
"mood_confident_detail" = "No caminho e tranquilo";
"mood_balanced_detail" = "Calmo e paciente";
"mood_cautious_detail" = "Observando os movimentos";
"mood_stressed_detail" = "Preciso respirar";
"achievement_streak_3_title" = "Sequência de 3 meses";
"achievement_streak_3_detail" = "Você manteve seus check-ins em dia por três meses seguidos.";
"achievement_streak_6_title" = "Boa fase de meio ano";
"achievement_streak_6_detail" = "Seis check-ins consecutivos no prazo.";
"achievement_streak_12_title" = "Um ano de ritmo";
"achievement_streak_12_detail" = "Um ano inteiro sem perder o prazo.";
"achievement_perfect_on_time_title" = "Nunca atrasado";
"achievement_perfect_on_time_detail" = "Todos os check-ins foram enviados antes do prazo.";
"achievement_clutch_finish_title" = "No limite";
"achievement_clutch_finish_detail" = "Enviado com poucas horas de sobra, mas ainda no prazo.";
"achievement_early_bird_title" = "Adiantado";
"achievement_early_bird_detail" = "Em média você termina com bastante tempo sobrando.";
"achievements_title" = "Conquistas";
"achievements_view_all" = "Ver todas as conquistas";
"achievements_nav_title" = "Conquistas";
"achievements_progress_title" = "Progresso";
"achievements_unlocked_title" = "Desbloqueadas";
"achievements_unlocked_empty" = "Complete check-ins para desbloquear conquistas.";
"achievements_locked_title" = "Bloqueadas";
"achievements_locked_empty" = "Todas as conquistas desbloqueadas. Bom trabalho.";
"rating_accessibility" = "Avaliação %d de 5";
"achievements_unlocked_count" = "%d de %d desbloqueadas";
"last_check_in" = "Último check-in: %@";
"next_check_in" = "Próximo check-in: %@";
"on_time_rate" = "%@ no prazo";
"on_time_count" = "%d/%d no prazo";
"tightest_finish" = "Fechamento mais apertado: %@ antes do prazo.";
"date_today" = "Hoje";
"date_yesterday" = "Ontem";
"date_never" = "Nunca";
"calendar_event_title" = "%@: Check-in mensal";
"calendar_event_notes" = "Abra %@ e conclua seu check-in mensal.";
"checkin_enjoying_dialog_title" = "Quanto você está gostando do Portfolio Journal?";
"checkin_enjoying_dialog_message" = "Parabéns pela sua nova conquista. Seu feedback nos ajuda a melhorar.";
"not_now" = "Agora não";
"rating_1_star" = "1 estrela";
"rating_n_stars" = "%d estrelas";
"app_store_review_title" = "Gostaria de deixar uma avaliação na App Store?";
"app_store_review_message" = "Obrigado pelas 5 estrelas. Isso realmente ajuda outros investidores a descobrir o app.";
"write_review" = "Escrever avaliação";
"save_1_snapshot" = "Salvar 1 snapshot";
"save_n_snapshots" = "Salvar %d snapshots";
"checkin_update_month" = "Atualizar %@";
"checkin_start_new" = "Iniciar";
"Home" = "Início";
"Sources" = "Fontes";
"Charts" = "Gráficos";
"Settings" = "Ajustes";
"Journal" = "Diário";
"Search monthly notes" = "Buscar notas mensais";
"Monthly Check-ins" = "Check-ins mensais";
"No monthly notes yet." = "Ainda não há notas mensais.";
"No matching notes." = "Nenhuma nota correspondente.";
"Jump to month" = "Ir para o mês";
"Today" = "Hoje";
"Mood not set" = "Humor não definido";
"No rating" = "Sem avaliação";
"No note yet." = "Ainda sem nota.";
"Monthly Note" = "Nota mensal";
"Open Full Note" = "Abrir nota completa";
"Duplicate Previous" = "Duplicar anterior";
"Save" = "Salvar";
"Monthly Check-in" = "Check-in mensal";
"This Month" = "Este mês";
"No check-in yet this month" = "Ainda não há check-in este mês";
"Start your first check-in anytime." = "Comece seu primeiro check-in quando quiser.";
"Mark Check-in Complete" = "Marcar check-in como concluído";
"Editing stays open. New check-ins unlock after 70% of the month." = "A edição permanece aberta. Novos check-ins são liberados após 70 % do mês.";
"Momentum & Streaks" = "Ritmo e sequências";
"Log a check-in to start a streak" = "Registre um check-in para começar uma sequência";
"Streak" = "Sequência";
"On-time in a row" = "No prazo em sequência";
"Best" = "Melhor";
"Personal best" = "Recorde pessoal";
"Avg early" = "Antecedência média";
"vs deadline" = "vs prazo";
"On-time score" = "Pontuação de prazo";
"Achievements" = "Conquistas";
"View all achievements" = "Ver todas as conquistas";
"Monthly Pulse" = "Pulso mensal";
"Optional" = "Opcional";
"Rate this month" = "Avalie este mês";
"How did it feel?" = "Como foi?";
"Monthly Summary" = "Resumo mensal";
"Starting" = "Início";
"Ending" = "Final";
"Contributions" = "Contribuições";
"Net Performance" = "Desempenho líquido";
"Update Sources" = "Atualizar fontes";
"Add sources to start your monthly check-in." = "Adicione fontes para começar seu check-in mensal.";
"Updated this cycle" = "Atualizado neste ciclo";
"Needs update" = "Precisa de atualização";
"Snapshot Notes" = "Notas de snapshot";
"No snapshot notes for this month." = "Não há notas de snapshot neste mês.";
"Source" = "Fonte";
"Your full portfolio,\nfully clear" = "Sua carteira completa,\ntotalmente clara";
"One payment. Every feature. Forever." = "Um pagamento. Todos os recursos. Para sempre.";
"Get Full Access" = "Obter acesso completo";
"Restore Purchases" = "Restaurar compras";
"Payment charged to your Apple ID account." = "O pagamento será cobrado na sua conta Apple ID.";
"Terms" = "Termos";
"Privacy" = "Privacidade";
"· one-time · Family Sharing" = "· pagamento único · Compartilhamento Familiar";
"Full access, one payment" = "Acesso total, um pagamento";
"Unlimited sources, advanced charts & more" = "Fontes ilimitadas, gráficos avançados e mais";
"See full access" = "Ver acesso completo";
"Batch Update" = "Atualização em lote";
"Current value" = "Valor atual";
"Contribution this period (optional)" = "Contribuição deste período (opcional)";
"Include Contribution" = "Incluir contribuição";
"New capital added" = "Novo capital adicionado";
"Contribution (Optional)" = "Contribuição (opcional)";
"Track new capital added to separate it from investment growth." = "Registre o novo capital adicionado para separar do crescimento do investimento.";
"Monthly Highlights" = "Destaques do mês";
"Best Performer" = "Melhor desempenho";
"Worst Performer" = "Pior desempenho";
"Best Contributor" = "Maior contribuinte";
"Update Check-in" = "Atualizar check-in";
"Completed %@" = "Concluído %@";
@@ -140,6 +140,13 @@ class IAPService: ObservableObject {
} }
} }
// MARK: - TestFlight Detection
/// TestFlight builds use a "sandboxReceipt" instead of the production "receipt".
private var isRunningOnTestFlight: Bool {
Bundle.main.appStoreReceiptURL?.lastPathComponent == "sandboxReceipt"
}
// MARK: - Update Premium Status // MARK: - Update Premium Status
func updatePremiumStatus() async { func updatePremiumStatus() async {
@@ -154,6 +161,14 @@ class IAPService: ObservableObject {
} }
#endif #endif
// TestFlight builds always get Premium so testers can evaluate all features
if isRunningOnTestFlight {
isPremium = true
isFamilyShared = false
sharedDefaults?.set(true, forKey: "premiumUnlocked")
return
}
for await result in StoreKit.Transaction.currentEntitlements { for await result in StoreKit.Transaction.currentEntitlements {
if case .verified(let transaction) = result { if case .verified(let transaction) = result {
if transaction.productID == Self.premiumProductID { if transaction.productID == Self.premiumProductID {
@@ -46,58 +46,23 @@ enum CurrencyFormatter {
return formatter.currencySymbol ?? code return formatter.currencySymbol ?? code
} }
/// Parses a user-typed numeric string using digit-position-based separator detection. /// Parses a user-typed numeric string accepting both `.` and `,` as decimal/grouping separators.
/// Locale-independent: a separator followed by 1-2 digits at the end is decimal, /// The parser is intentionally permissive to avoid turning decimal input into huge integers.
/// otherwise it is a thousands separator.
static func parseUserInput(_ string: String, currencySymbol: String = "") -> Decimal? { static func parseUserInput(_ string: String, currencySymbol: String = "") -> Decimal? {
let stripped = string let stripped = string
.replacingOccurrences(of: currencySymbol, with: "") .replacingOccurrences(of: currencySymbol, with: "")
.trimmingCharacters(in: .whitespaces) .replacingOccurrences(of: "\u{00A0}", with: " ")
.filter { $0.isNumber || $0 == "." || $0 == "," } .trimmingCharacters(in: .whitespacesAndNewlines)
.filter { $0.isNumber || $0 == "." || $0 == "," || $0 == "-" }
guard !stripped.isEmpty else { return nil } guard !stripped.isEmpty else { return nil }
let hasDot = stripped.contains(".") let isNegative = stripped.hasPrefix("-")
let hasComma = stripped.contains(",") let unsigned = stripped.replacingOccurrences(of: "-", with: "")
guard !unsigned.isEmpty else { return nil }
let normalized: String let normalizedUnsigned = normalizeNumericInput(unsigned)
let normalized = isNegative ? "-\(normalizedUnsigned)" : normalizedUnsigned
if hasDot && hasComma {
// Both separators: the one appearing last is the decimal
let lastDot = stripped.lastIndex(of: ".")!
let lastComma = stripped.lastIndex(of: ",")!
if lastComma > lastDot {
// e.g. 1.234,56 1234.56
normalized = stripped
.replacingOccurrences(of: ".", with: "")
.replacingOccurrences(of: ",", with: ".")
} else {
// e.g. 1,234.56 1234.56
normalized = stripped.replacingOccurrences(of: ",", with: "")
}
} else if hasComma {
// Only comma: decimal if exactly 2 parts and last has 2 digits
let parts = stripped.components(separatedBy: ",")
if parts.count == 2 && (parts.last?.count ?? 0) <= 2 {
// e.g. 408857,62 408857.62
normalized = stripped.replacingOccurrences(of: ",", with: ".")
} else {
// e.g. 408,857 or 1,234,567 thousands
normalized = stripped.replacingOccurrences(of: ",", with: "")
}
} else if hasDot {
// Only dot: decimal if exactly 2 parts and last has 2 digits
let parts = stripped.components(separatedBy: ".")
if parts.count == 2 && (parts.last?.count ?? 0) <= 2 {
// e.g. 408857.62 already correct
normalized = stripped
} else {
// e.g. 408.857 or 1.234.567 thousands
normalized = stripped.replacingOccurrences(of: ".", with: "")
}
} else {
normalized = stripped
}
let formatter = NumberFormatter() let formatter = NumberFormatter()
formatter.numberStyle = .decimal formatter.numberStyle = .decimal
@@ -105,6 +70,62 @@ enum CurrencyFormatter {
return formatter.number(from: normalized)?.decimalValue return formatter.number(from: normalized)?.decimalValue
} }
private static func normalizeNumericInput(_ input: String) -> String {
let hasDot = input.contains(".")
let hasComma = input.contains(",")
if hasDot && hasComma {
// Both separators present: the last one is considered decimal.
let lastDot = input.lastIndex(of: ".")!
let lastComma = input.lastIndex(of: ",")!
if lastComma > lastDot {
return input
.replacingOccurrences(of: ".", with: "")
.replacingOccurrences(of: ",", with: ".")
}
return input.replacingOccurrences(of: ",", with: "")
}
if hasDot {
return normalizeSingleSeparator(input, separator: ".")
}
if hasComma {
return normalizeSingleSeparator(input, separator: ",")
}
return input
}
private static func normalizeSingleSeparator(_ input: String, separator: Character) -> String {
let parts = input.split(separator: separator, omittingEmptySubsequences: false)
guard parts.count > 1 else { return input }
if parts.count == 2 {
// A single separator is treated as decimal (e.g. 533.595).
return "\(parts[0]).\(parts[1])"
}
let integerParts = Array(parts.dropLast())
let fractionPart = String(parts.last ?? "")
// If all groups follow a strict thousands pattern and the last one has 3 digits,
// prefer grouping-only interpretation (e.g. 1.234.567).
if looksLikeGroupedThousands(integerParts), fractionPart.count == 3 {
return input.replacingOccurrences(of: String(separator), with: "")
}
// Otherwise, treat the last separator as decimal and previous ones as grouping.
let integer = integerParts.joined()
return "\(integer).\(fractionPart)"
}
private static func looksLikeGroupedThousands(_ groups: [Substring]) -> Bool {
guard let first = groups.first, !first.isEmpty, first.count <= 3 else { return false }
guard groups.count >= 2 else { return false }
return groups.dropFirst().allSatisfy { $0.count == 3 }
}
/// Formats a decimal for display in an input field (no grouping separator). /// Formats a decimal for display in an input field (no grouping separator).
static func formatForInput(_ decimal: Decimal, currencyCode: String) -> String { static func formatForInput(_ decimal: Decimal, currencyCode: String) -> String {
let formatter = NumberFormatter() let formatter = NumberFormatter()
@@ -94,8 +94,11 @@ class SnapshotFormViewModel: ObservableObject {
// Contribution is optional but must be valid if included // Contribution is optional but must be valid if included
if includeContribution { if includeContribution {
guard let contribution = parseDecimal(contributionString), contribution >= 0 else { let trimmed = contributionString.trimmingCharacters(in: .whitespacesAndNewlines)
return false if !trimmed.isEmpty {
guard let contribution = parseDecimal(trimmed), contribution >= 0 else {
return false
}
} }
} }
@@ -120,7 +123,9 @@ class SnapshotFormViewModel: ObservableObject {
var contribution: Decimal? { var contribution: Decimal? {
guard includeContribution else { return nil } guard includeContribution else { return nil }
return parseDecimal(contributionString) let trimmed = contributionString.trimmingCharacters(in: .whitespacesAndNewlines)
guard !trimmed.isEmpty else { return nil }
return parseDecimal(trimmed)
} }
var formattedValue: String { var formattedValue: String {
@@ -518,6 +518,28 @@ struct EvolutionChartView: View {
var id: String { rawValue } var id: String { rawValue }
} }
private static let compactXAxisDateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.locale = .autoupdatingCurrent
formatter.setLocalizedDateFormatFromTemplate("MMM yy")
return formatter
}()
private var xAxisMonthStride: Int {
switch data.count {
case ...8:
return 1
case ...16:
return 2
case ...30:
return 3
case ...48:
return 4
default:
return 6
}
}
private var stackedCategoryData: [CategoryEvolutionPoint] { private var stackedCategoryData: [CategoryEvolutionPoint] {
guard !categoryData.isEmpty else { return [] } guard !categoryData.isEmpty else { return [] }
@@ -622,8 +644,17 @@ struct EvolutionChartView: View {
} }
.chartForegroundStyleScale(domain: chartCategoryNames, range: chartCategoryColors) .chartForegroundStyleScale(domain: chartCategoryNames, range: chartCategoryColors)
.chartXAxis { .chartXAxis {
AxisMarks(values: .stride(by: .month, count: 2)) { value in AxisMarks(values: .stride(by: .month, count: xAxisMonthStride)) { value in
AxisValueLabel(format: .dateTime.month(.abbreviated).year()) AxisGridLine(stroke: StrokeStyle(lineWidth: 0.8, dash: [3, 3]))
.foregroundStyle(Color.secondary.opacity(0.2))
AxisTick(stroke: StrokeStyle(lineWidth: 0.8))
.foregroundStyle(Color.secondary.opacity(0.28))
AxisValueLabel {
if let date = value.as(Date.self) {
Text(date, formatter: Self.compactXAxisDateFormatter)
.font(.caption2)
}
}
} }
} }
.chartYAxis { .chartYAxis {
@@ -413,9 +413,7 @@ struct TotalValueCard: View {
struct MonthlyCheckInCard: View { struct MonthlyCheckInCard: View {
let lastUpdated: String let lastUpdated: String
let lastUpdatedDate: Date? let lastUpdatedDate: Date?
@State private var showingStartOptions = false
@State private var startDestinationActive = false @State private var startDestinationActive = false
@State private var shouldDuplicatePrevious = false
@State private var cachedCompletionDate: Date? @State private var cachedCompletionDate: Date?
private var effectiveLastCheckInDate: Date? { private var effectiveLastCheckInDate: Date? {
@@ -466,11 +464,46 @@ struct MonthlyCheckInCard: View {
) )
} }
/// True during the first half of the month (before mid-month threshold) when the previous
/// period's check-in should be offered for update instead of starting a new one.
private var isBeforeMidMonth: Bool {
guard effectiveLastCheckInDate != nil else { return false }
let cal = Calendar.current
let day = cal.component(.day, from: Date())
let daysInMonth = cal.range(of: .day, in: .month, for: Date())?.count ?? 30
return day < daysInMonth / 2
}
/// Reference date to pass to MonthlyCheckInView depending on the current phase.
private var navigationReferenceDate: Date {
if isBeforeMidMonth {
return Calendar.current.date(byAdding: .month, value: -1, to: Date()) ?? Date()
}
// Use day 25 so effectiveMonth always resolves to the current calendar month
var comps = Calendar.current.dateComponents([.year, .month], from: Date())
comps.day = 25
return Calendar.current.date(from: comps) ?? Date()
}
private var buttonLabel: String {
if isBeforeMidMonth {
let formatter = DateFormatter()
formatter.dateFormat = "MMMM"
formatter.locale = .current
let prev = Calendar.current.date(byAdding: .month, value: -1, to: Date()) ?? Date()
return String(format: NSLocalizedString("checkin_update_month", comment: ""), formatter.string(from: prev))
}
return NSLocalizedString("checkin_start_new", comment: "")
}
private var currentMonthLabel: String { private var currentMonthLabel: String {
let formatter = DateFormatter() let formatter = DateFormatter()
formatter.dateFormat = "LLLL yyyy" formatter.dateFormat = "LLLL yyyy"
let effectiveMonth = MonthlyCheckInStore.effectiveMonth(for: Date(), relativeTo: Date()) if isBeforeMidMonth {
return formatter.string(from: effectiveMonth) let prev = Calendar.current.date(byAdding: .month, value: -1, to: Date()) ?? Date()
return formatter.string(from: prev)
}
return formatter.string(from: Date())
} }
var body: some View { var body: some View {
@@ -521,9 +554,9 @@ struct MonthlyCheckInCard: View {
} }
Button { Button {
showingStartOptions = true startDestinationActive = true
} label: { } label: {
Text("Start") Text(buttonLabel)
.font(.headline.weight(.semibold)) .font(.headline.weight(.semibold))
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.padding(.vertical, 12) .padding(.vertical, 12)
@@ -535,7 +568,7 @@ struct MonthlyCheckInCard: View {
NavigationLink( NavigationLink(
isActive: $startDestinationActive isActive: $startDestinationActive
) { ) {
MonthlyCheckInView(duplicatePrevious: shouldDuplicatePrevious) MonthlyCheckInView(referenceDate: navigationReferenceDate)
} label: { } label: {
EmptyView() EmptyView()
} }
@@ -544,47 +577,6 @@ struct MonthlyCheckInCard: View {
.background(Color(.systemBackground)) .background(Color(.systemBackground))
.cornerRadius(AppConstants.UI.cornerRadius) .cornerRadius(AppConstants.UI.cornerRadius)
.shadow(color: .black.opacity(0.05), radius: 8, y: 2) .shadow(color: .black.opacity(0.05), radius: 8, y: 2)
.sheet(isPresented: $showingStartOptions) {
VStack(spacing: 16) {
Text("Start Monthly Check-in")
.font(.title2.weight(.bold))
.multilineTextAlignment(.center)
.padding(.top, 24)
Button {
showingStartOptions = false
shouldDuplicatePrevious = false
startDestinationActive = true
} label: {
Text("Start from scratch")
.font(.headline)
.frame(maxWidth: .infinity)
.padding(.vertical, 14)
.background(Color(.systemGray5))
.cornerRadius(AppConstants.UI.cornerRadius)
}
Button {
showingStartOptions = false
shouldDuplicatePrevious = true
startDestinationActive = true
} label: {
Text("Duplicate previous month")
.font(.headline)
.frame(maxWidth: .infinity)
.padding(.vertical, 14)
.background(Color(.systemGray5))
.cornerRadius(AppConstants.UI.cornerRadius)
}
Button("Cancel", role: .cancel) {
showingStartOptions = false
}
.padding(.bottom, 16)
}
.padding(.horizontal, 24)
.presentationDetents([.height(280)])
}
.onAppear { .onAppear {
cachedCompletionDate = MonthlyCheckInStore.latestCompletionDate() cachedCompletionDate = MonthlyCheckInStore.latestCompletionDate()
} }
@@ -9,6 +9,7 @@ struct EvolutionChartCard: View {
@State private var selectedDataPoint: (date: Date, value: Decimal)? @State private var selectedDataPoint: (date: Date, value: Decimal)?
@State private var chartMode: ChartMode = .total @State private var chartMode: ChartMode = .total
@State private var showGoalLines = true @State private var showGoalLines = true
@State private var chartWidth: CGFloat = 300
enum ChartMode: String, CaseIterable, Identifiable { enum ChartMode: String, CaseIterable, Identifiable {
case total = "Total" case total = "Total"
@@ -17,6 +18,30 @@ struct EvolutionChartCard: View {
var id: String { rawValue } var id: String { rawValue }
} }
private static let compactXAxisDateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.locale = .autoupdatingCurrent
formatter.setLocalizedDateFormatFromTemplate("MMM yy")
return formatter
}()
/// Calculates the optimal month stride so labels never overlap,
/// using the actual rendered width of the chart instead of just data count.
private func xAxisMonthStride(for width: CGFloat) -> Int {
// ~50pt for Y-axis, ~44pt per "Jan 24" label
let usableWidth = max(width - 50, 80)
let maxLabels = max(2, Int(usableWidth / 44))
let rawStride = max(1, Int(ceil(Double(data.count) / Double(maxLabels))))
switch rawStride {
case ...1: return 1
case ...2: return 2
case ...3: return 3
case ...4: return 4
case ...6: return 6
default: return 12
}
}
var body: some View { var body: some View {
VStack(alignment: .leading, spacing: 12) { VStack(alignment: .leading, spacing: 12) {
headerView headerView
@@ -84,8 +109,17 @@ struct EvolutionChartCard: View {
} }
.chartForegroundStyleScale(domain: chartCategoryNames, range: chartCategoryColors) .chartForegroundStyleScale(domain: chartCategoryNames, range: chartCategoryColors)
.chartXAxis { .chartXAxis {
AxisMarks(values: .stride(by: .month, count: 3)) { value in AxisMarks(values: .stride(by: .month, count: xAxisMonthStride(for: chartWidth))) { value in
AxisValueLabel(format: .dateTime.month(.abbreviated).year()) AxisGridLine(stroke: StrokeStyle(lineWidth: 0.8, dash: [3, 3]))
.foregroundStyle(Color.secondary.opacity(0.2))
AxisTick(stroke: StrokeStyle(lineWidth: 0.8))
.foregroundStyle(Color.secondary.opacity(0.28))
AxisValueLabel {
if let date = value.as(Date.self) {
Text(date, formatter: Self.compactXAxisDateFormatter)
.font(.caption2)
}
}
} }
} }
.chartYAxis { .chartYAxis {
@@ -124,6 +158,13 @@ struct EvolutionChartCard: View {
} }
} }
.frame(height: 200) .frame(height: 200)
.background(
GeometryReader { geo in
Color.clear
.onAppear { chartWidth = geo.size.width }
.onChange(of: geo.size.width) { _, w in chartWidth = w }
}
)
// Performance: Use GPU rendering for smoother scrolling // Performance: Use GPU rendering for smoother scrolling
.drawingGroup() .drawingGroup()
} }
@@ -5,7 +5,6 @@ struct MonthlyCheckInView: View {
@EnvironmentObject var accountStore: AccountStore @EnvironmentObject var accountStore: AccountStore
@StateObject private var viewModel = MonthlyCheckInViewModel() @StateObject private var viewModel = MonthlyCheckInViewModel()
@State private var referenceDate: Date @State private var referenceDate: Date
let duplicatePrevious: Bool
@State private var monthlyNote: String @State private var monthlyNote: String
@State private var starRating: Int @State private var starRating: Int
@@ -13,14 +12,12 @@ struct MonthlyCheckInView: View {
@FocusState private var noteFocused: Bool @FocusState private var noteFocused: Bool
@State private var editingSnapshot: Snapshot? @State private var editingSnapshot: Snapshot?
@State private var addingSource: InvestmentSource? @State private var addingSource: InvestmentSource?
@State private var didApplyDuplicate = false
@State private var showBatchUpdate = false @State private var showBatchUpdate = false
@State private var showAchievementSatisfactionDialog = false @State private var showAchievementSatisfactionDialog = false
@State private var showAppStoreReviewAlert = false @State private var showAppStoreReviewAlert = false
init(referenceDate: Date = Date(), duplicatePrevious: Bool = false) { init(referenceDate: Date = Date()) {
_referenceDate = State(initialValue: referenceDate) _referenceDate = State(initialValue: referenceDate)
self.duplicatePrevious = duplicatePrevious
_monthlyNote = State(initialValue: MonthlyCheckInStore.note(for: referenceDate)) _monthlyNote = State(initialValue: MonthlyCheckInStore.note(for: referenceDate))
_starRating = State(initialValue: MonthlyCheckInStore.rating(for: referenceDate) ?? 0) _starRating = State(initialValue: MonthlyCheckInStore.rating(for: referenceDate) ?? 0)
_selectedMood = State(initialValue: MonthlyCheckInStore.mood(for: referenceDate)) _selectedMood = State(initialValue: MonthlyCheckInStore.mood(for: referenceDate))
@@ -109,9 +106,32 @@ struct MonthlyCheckInView: View {
} }
.padding() .padding()
} }
.navigationTitle("Monthly Check-in")
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
.toolbar { .toolbar {
ToolbarItem(placement: .principal) {
HStack(spacing: 12) {
Button {
navigateMonth(offset: -1)
} label: {
Image(systemName: "chevron.left")
.font(.subheadline.weight(.semibold))
.foregroundColor(.appPrimary)
}
Text(monthLabel)
.font(.headline)
Button {
navigateMonth(offset: 1)
} label: {
Image(systemName: "chevron.right")
.font(.subheadline.weight(.semibold))
.foregroundColor(canGoToNextMonth ? .appPrimary : .secondary.opacity(0.3))
}
.disabled(!canGoToNextMonth)
}
}
ToolbarItem(placement: .navigationBarTrailing) { ToolbarItem(placement: .navigationBarTrailing) {
Button { Button {
shareMonthlyCheckIn() shareMonthlyCheckIn()
@@ -124,10 +144,6 @@ struct MonthlyCheckInView: View {
viewModel.selectedAccount = accountStore.selectedAccount viewModel.selectedAccount = accountStore.selectedAccount
viewModel.showAllAccounts = accountStore.showAllAccounts viewModel.showAllAccounts = accountStore.showAllAccounts
viewModel.selectedRange = DateRange.month(containing: referenceDate) viewModel.selectedRange = DateRange.month(containing: referenceDate)
if duplicatePrevious, !didApplyDuplicate {
viewModel.duplicatePreviousMonthSnapshots(referenceDate: referenceDate)
didApplyDuplicate = true
}
viewModel.refresh() viewModel.refresh()
monthlyNote = MonthlyCheckInStore.note(for: referenceDate) monthlyNote = MonthlyCheckInStore.note(for: referenceDate)
starRating = MonthlyCheckInStore.rating(for: referenceDate) ?? 0 starRating = MonthlyCheckInStore.rating(for: referenceDate) ?? 0
@@ -164,92 +180,56 @@ struct MonthlyCheckInView: View {
MonthlyCheckInStore.setMood(newValue, for: referenceDate) MonthlyCheckInStore.setMood(newValue, for: referenceDate)
} }
.confirmationDialog( .confirmationDialog(
"How much are you enjoying Portfolio Journal?", "checkin_enjoying_dialog_title",
isPresented: $showAchievementSatisfactionDialog, isPresented: $showAchievementSatisfactionDialog,
titleVisibility: .visible titleVisibility: .visible
) { ) {
ForEach(1...5, id: \.self) { value in ForEach(1...5, id: \.self) { value in
Button("\(value) Star\(value > 1 ? "s" : "")") { Button(value == 1
? String(localized: "rating_1_star")
: String(format: NSLocalizedString("rating_n_stars", comment: ""), value)
) {
if value == 5 { if value == 5 {
showAppStoreReviewAlert = true showAppStoreReviewAlert = true
} }
} }
} }
Button("Not Now", role: .cancel) {} Button(String(localized: "not_now"), role: .cancel) {}
} message: { } message: {
Text("Congrats on your new achievement! Your feedback helps us improve.") Text("checkin_enjoying_dialog_message")
} }
.alert("Would you like to leave an App Store review?", isPresented: $showAppStoreReviewAlert) { .alert("app_store_review_title", isPresented: $showAppStoreReviewAlert) {
Button("Not Now", role: .cancel) {} Button(String(localized: "not_now"), role: .cancel) {}
Button("Write a Review") { Button(String(localized: "write_review")) {
ReviewPromptService.shared.markStoreReviewCompleted() ReviewPromptService.shared.markStoreReviewCompleted()
openURL(ReviewPromptService.appStoreWriteReviewURL()) openURL(ReviewPromptService.appStoreWriteReviewURL())
} }
} message: { } message: {
Text("Thanks for the 5 stars. It really helps other investors discover the app.") Text("app_store_review_message")
} }
} }
private var headerCard: some View { private var headerCard: some View {
VStack(alignment: .leading, spacing: 8) { VStack(alignment: .leading, spacing: 8) {
HStack { if referenceDate.isSameMonth(as: Date()) {
Button { VStack(alignment: .leading, spacing: 6) {
navigateMonth(offset: -1) ProgressView(value: checkInProgress)
} label: { .tint(progressBarTint)
Image(systemName: "chevron.left")
.font(.headline)
.foregroundColor(.appPrimary)
}
Spacer() if let nextDate = nextCheckInDate {
Text(
Text(monthLabel) String(
.font(.title3.weight(.bold)) format: NSLocalizedString("next_check_in", comment: ""),
nextDate.mediumDateString
Spacer() )
Button {
navigateMonth(offset: 1)
} label: {
Image(systemName: "chevron.right")
.font(.headline)
.foregroundColor(canGoToNextMonth ? .appPrimary : .secondary.opacity(0.3))
}
.disabled(!canGoToNextMonth)
}
if let date = lastCompletionDate {
Text(
String(
format: NSLocalizedString("last_check_in", comment: ""),
date.friendlyDescription
)
)
.font(.subheadline)
.foregroundColor(.secondary)
} else {
Text("No check-in yet this month")
.font(.subheadline)
.foregroundColor(.secondary)
}
VStack(alignment: .leading, spacing: 6) {
ProgressView(value: checkInProgress)
.tint(progressBarTint)
if let nextDate = nextCheckInDate {
Text(
String(
format: NSLocalizedString("next_check_in", comment: ""),
nextDate.mediumDateString
) )
) .font(.caption)
.font(.caption) .foregroundColor(.secondary)
.foregroundColor(.secondary) } else {
} else { Text("Start your first check-in anytime.")
Text("Start your first check-in anytime.") .font(.caption)
.font(.caption) .foregroundColor(.secondary)
.foregroundColor(.secondary) }
} }
} }
@@ -968,7 +948,9 @@ struct BatchUpdateView: View {
Button { Button {
saveAll() saveAll()
} label: { } label: {
Text("Save \(filledCount) Snapshot\(filledCount == 1 ? "" : "s")") Text(filledCount == 1
? String(localized: "save_1_snapshot")
: String(format: NSLocalizedString("save_n_snapshots", comment: ""), filledCount))
.font(.headline) .font(.headline)
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
} }
@@ -0,0 +1,141 @@
import SwiftUI
/// Shown before onboarding on a fresh install when iCloud is available.
/// Lets the user choose between restoring from iCloud or starting fresh.
struct OnboardingICloudCheckView: View {
/// Called when the user decides to start fresh (no iCloud restore).
let onSkip: () -> Void
@AppStorage("cloudSyncEnabled") private var cloudSyncEnabled = false
@State private var showRestartPrompt = false
var body: some View {
if showRestartPrompt {
restartPromptView
} else {
checkView
}
}
// MARK: - Check View
private var checkView: some View {
VStack {
Spacer()
VStack(spacing: 28) {
ZStack {
Circle()
.fill(Color.appPrimary.opacity(0.12))
.frame(width: 140, height: 140)
Circle()
.fill(Color.appPrimary.opacity(0.22))
.frame(width: 100, height: 100)
Image(systemName: "icloud.fill")
.font(.system(size: 50))
.foregroundColor(.appPrimary)
}
VStack(spacing: 14) {
Text(String(localized: "icloud_check_title"))
.font(.title.weight(.bold))
.multilineTextAlignment(.center)
Text(String(localized: "icloud_check_description"))
.font(.body)
.foregroundColor(.secondary)
.multilineTextAlignment(.center)
.padding(.horizontal, 40)
}
}
Spacer()
Spacer()
VStack(spacing: 12) {
Button {
cloudSyncEnabled = true
showRestartPrompt = true
} label: {
Label("Restore from iCloud", systemImage: "icloud.and.arrow.down")
.font(.headline)
.foregroundColor(.white)
.frame(maxWidth: .infinity)
.padding()
.background(Color.appPrimary)
.cornerRadius(AppConstants.UI.cornerRadius)
}
Button {
onSkip()
} label: {
Text("Start Fresh")
.font(.subheadline)
.foregroundColor(.secondary)
}
}
.padding(.horizontal, 24)
.padding(.bottom, 40)
}
.background(AppBackground())
}
// MARK: - Restart Prompt View
private var restartPromptView: some View {
VStack {
Spacer()
VStack(spacing: 28) {
ZStack {
Circle()
.fill(Color.positiveGreen.opacity(0.12))
.frame(width: 140, height: 140)
Circle()
.fill(Color.positiveGreen.opacity(0.22))
.frame(width: 100, height: 100)
Image(systemName: "checkmark.icloud.fill")
.font(.system(size: 50))
.foregroundColor(.positiveGreen)
}
VStack(spacing: 14) {
Text(String(localized: "icloud_enabled_title"))
.font(.title.weight(.bold))
.multilineTextAlignment(.center)
Text(String(localized: "icloud_enabled_description"))
.font(.body)
.foregroundColor(.secondary)
.multilineTextAlignment(.center)
.padding(.horizontal, 40)
}
}
Spacer()
Spacer()
// "Got it" just acknowledges the user must close and reopen manually.
// The button stays active so it doesn't look broken.
Button {
// No-op: user needs to close and reopen the app.
// Nothing to navigate to; this session has no CloudKit container.
} label: {
Text("Got it")
.font(.headline)
.foregroundColor(.white)
.frame(maxWidth: .infinity)
.padding()
.background(Color.positiveGreen)
.cornerRadius(AppConstants.UI.cornerRadius)
}
.padding(.horizontal, 24)
.padding(.bottom, 40)
}
.background(AppBackground())
}
}
#Preview {
OnboardingICloudCheckView(onSkip: {})
}
@@ -185,46 +185,7 @@ struct AddSourceView: View {
} }
private func parseDecimal(_ string: String) -> Decimal? { private func parseDecimal(_ string: String) -> Decimal? {
let locale = CurrencyFormatter.locale(for: currencyCode) CurrencyFormatter.parseUserInput(string, currencySymbol: currencySymbol)
let stripped = string
.replacingOccurrences(of: currencySymbol, with: "")
.trimmingCharacters(in: .whitespaces)
guard !stripped.isEmpty else { return nil }
let decimalSep = locale.decimalSeparator ?? "."
let groupingSep = locale.groupingSeparator ?? ""
// Detect alternate decimal BEFORE removing separators
let usesAlternateDecimal =
(decimalSep == "," && stripped.contains(".") && !stripped.contains(",")) ||
(decimalSep == "." && stripped.contains(",") && !stripped.contains("."))
if usesAlternateDecimal {
let normalized = stripped
.replacingOccurrences(of: groupingSep, with: "")
.replacingOccurrences(of: ",", with: ".")
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.locale = Locale(identifier: "en_US_POSIX")
return formatter.number(from: normalized)?.decimalValue
}
let cleaned = stripped.replacingOccurrences(of: groupingSep, with: "")
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.locale = locale
if let value = formatter.number(from: cleaned)?.decimalValue {
return value
}
// Fallback for mixed locale input
let normalized = cleaned
.replacingOccurrences(of: decimalSep, with: ".")
.replacingOccurrences(of: ",", with: ".")
formatter.locale = Locale(identifier: "en_US_POSIX")
return formatter.number(from: normalized)?.decimalValue
} }
private var currencySymbol: String { private var currencySymbol: String {
@@ -559,13 +520,16 @@ struct AddSnapshotView: View {
let repository = SnapshotRepository() let repository = SnapshotRepository()
if let snapshot = snapshot { if let snapshot = snapshot {
let contributionTextIsEmpty = viewModel.contributionString
.trimmingCharacters(in: .whitespacesAndNewlines)
.isEmpty
repository.updateSnapshot( repository.updateSnapshot(
snapshot, snapshot,
date: viewModel.date, date: viewModel.date,
value: value, value: value,
contribution: viewModel.contribution, contribution: viewModel.contribution,
notes: viewModel.notes.isEmpty ? nil : viewModel.notes, notes: viewModel.notes.isEmpty ? nil : viewModel.notes,
clearContribution: !viewModel.includeContribution, clearContribution: !viewModel.includeContribution || contributionTextIsEmpty,
clearNotes: viewModel.notes.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty clearNotes: viewModel.notes.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
) )
} else { } else {
@@ -0,0 +1,36 @@
import XCTest
@testable import PortfolioJournal
final class CurrencyFormatterTests: XCTestCase {
func testParseUserInput_withDotDecimalAndThreeDigits_keepsDecimal() {
let value = CurrencyFormatter.parseUserInput("533.595", currencySymbol: "")
XCTAssertEqual(value, Decimal(string: "533.595"))
}
func testParseUserInput_withCommaDecimalAndThreeDigits_keepsDecimal() {
let value = CurrencyFormatter.parseUserInput("533,595", currencySymbol: "")
XCTAssertEqual(value, Decimal(string: "533.595"))
}
func testParseUserInput_withGroupedThousands_preservesIntegerValue() {
let dotGrouped = CurrencyFormatter.parseUserInput("1.234.567", currencySymbol: "")
let commaGrouped = CurrencyFormatter.parseUserInput("1,234,567", currencySymbol: "$")
XCTAssertEqual(dotGrouped, Decimal(string: "1234567"))
XCTAssertEqual(commaGrouped, Decimal(string: "1234567"))
}
func testParseUserInput_withMixedSeparators_parsesDecimal() {
let eu = CurrencyFormatter.parseUserInput("1.234,56", currencySymbol: "")
let us = CurrencyFormatter.parseUserInput("1,234.56", currencySymbol: "$")
XCTAssertEqual(eu, Decimal(string: "1234.56"))
XCTAssertEqual(us, Decimal(string: "1234.56"))
}
func testParseUserInput_withSmallDecimalValue_keepsPrecision() {
let value = CurrencyFormatter.parseUserInput("0.000123", currencySymbol: "$")
XCTAssertEqual(value, Decimal(string: "0.000123"))
}
}
+132
View File
@@ -0,0 +1,132 @@
#!/bin/zsh
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
PROJECT_PATH="${PROJECT_PATH:-$ROOT_DIR/PortfolioJournal.xcodeproj}"
SCHEME="${SCHEME:-PortfolioJournal}"
CONFIGURATION="${CONFIGURATION:-Release}"
ARCHIVE_DIR="${ARCHIVE_DIR:-$ROOT_DIR/build/appstore}"
ARCHIVE_PATH="${ARCHIVE_PATH:-$ARCHIVE_DIR/${SCHEME}.xcarchive}"
EXPORT_PATH="${EXPORT_PATH:-$ARCHIVE_DIR/export}"
EXPORT_OPTIONS_PLIST="${EXPORT_OPTIONS_PLIST:-$ARCHIVE_DIR/ExportOptions.plist}"
KEY_ID="${APPSTORE_KEY_ID:-YJLSGZGP79}"
ISSUER_ID="${APPSTORE_ISSUER_ID:-33f82bb1-0dbd-4b3f-a0ef-1a863dfd9453}"
TEAM_ID="${DEVELOPMENT_TEAM_ID:-2825Q76T7H}"
P8_PATH="${APPSTORE_P8_PATH:-}"
P8_DIR=""
fail() {
echo "Error: $*" >&2
exit 1
}
find_p8_file() {
if [[ -n "$P8_PATH" ]]; then
[[ -f "$P8_PATH" ]] || fail "No existe el fichero APPSTORE_P8_PATH=$P8_PATH"
echo "$P8_PATH"
return
fi
local default_path="$HOME/AuthKey_${KEY_ID}.p8"
if [[ -f "$default_path" ]]; then
echo "$default_path"
return
fi
local matches=("${HOME}"/*.p8(N))
if (( ${#matches[@]} == 1 )); then
echo "${matches[1]}"
return
fi
if (( ${#matches[@]} > 1 )); then
fail "Hay varios ficheros .p8 en $HOME. Define APPSTORE_P8_PATH con la ruta correcta."
fi
fail "No se encontro AuthKey_${KEY_ID}.p8 ni un unico fichero .p8 en $HOME."
}
create_export_options() {
mkdir -p "$ARCHIVE_DIR"
cat > "$EXPORT_OPTIONS_PLIST" <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "https://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>destination</key>
<string>export</string>
<key>manageAppVersionAndBuildNumber</key>
<false/>
<key>method</key>
<string>app-store-connect</string>
<key>signingStyle</key>
<string>automatic</string>
<key>stripSwiftSymbols</key>
<true/>
<key>teamID</key>
<string>${TEAM_ID}</string>
<key>uploadSymbols</key>
<true/>
</dict>
</plist>
EOF
}
archive_app() {
echo "==> Archivando ${SCHEME}"
xcodebuild \
-project "$PROJECT_PATH" \
-scheme "$SCHEME" \
-configuration "$CONFIGURATION" \
-archivePath "$ARCHIVE_PATH" \
-destination "generic/platform=iOS" \
clean archive
}
export_ipa() {
echo "==> Exportando IPA"
xcodebuild \
-exportArchive \
-archivePath "$ARCHIVE_PATH" \
-exportPath "$EXPORT_PATH" \
-exportOptionsPlist "$EXPORT_OPTIONS_PLIST"
}
upload_ipa() {
local ipa
ipa="$(find "$EXPORT_PATH" -maxdepth 1 -name '*.ipa' -print -quit)"
[[ -n "$ipa" ]] || fail "No se encontro ningun .ipa en $EXPORT_PATH"
echo "==> Subiendo $(basename "$ipa") a App Store Connect"
API_PRIVATE_KEYS_DIR="$P8_DIR" \
xcrun altool \
--upload-app \
--type ios \
--file "$ipa" \
--apiKey "$KEY_ID" \
--apiIssuer "$ISSUER_ID"
}
main() {
command -v xcodebuild >/dev/null 2>&1 || fail "xcodebuild no esta disponible"
command -v xcrun >/dev/null 2>&1 || fail "xcrun no esta disponible"
P8_PATH="$(find_p8_file)"
P8_DIR="$(dirname "$P8_PATH")"
echo "Usando proyecto: $PROJECT_PATH"
echo "Usando scheme: $SCHEME"
echo "Usando clave API: $KEY_ID"
echo "Usando fichero .p8: $P8_PATH"
rm -rf "$ARCHIVE_PATH" "$EXPORT_PATH"
create_export_options
archive_app
export_ipa
upload_ipa
echo "==> Proceso completado"
}
main "$@"
+8
View File
@@ -0,0 +1,8 @@
app_identifier("com.alexandrevazquez.PortfolioJournal") # The bundle identifier of your app
apple_id("alexandre.vazquez@gmail.com") # Your Apple Developer Portal username
itc_team_id("128443966") # App Store Connect Team ID
team_id("2825Q76T7H") # Developer Portal Team ID
# For more information about the Appfile, see:
# https://docs.fastlane.tools/advanced/#appfile
+3
View File
@@ -0,0 +1,3 @@
# The Deliverfile allows you to store various App Store Connect metadata
# For more information, check out the docs
# https://docs.fastlane.tools/actions/deliver/
+25
View File
@@ -0,0 +1,25 @@
# This file contains the fastlane.tools configuration
# You can find the documentation at https://docs.fastlane.tools
#
# For a list of all available actions, check out
#
# https://docs.fastlane.tools/actions
#
# For a list of all available plugins, check out
#
# https://docs.fastlane.tools/plugins/available-plugins
#
# Uncomment the line if you want fastlane to automatically update itself
# update_fastlane
default_platform(:ios)
platform :ios do
desc "Push a new release build to the App Store"
lane :release do
increment_build_number(xcodeproj: "PortfolioJournal.xcodeproj")
build_app(scheme: "PortfolioJournal")
upload_to_app_store
end
end
+995
View File
@@ -0,0 +1,995 @@
<!DOCTYPE html>
<html>
<head>
<title>deliver - Portfolio Journal: Tracker
</title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<style>
.app-name {
font-size: 42px;
font-family: 'Helvetica Neue', HelveticaNeue, Helvetica Neue;
font-weight: 300;
margin-top: 22px;
margin-left: 25px;
margin-right: 25px;
}
.app-subtitle {
font-size: 26px;
font-family: 'Helvetica Neue', HelveticaNeue, Helvetica Neue;
font-weight: 300;
margin-left: 25px;
margin-right: 25px;
}
.app-urls {
margin-left: 25px;
font-size: 14px;
margin-top: 4px;
margin-right: 25px;
}
body {
font-family: 'Helvetica Neue', HelveticaNeue;
}
hr {
margin-top: 40px;
margin-bottom: 40px;
}
.app-url-descr {
height:22px;
display: inline-block;
font-weight: 500;
}
.app-url {
color: #0056ba;
font-weight: 300;
margin-right: 10px;
}
.app-keyword {
margin-left: 25px;
margin-right: 25px;
margin-top: 22px;
}
.cat-headline {
font-weight: 700;
margin-bottom: 5px;
}
.app-description {
margin-left: 25px;
margin-right: 25px;
margin-top: 22px;
}
.app-description-text {
font-weight: 300;
}
.app-changelog {
margin-left: 25px;
margin-right: 25px;
margin-top: 22px;
font-weight: 300;
}
.app-screenshots {
margin-left: 15px;
margin-right: 15px;
margin-top: 22px;
}
.app-keyword-list {
font-weight: 300;
}
.app-changelog-list {
list-style-type: square;
font-weight: 300;
}
ul {
margin-top: 6px;
}
.app-screenshot-row {
width: 100%;
margin-top: 10px;
margin-bottom: 10px;
}
.app-screenshot {
width: calc(20% - 30px);
margin-left: 10px;
margin-right: 10px;
}
#app-screenshots .cat-headline {
margin-left: 10px;
margin-right: 10px;
}
.app-icons {
overflow: hidden;
}
.app-icons img {
width: 150px;
}
.app-icons .app-icon {
float: left;
margin-right: 20px;
}
.app-minor-information {
margin-left: 15px;
margin-right: 15px;
margin-top: 22px;
}
.app-minor-information-key {
font-weight: 700;
}
</style>
</head>
<body>
<div class="app-icons">
</div>
<div class="app-name">
de-DE: Portfolio Journal: Tracker
</div>
<div class="app-subtitle">
Subtitle: Aktien, ETF & Vermögen tracken
</div>
<div class="app-urls">
<div class="app-url-descr">
support_url: <a target="_blank" class="app-url" href="https://portfoliojournal.app/support.html">https://portfoliojournal.app/support.html</a>
</div>
<div class="app-url-descr">
marketing_url: <a target="_blank" class="app-url" href="https://portfoliojournal.app">https://portfoliojournal.app</a>
</div>
</div>
<div class="app-keyword">
<div class="cat-headline">Keywords</div>
<ul class="app-keyword-list">
<li>investition</li>
<li>depot</li>
<li>dividende</li>
<li>finanzen</li>
<li>fonds</li>
<li>krypto</li>
<li>börse</li>
<li>sparplan</li>
<li>rendite</li>
<li>FIRE</li>
<li>index</li>
<li>ziel</li>
</ul>
</div>
<div class="app-description">
<div class="cat-headline">Description</div>
<div class="app-description-text">
Portfolio Journal ist der Investitions-Tracker für Langfristanleger, die Wert auf Einfachheit und Datenschutz legen. Kein Broker-Login. Kein Server. Deine Daten bleiben auf deinem Gerät — oder werden optional privat über iCloud synchronisiert.<br /><br />Öffne die App einmal im Monat, trage deinen Portfoliowert ein und lass Portfolio Journal den Rest erledigen.<br /><br />---<br /><br />DEIN GESAMTES PORTFOLIO TRACKEN<br />• Aktien, ETFs, Anleihen, Krypto, Immobilien — jede Anlageklasse<br />• Mehrere Konten: Depot, Altersvorsorge, Tagesgeld und mehr<br />• Monatlicher Check-in-Ansatz: kein tägliches Rauschen, nur langfristige Perspektive<br />• Vermögensentwicklung über Monate und Jahre<br /><br />DEIN VERMÖGEN VISUALISIEREN<br />• Entwicklungschart: Sieh wie dein Portfolio wächst<br />• Allokationschart: Wisse genau, wo dein Geld steckt<br />• Drawdown-Analyse: Verstehe deine größten Rückgänge und Erholungen<br />• Ruhemodus: Eine ablenkungsfreie Ansicht für deinen Seelenfrieden<br /><br />ZIELE SETZEN UND VERFOLGEN<br />• Erstelle Finanzziele mit Zielbetrag und Frist<br />• Verfolge deinen Fortschritt mit visuellen Indikatoren<br />• Bleib motiviert mit einem klaren Blick auf deinen Weg<br /><br />EIN INVESTITIONSTAGEBUCH FÜHREN<br />• Halte Gedanken, Entscheidungen und Lektionen fest<br />• Baue einen persönlichen Bericht über deine Anlagestrategie auf<br />• Reflektiere vergangene Entscheidungen, um zukünftige zu verbessern<br /><br />DATENSCHUTZ BY DESIGN<br />• Kein Konto, kein Login, keine Registrierung erforderlich.<br />• Keine Analyse, kein Tracking, kein Datenverkauf — niemals.<br />• iCloud-Sync ist optional: Du bestimmst, und alles ist Ende-zu-Ende-verschlüsselt.<br />• Funktioniert 100% offline.<br /><br />FÜR PASSIVE ANLEGER ENTWICKELT<br />Portfolio Journal wurde für Boglehead-Anhänger, ETF- und Indexfonds-Investoren sowie alle entwickelt, die eine Buy-and-Hold-Strategie verfolgen. Es ist kein Trading-Tool — es ist ein langfristiger Begleiter.<br /><br />Ob du ein Drei-Fonds-Portfolio verwaltest, auf finanzielle Unabhängigkeit (FIRE) hinarbeitest oder einfach dein Nettovermögen im Blick behältst — Portfolio Journal bietet dir Klarheit ohne Komplexität.<br /><br />DEINE DATEN EXPORTIEREN<br />• CSV-Export: Nimm deine Daten jederzeit überallhin mit.<br />• Volle Kontrolle über deine Finanzgeschichte.<br /><br />---<br /><br />FÜR WEN IST PORTFOLIO JOURNAL?<br />- Langfrist- und Passivanleger, die keine täglichen Updates benötigen<br />- ETF- und Indexfonds-Enthusiasten<br />- Boglehead- und FIRE-Community-Mitglieder<br />- Alle, die ihr Nettovermögen oder ihren Vermögensaufbau verfolgen möchten<br />- Datenschutzbewusste Nutzer, die ihre Finanzdaten nicht auf fremden Servern sehen wollen<br /><br />---<br /><br />Lade Portfolio Journal jetzt herunter und beginne, dir einen klaren, privaten und langfristigen Überblick über deine finanzielle Zukunft aufzubauen.
</div>
</div>
<div class="app-changelog">
<div class="cat-headline">Changelog</div>
Verbesserungen bei der iCloud-Synchronisierung:<br />• Daten werden jetzt sofort synchronisiert, wenn du zwischen Apple-Geräten wechselst<br />• Neue Geräteerkennung: Die App erkennt vorhandene iCloud-Daten vor dem Onboarding, sodass du auf einem neuen Gerät nie von vorne anfangen musst<br />• Behoben: Änderungen von einem anderen Gerät wurden in einigen Fällen nicht korrekt übernommen<br />• Allgemeine Stabilitätsverbesserungen und Fehlerbehebungen
</div>
<div class="app-changelog">
<div class="cat-headline">Promotional Text</div>
Investitionen sicher tracken. Elegante Charts, monatliche Check-ins und optionale iCloud-Sync — kein Broker-Login, keine Datenweitergabe.
</div>
<div class="app-screenshots">
<div class="cat-headline">Screenshots</div>
<!-- no screenshots -->
<div style="border: 3px solid red; padding: 0px 20px">
<h2 style="color: red">No Screenshots Found</h2>
<p>
deliver couldn't find any screenshots.
The existing screenshots on App Store Connect will be kept.
if you want to remove them you have to use the <i>--overwrite_screenshots</i> flag.
<p>
If you want to download your existing screenshots, run <i>deliver download_screenshots</i>.
</p>
</div>
</div>
<hr />
<div class="app-name">
en-US: Portfolio Journal: Tracker
</div>
<div class="app-subtitle">
Subtitle: Track Stocks, ETF & Net Worth
</div>
<div class="app-urls">
<div class="app-url-descr">
support_url: <a target="_blank" class="app-url" href="https://portfoliojournal.app/support.html
">https://portfoliojournal.app/support.html
</a>
</div>
<div class="app-url-descr">
marketing_url: <a target="_blank" class="app-url" href="https://portfoliojournal.app
">https://portfoliojournal.app
</a>
</div>
</div>
<div class="app-keyword">
<div class="cat-headline">Keywords</div>
<ul class="app-keyword-list">
<li>investment</li>
<li>wealth</li>
<li>dividend</li>
<li>finance</li>
<li>returns</li>
<li>fund</li>
<li>crypto</li>
<li>FIRE</li>
<li>watchlist</li>
<li>index</li>
<li>savings</li>
<li>goal</li>
<li>log</li>
<li>monitor</li>
</ul>
</div>
<div class="app-description">
<div class="cat-headline">Description</div>
<div class="app-description-text">
Portfolio Journal is the investment tracker built for long-term investors who value simplicity and privacy. No brokerage login. No server. Your data stays on your device — or syncs privately via iCloud if you choose.<br /><br />Just open the app once a month, enter your portfolio value, and let Portfolio Journal do the rest.<br /><br />---<br /><br />TRACK YOUR ENTIRE PORTFOLIO<br />• Stocks, ETFs, bonds, crypto, real estate — any asset class<br />• Multiple accounts: brokerage, retirement, savings, and more<br />• Monthly check-in approach: no daily noise, just long-term perspective<br />• Net worth evolution over months and years<br /><br />VISUALIZE YOUR WEALTH<br />• Evolution chart: see your portfolio grow over time<br />• Allocation chart: know exactly where your money is<br />• Drawdown analysis: understand your worst drops and recoveries<br />• Calm Mode: a distraction-free view for peace of mind<br /><br />SET AND TRACK GOALS<br />• Create financial goals with target amounts and deadlines<br />• Track progress with visual indicators<br />• Stay motivated with a clear picture of your journey<br /><br />KEEP AN INVESTMENT JOURNAL<br />• Log thoughts, decisions, and lessons learned<br />• Build a personal record of your investment mindset over time<br />• Reflect on past decisions to improve future ones<br /><br />PRIVACY BY DESIGN<br />• No account required. No login. No sign-up.<br />• No analytics, no tracking, no data selling — ever.<br />• iCloud sync is optional: you control it, and it's encrypted end-to-end.<br />• Everything works 100% offline.<br /><br />BUILT FOR PASSIVE INVESTORS<br />Portfolio Journal is designed for Bogleheads, ETF investors, index fund fans, and anyone following a buy-and-hold strategy. It's not a trading tool — it's a long-term companion.<br /><br />Whether you're tracking a three-fund portfolio, building toward FIRE, or simply monitoring your net worth over time, Portfolio Journal gives you clarity without complexity.<br /><br />EXPORT YOUR DATA<br />• CSV export: take your data anywhere, anytime.<br />• Full ownership of your financial history.<br /><br />---<br /><br />WHO IS PORTFOLIO JOURNAL FOR?<br />- Long-term and passive investors who don't need daily updates<br />- ETF and index fund enthusiasts<br />- Bogleheads and FIRE community members<br />- Anyone tracking their net worth or wealth-building journey<br />- Privacy-conscious users who don't want their financial data on someone else's servers<br /><br />---<br /><br />Download Portfolio Journal today and start building a clear, private, long-term view of your financial future.
</div>
</div>
<div class="app-changelog">
<div class="cat-headline">Changelog</div>
iCloud Sync improvements:<br />• Data now syncs instantly when switching between Apple devices<br />• New device detection: the app now detects existing iCloud data before onboarding, so you never start from scratch on a new phone<br />• Fixed cases where updates made on another device weren't reflected correctly<br />• General stability improvements and bug fixes
</div>
<div class="app-changelog">
<div class="cat-headline">Promotional Text</div>
Track investments privately. Beautiful charts, monthly check-ins, and optional iCloud sync — no brokerage login, no data sharing.
</div>
<div class="app-screenshots">
<div class="cat-headline">Screenshots</div>
<!-- no screenshots -->
<div style="border: 3px solid red; padding: 0px 20px">
<h2 style="color: red">No Screenshots Found</h2>
<p>
deliver couldn't find any screenshots.
The existing screenshots on App Store Connect will be kept.
if you want to remove them you have to use the <i>--overwrite_screenshots</i> flag.
<p>
If you want to download your existing screenshots, run <i>deliver download_screenshots</i>.
</p>
</div>
</div>
<hr />
<div class="app-name">
es-ES: Portfolio Journal: Tracker
</div>
<div class="app-subtitle">
Subtitle: Acciones, ETF y tu patrimonio
</div>
<div class="app-urls">
<div class="app-url-descr">
support_url: <a target="_blank" class="app-url" href="https://portfoliojournal.app/support.html">https://portfoliojournal.app/support.html</a>
</div>
<div class="app-url-descr">
marketing_url: <a target="_blank" class="app-url" href="https://portfoliojournal.app">https://portfoliojournal.app</a>
</div>
</div>
<div class="app-keyword">
<div class="cat-headline">Keywords</div>
<ul class="app-keyword-list">
<li>cartera</li>
<li>inversión</li>
<li>finanzas</li>
<li>dividendos</li>
<li>fondos</li>
<li>cripto</li>
<li>bolsa</li>
<li>riqueza</li>
<li>ahorro</li>
<li>índice</li>
<li>FIRE</li>
<li>metas</li>
<li>objetivo</li>
</ul>
</div>
<div class="app-description">
<div class="cat-headline">Description</div>
<div class="app-description-text">
Portfolio Journal es el diario de inversión para quienes invierten a largo plazo y valoran la privacidad. Sin acceso a brokers. Sin servidores. Tus datos permanecen en tu dispositivo — o se sincronizan de forma privada con iCloud si así lo decides.<br /><br />Abre la app una vez al mes, introduce el valor de tu cartera y deja que Portfolio Journal haga el resto.<br /><br />---<br /><br />REGISTRA TODA TU CARTERA<br />• Acciones, ETF, bonos, cripto, inmuebles — cualquier tipo de activo<br />• Múltiples cuentas: broker, pensiones, ahorros y más<br />• Enfoque de check-in mensual: sin ruido diario, solo perspectiva a largo plazo<br />• Evolución de tu patrimonio a lo largo de meses y años<br /><br />VISUALIZA TU RIQUEZA<br />• Gráfico de evolución: observa cómo crece tu cartera con el tiempo<br />• Gráfico de asignación: sabe exactamente dónde está tu dinero<br />• Análisis de drawdown: comprende tus peores caídas y recuperaciones<br />• Modo Calma: una vista sin distracciones para tu tranquilidad<br /><br />ESTABLECE Y ALCANZA METAS<br />• Crea objetivos financieros con importe y fecha límite<br />• Sigue tu progreso con indicadores visuales<br />• Mantén la motivación con una visión clara de tu camino<br /><br />LLEVA UN DIARIO DE INVERSIÓN<br />• Anota pensamientos, decisiones y lecciones aprendidas<br />• Construye un registro personal de tu mentalidad inversora<br />• Reflexiona sobre decisiones pasadas para mejorar las futuras<br /><br />PRIVACIDAD POR DISEÑO<br />• Sin cuenta, sin login, sin registro.<br />• Sin analíticas, sin rastreo, sin venta de datos — nunca.<br />• La sincronización con iCloud es opcional: tú la controlas, y está cifrada de extremo a extremo.<br />• Funciona al 100% sin conexión.<br /><br />DISEÑADO PARA INVERSORES PASIVOS<br />Portfolio Journal está pensado para seguidores de Boglehead, inversores en ETF e índices, y cualquiera que siga una estrategia de compra y mantenimiento. No es una herramienta de trading — es un compañero a largo plazo.<br /><br />Ya sea que gestiones una cartera de tres fondos, avances hacia la independencia financiera (FIRE) o simplemente controles tu patrimonio neto, Portfolio Journal te da claridad sin complejidad.<br /><br />EXPORTA TUS DATOS<br />• Exportación a CSV: lleva tus datos donde quieras, cuando quieras.<br />• Plena propiedad de tu historial financiero.<br /><br />---<br /><br />¿PARA QUIÉN ES PORTFOLIO JOURNAL?<br />- Inversores pasivos y a largo plazo que no necesitan actualizaciones diarias<br />- Aficionados a ETF y fondos indexados<br />- Comunidad Boglehead y FIRE<br />- Cualquiera que controle su patrimonio neto o su camino hacia la libertad financiera<br />- Usuarios que valoran su privacidad y no quieren sus datos financieros en servidores ajenos<br /><br />---<br /><br />Descarga Portfolio Journal hoy y empieza a construir una visión clara, privada y a largo plazo de tu futuro financiero.
</div>
</div>
<div class="app-changelog">
<div class="cat-headline">Changelog</div>
Mejoras en la sincronización con iCloud:<br />• Los datos ahora se sincronizan al instante al cambiar entre dispositivos Apple<br />• Detección de dispositivo nuevo: la app detecta datos existentes en iCloud antes del proceso de bienvenida, para que nunca empieces desde cero en un teléfono nuevo<br />• Corregidos casos en los que los cambios realizados en otro dispositivo no se reflejaban correctamente<br />• Mejoras generales de estabilidad y corrección de errores
</div>
<div class="app-changelog">
<div class="cat-headline">Promotional Text</div>
Registra tus inversiones con privacidad. Gráficos elegantes, check-ins mensuales y sincronización iCloud opcional — sin acceso a brokers.
</div>
<div class="app-screenshots">
<div class="cat-headline">Screenshots</div>
<!-- no screenshots -->
<div style="border: 3px solid red; padding: 0px 20px">
<h2 style="color: red">No Screenshots Found</h2>
<p>
deliver couldn't find any screenshots.
The existing screenshots on App Store Connect will be kept.
if you want to remove them you have to use the <i>--overwrite_screenshots</i> flag.
<p>
If you want to download your existing screenshots, run <i>deliver download_screenshots</i>.
</p>
</div>
</div>
<hr />
<div class="app-name">
fr-FR: Portfolio Journal: Tracker
</div>
<div class="app-subtitle">
Subtitle: Actions, ETF et patrimoine
</div>
<div class="app-urls">
<div class="app-url-descr">
support_url: <a target="_blank" class="app-url" href="https://portfoliojournal.app/support.html">https://portfoliojournal.app/support.html</a>
</div>
<div class="app-url-descr">
marketing_url: <a target="_blank" class="app-url" href="https://portfoliojournal.app">https://portfoliojournal.app</a>
</div>
</div>
<div class="app-keyword">
<div class="cat-headline">Keywords</div>
<ul class="app-keyword-list">
<li>investissement</li>
<li>dividendes</li>
<li>finances</li>
<li>bourse</li>
<li>fonds</li>
<li>crypto</li>
<li>épargne</li>
<li>richesse</li>
<li>FIRE</li>
<li>objectif</li>
<li>rendement</li>
</ul>
</div>
<div class="app-description">
<div class="cat-headline">Description</div>
<div class="app-description-text">
Portfolio Journal est l'application de suivi d'investissements conçue pour les investisseurs à long terme qui privilégient la simplicité et la confidentialité. Aucune connexion à votre courtier. Aucun serveur. Vos données restent sur votre appareil — ou se synchronisent en privé via iCloud si vous le souhaitez.<br /><br />Ouvrez l'application une fois par mois, saisissez la valeur de votre portefeuille et laissez Portfolio Journal faire le reste.<br /><br />---<br /><br />SUIVEZ L'INTÉGRALITÉ DE VOTRE PORTEFEUILLE<br />• Actions, ETF, obligations, crypto, immobilier — toutes les classes d'actifs<br />• Plusieurs comptes : courtier, retraite, épargne et plus encore<br />• Approche par bilan mensuel : sans bruit quotidien, juste une perspective à long terme<br />• Évolution de votre patrimoine sur des mois et des années<br /><br />VISUALISEZ VOTRE RICHESSE<br />• Graphique d'évolution : observez la croissance de votre portefeuille dans le temps<br />• Graphique d'allocation : sachez exactement où se trouve votre argent<br />• Analyse du drawdown : comprenez vos pires baisses et vos récupérations<br />• Mode Calme : une vue sans distraction pour votre tranquillité d'esprit<br /><br />FIXEZ ET SUIVEZ VOS OBJECTIFS<br />• Créez des objectifs financiers avec des montants cibles et des échéances<br />• Suivez votre progression avec des indicateurs visuels<br />• Restez motivé grâce à une vision claire de votre parcours<br /><br />TENEZ UN JOURNAL D'INVESTISSEMENT<br />• Notez vos réflexions, décisions et leçons apprises<br />• Construisez un registre personnel de votre état d'esprit d'investisseur<br />• Réfléchissez aux décisions passées pour améliorer les futures<br /><br />CONFIDENTIALITÉ PAR CONCEPTION<br />• Aucun compte requis. Aucune connexion. Aucune inscription.<br />• Aucune analyse, aucun suivi, aucune vente de données — jamais.<br />• La synchronisation iCloud est facultative : vous la contrôlez, et elle est chiffrée de bout en bout.<br />• Fonctionne à 100% hors ligne.<br /><br />CONÇU POUR LES INVESTISSEURS PASSIFS<br />Portfolio Journal est pensé pour les adeptes de Boglehead, les investisseurs en ETF et fonds indiciels, et tous ceux qui suivent une stratégie d'achat et de conservation. Ce n'est pas un outil de trading — c'est un compagnon à long terme.<br /><br />Que vous gériez un portefeuille à trois fonds, que vous visiez l'indépendance financière (FIRE) ou que vous suiviez simplement l'évolution de votre patrimoine net, Portfolio Journal vous apporte de la clarté sans complexité.<br /><br />EXPORTEZ VOS DONNÉES<br />• Export CSV : emportez vos données partout, à tout moment.<br />• Propriété totale de votre historique financier.<br /><br />---<br /><br />POUR QUI EST PORTFOLIO JOURNAL ?<br />- Investisseurs passifs et à long terme qui n'ont pas besoin de mises à jour quotidiennes<br />- Fans d'ETF et de fonds indiciels<br />- Membres de la communauté Boglehead et FIRE<br />- Toute personne qui suit son patrimoine net ou son parcours de création de richesse<br />- Utilisateurs soucieux de leur vie privée qui ne veulent pas de leurs données financières sur les serveurs d'autrui<br /><br />---<br /><br />Téléchargez Portfolio Journal dès aujourd'hui et commencez à construire une vision claire, privée et à long terme de votre avenir financier.
</div>
</div>
<div class="app-changelog">
<div class="cat-headline">Changelog</div>
Améliorations de la synchronisation iCloud :<br />• Les données se synchronisent désormais instantanément lors du passage entre appareils Apple<br />• Détection de nouvel appareil : l'app détecte les données iCloud existantes avant l'intégration, pour ne jamais repartir de zéro sur un nouvel iPhone<br />• Correction des cas où les modifications effectuées sur un autre appareil n'étaient pas correctement reflétées<br />• Améliorations générales de la stabilité et corrections de bugs
</div>
<div class="app-changelog">
<div class="cat-headline">Promotional Text</div>
Suivez vos investissements en toute confidentialité. Graphiques élégants, bilans mensuels et sync iCloud en option — sans login broker.
</div>
<div class="app-screenshots">
<div class="cat-headline">Screenshots</div>
<!-- no screenshots -->
<div style="border: 3px solid red; padding: 0px 20px">
<h2 style="color: red">No Screenshots Found</h2>
<p>
deliver couldn't find any screenshots.
The existing screenshots on App Store Connect will be kept.
if you want to remove them you have to use the <i>--overwrite_screenshots</i> flag.
<p>
If you want to download your existing screenshots, run <i>deliver download_screenshots</i>.
</p>
</div>
</div>
<hr />
<div class="app-name">
it: Portfolio Journal: Tracker
</div>
<div class="app-subtitle">
Subtitle: Azioni, ETF e patrimonio
</div>
<div class="app-urls">
<div class="app-url-descr">
support_url: <a target="_blank" class="app-url" href="https://portfoliojournal.app/support.html">https://portfoliojournal.app/support.html</a>
</div>
<div class="app-url-descr">
marketing_url: <a target="_blank" class="app-url" href="https://portfoliojournal.app">https://portfoliojournal.app</a>
</div>
</div>
<div class="app-keyword">
<div class="cat-headline">Keywords</div>
<ul class="app-keyword-list">
<li>investimento</li>
<li>dividendi</li>
<li>finanze</li>
<li>borsa</li>
<li>fondi</li>
<li>cripto</li>
<li>risparmio</li>
<li>rendimento</li>
<li>ricchezza</li>
<li>FIRE</li>
<li>meta</li>
</ul>
</div>
<div class="app-description">
<div class="cat-headline">Description</div>
<div class="app-description-text">
Portfolio Journal è il diario di investimento pensato per chi investe a lungo termine e tiene alla propria privacy. Nessun accesso al broker. Nessun server. I tuoi dati rimangono sul tuo dispositivo — o si sincronizzano privatamente via iCloud se lo desideri.<br /><br />Apri l'app una volta al mese, inserisci il valore del tuo portafoglio e lascia fare il resto a Portfolio Journal.<br /><br />---<br /><br />TIENI TRACCIA DI TUTTO IL TUO PORTAFOGLIO<br />• Azioni, ETF, obbligazioni, crypto, immobili — qualsiasi classe di asset<br />• Più conti: broker, previdenza, risparmio e altro<br />• Approccio con check-in mensile: nessun rumore quotidiano, solo prospettiva a lungo termine<br />• Evoluzione del patrimonio nel corso di mesi e anni<br /><br />VISUALIZZA LA TUA RICCHEZZA<br />• Grafico di evoluzione: osserva come cresce il tuo portafoglio nel tempo<br />• Grafico di allocazione: sappi esattamente dove si trova il tuo denaro<br />• Analisi del drawdown: comprendi i tuoi cali peggiori e le riprese<br />• Modalità Calma: una vista senza distrazioni per la tua tranquillità<br /><br />IMPOSTA E RAGGIUNGI I TUOI OBIETTIVI<br />• Crea obiettivi finanziari con importi target e scadenze<br />• Monitora i progressi con indicatori visivi<br />• Rimani motivato con una visione chiara del tuo percorso<br /><br />TIENI UN DIARIO DI INVESTIMENTO<br />• Annota pensieri, decisioni e lezioni apprese<br />• Costruisci un registro personale della tua mentalità di investitore<br />• Rifletti sulle decisioni passate per migliorare quelle future<br /><br />PRIVACY BY DESIGN<br />• Nessun account richiesto. Nessun login. Nessuna registrazione.<br />• Nessuna analisi, nessun tracciamento, nessuna vendita di dati — mai.<br />• La sincronizzazione iCloud è facoltativa: la controlli tu ed è crittografata end-to-end.<br />• Funziona al 100% offline.<br /><br />PROGETTATO PER INVESTITORI PASSIVI<br />Portfolio Journal è pensato per i seguaci di Boglehead, gli investitori in ETF e fondi indice, e chiunque segua una strategia buy-and-hold. Non è uno strumento di trading — è un compagno a lungo termine.<br /><br />Che tu gestisca un portafoglio a tre fondi, stia lavorando verso l'indipendenza finanziaria (FIRE) o voglia semplicemente monitorare il tuo patrimonio netto, Portfolio Journal ti offre chiarezza senza complessità.<br /><br />ESPORTA I TUOI DATI<br />• Esportazione CSV: porta i tuoi dati ovunque, in qualsiasi momento.<br />• Piena proprietà della tua storia finanziaria.<br /><br />---<br /><br />PER CHI È PORTFOLIO JOURNAL?<br />- Investitori passivi e a lungo termine che non hanno bisogno di aggiornamenti quotidiani<br />- Appassionati di ETF e fondi indice<br />- Membri della comunità Boglehead e FIRE<br />- Chi monitora il proprio patrimonio netto o il proprio percorso di creazione di ricchezza<br />- Utenti attenti alla privacy che non vogliono i loro dati finanziari sui server di terzi<br /><br />---<br /><br />Scarica Portfolio Journal oggi e inizia a costruire una visione chiara, privata e a lungo termine del tuo futuro finanziario.
</div>
</div>
<div class="app-changelog">
<div class="cat-headline">Changelog</div>
Miglioramenti alla sincronizzazione iCloud:<br />• I dati ora si sincronizzano immediatamente quando passi da un dispositivo Apple all'altro<br />• Rilevamento nuovo dispositivo: l'app rileva i dati iCloud esistenti prima dell'onboarding, così non devi ricominciare da zero su un nuovo iPhone<br />• Risolti casi in cui le modifiche apportate su un altro dispositivo non venivano recepite correttamente<br />• Miglioramenti generali alla stabilità e correzioni di bug
</div>
<div class="app-changelog">
<div class="cat-headline">Promotional Text</div>
Traccia i tuoi investimenti in privato. Grafici eleganti, check-in mensili e sincronizzazione iCloud opzionale — senza login al broker.
</div>
<div class="app-screenshots">
<div class="cat-headline">Screenshots</div>
<!-- no screenshots -->
<div style="border: 3px solid red; padding: 0px 20px">
<h2 style="color: red">No Screenshots Found</h2>
<p>
deliver couldn't find any screenshots.
The existing screenshots on App Store Connect will be kept.
if you want to remove them you have to use the <i>--overwrite_screenshots</i> flag.
<p>
If you want to download your existing screenshots, run <i>deliver download_screenshots</i>.
</p>
</div>
</div>
<hr />
<div class="app-name">
ja: Portfolio Journal: Tracker
</div>
<div class="app-subtitle">
Subtitle: 株式・ETF・資産を一元管理
</div>
<div class="app-urls">
<div class="app-url-descr">
support_url: <a target="_blank" class="app-url" href="https://portfoliojournal.app/support.html">https://portfoliojournal.app/support.html</a>
</div>
<div class="app-url-descr">
marketing_url: <a target="_blank" class="app-url" href="https://portfoliojournal.app">https://portfoliojournal.app</a>
</div>
</div>
<div class="app-keyword">
<div class="cat-headline">Keywords</div>
<ul class="app-keyword-list">
<li>投資</li>
<li>配当</li>
<li>ポートフォリオ</li>
<li>家計</li>
<li>仮想通貨</li>
<li>NISA</li>
<li>iDeCo</li>
<li>積立</li>
<li>高配当</li>
<li>インデックス</li>
<li>節税</li>
<li>米国株</li>
<li>FIRE</li>
<li>損益</li>
<li>財産</li>
<li>株価</li>
<li>記録</li>
<li>管理</li>
<li>予算</li>
<li>資産形成</li>
</ul>
</div>
<div class="app-description">
<div class="cat-headline">Description</div>
<div class="app-description-text">
Portfolio Journalは、長期投資家のためのプライバシー重視の資産管理アプリです。証券口座へのログイン不要。サーバーなし。あなたのデータはデバイス上に保管され、希望する場合のみiCloudでプライベートに同期されます。<br /><br />月に一度アプリを開き、ポートフォリオの価値を入力するだけ。あとはPortfolio Journalがすべて計算します。<br /><br />---<br /><br />資産全体を記録する<br />• 株式、ETF、債券、暗号資産、不動産など、あらゆる資産クラスに対応<br />• 複数口座に対応:証券口座、退職金口座、貯蓄口座など<br />• 月次チェックイン方式:日々の相場ノイズなし、長期的な視点を大切に<br />• 月・年単位での資産推移を把握<br /><br />資産を可視化する<br />• 推移グラフ:ポートフォリオの成長を時系列で確認<br />• アロケーションチャート:資金の配分をひと目で把握<br />• ドローダウン分析:最大下落と回復の過程を理解<br />• カームモード:集中を妨げない、落ち着いた表示画面<br /><br />目標を設定・追跡する<br />• 目標金額と期限を設定して財務目標を作成<br />• ビジュアルインジケーターで進捗を確認<br />• 自分の歩みを明確に把握し、モチベーションを維持<br /><br />投資ジャーナルをつける<br />• 考え、決断、学びを記録<br />• 投資家としてのマインドセットの記録を積み重ねる<br />• 過去の判断を振り返り、未来に活かす<br /><br />プライバシー・バイ・デザイン<br />• アカウント不要。ログイン不要。登録不要。<br />• 分析なし、トラッキングなし、データ販売なし — 一切なし。<br />• iCloud同期はオプション:自分でコントロールでき、エンドツーエンド暗号化に対応。<br />• 完全オフラインで動作。<br /><br />パッシブ投資家のために設計<br />Portfolio JournalはBoglehead、ETF・インデックスファンド投資家、そしてバイ・アンド・ホールド戦略を実践するすべての人のために開発されました。トレーディングツールではなく、長期的な投資の記録・管理ツールです。<br /><br />3ファンドポートフォリオを管理している方も、FIRE(経済的自立と早期退職)を目指している方も、単純に純資産の推移を追いたい方も — Portfolio Journalは複雑さなく明確な視点を提供します。<br /><br />データをエクスポートする<br />• CSVエクスポート:いつでもどこでもデータを持ち出せます。<br />• 自分の金融履歴の完全なオーナーシップ。<br /><br />---<br /><br />Portfolio Journalはこんな方に<br />- 毎日の更新が不要な長期・パッシブ投資家<br />- ETFおよびインデックスファンドの愛好家<br />- BogleheadコミュニティやFIREを目指す方<br />- 純資産や資産形成の過程を追いたい方<br />- 自分の金融データを他人のサーバーに置きたくない、プライバシー重視のユーザー<br /><br />---<br /><br />Portfolio Journalをダウンロードして、プライベートで明確な長期的金融ビジョンの構築を始めましょう。
</div>
</div>
<div class="app-changelog">
<div class="cat-headline">Changelog</div>
iCloud同期の改善:<br />• Appleデバイス間を切り替えたとき、データが即座に同期されるようになりました<br />• 新デバイス検出機能:オンボーディング前にiCloudの既存データを検出するようになり、新しいiPhoneでもゼロからやり直す必要がありません<br />• 別のデバイスでの変更が正しく反映されないケースを修正<br />• 全般的な安定性の向上とバグ修正
</div>
<div class="app-changelog">
<div class="cat-headline">Promotional Text</div>
プライバシーを守りながら投資を記録。美しいグラフ、月次チェックイン、オプションのiCloud同期対応。証券会社へのログイン不要、データ共有なし。
</div>
<div class="app-screenshots">
<div class="cat-headline">Screenshots</div>
<!-- no screenshots -->
<div style="border: 3px solid red; padding: 0px 20px">
<h2 style="color: red">No Screenshots Found</h2>
<p>
deliver couldn't find any screenshots.
The existing screenshots on App Store Connect will be kept.
if you want to remove them you have to use the <i>--overwrite_screenshots</i> flag.
<p>
If you want to download your existing screenshots, run <i>deliver download_screenshots</i>.
</p>
</div>
</div>
<hr />
<div class="app-name">
pt-BR: Portfolio Journal: Tracker
</div>
<div class="app-subtitle">
Subtitle: Ações, ETF e Patrimônio
</div>
<div class="app-urls">
<div class="app-url-descr">
support_url: <a target="_blank" class="app-url" href="https://portfoliojournal.app/support.html">https://portfoliojournal.app/support.html</a>
</div>
<div class="app-url-descr">
marketing_url: <a target="_blank" class="app-url" href="https://portfoliojournal.app">https://portfoliojournal.app</a>
</div>
</div>
<div class="app-keyword">
<div class="cat-headline">Keywords</div>
<ul class="app-keyword-list">
<li>investimento</li>
<li>dividendos</li>
<li>finanças</li>
<li>bolsa</li>
<li>fundos</li>
<li>cripto</li>
<li>renda</li>
<li>poupança</li>
<li>índice</li>
<li>FIRE</li>
<li>metas</li>
<li>rendimento</li>
</ul>
</div>
<div class="app-description">
<div class="cat-headline">Description</div>
<div class="app-description-text">
Portfolio Journal é o aplicativo de acompanhamento de investimentos feito para investidores de longo prazo que valorizam simplicidade e privacidade. Sem login em corretoras. Sem servidor. Seus dados ficam no seu dispositivo — ou sincronizam de forma privada pelo iCloud se você preferir.<br /><br />Abra o app uma vez por mês, insira o valor da sua carteira e deixe o Portfolio Journal fazer o resto.<br /><br />---<br /><br />ACOMPANHE TODA A SUA CARTEIRA<br />• Ações, ETFs, renda fixa, cripto, imóveis — qualquer classe de ativo<br />• Múltiplas contas: corretora, previdência, poupança e mais<br />• Abordagem por check-in mensal: sem ruído diário, só perspectiva de longo prazo<br />• Evolução do patrimônio ao longo de meses e anos<br /><br />VISUALIZE SUA RIQUEZA<br />• Gráfico de evolução: acompanhe o crescimento da sua carteira ao longo do tempo<br />• Gráfico de alocação: saiba exatamente onde está o seu dinheiro<br />• Análise de drawdown: entenda suas piores quedas e recuperações<br />• Modo Calmo: uma visão sem distrações para sua tranquilidade<br /><br />DEFINA E ACOMPANHE SEUS OBJETIVOS<br />• Crie metas financeiras com valor-alvo e prazo<br />• Acompanhe o progresso com indicadores visuais<br />• Mantenha-se motivado com uma visão clara do seu caminho<br /><br />MANTENHA UM DIÁRIO DE INVESTIMENTOS<br />• Registre pensamentos, decisões e lições aprendidas<br />• Construa um histórico pessoal da sua mentalidade como investidor<br />• Reflita sobre decisões passadas para melhorar as futuras<br /><br />PRIVACIDADE POR DESIGN<br />• Sem conta, sem login, sem cadastro.<br />• Sem análises, sem rastreamento, sem venda de dados — nunca.<br />• A sincronização com iCloud é opcional: você controla, com criptografia de ponta a ponta.<br />• Funciona 100% offline.<br /><br />FEITO PARA INVESTIDORES PASSIVOS<br />Portfolio Journal foi desenvolvido para seguidores da filosofia Boglehead, investidores em ETFs e fundos de índice, e todos que adotam uma estratégia de comprar e manter. Não é uma ferramenta de trading — é um companheiro de longo prazo.<br /><br />Seja gerenciando uma carteira de três fundos, buscando a independência financeira (FIRE) ou simplesmente monitorando seu patrimônio líquido, Portfolio Journal oferece clareza sem complexidade.<br /><br />EXPORTE SEUS DADOS<br />• Exportação em CSV: leve seus dados para onde quiser, quando quiser.<br />• Propriedade total do seu histórico financeiro.<br /><br />---<br /><br />PARA QUEM É O PORTFOLIO JOURNAL?<br />- Investidores passivos e de longo prazo que não precisam de atualizações diárias<br />- Entusiastas de ETFs e fundos de índice<br />- Membros da comunidade Boglehead e FIRE<br />- Quem acompanha seu patrimônio líquido ou jornada de construção de riqueza<br />- Usuários preocupados com privacidade que não querem seus dados financeiros em servidores de terceiros<br /><br />---<br /><br />Baixe Portfolio Journal hoje e comece a construir uma visão clara, privada e de longo prazo do seu futuro financeiro.
</div>
</div>
<div class="app-changelog">
<div class="cat-headline">Changelog</div>
Melhorias na sincronização com iCloud:<br />• Os dados agora sincronizam instantaneamente ao trocar entre dispositivos Apple<br />• Detecção de novo dispositivo: o app detecta dados existentes no iCloud antes do onboarding, para você nunca começar do zero em um novo iPhone<br />• Corrigidos casos em que alterações feitas em outro dispositivo não eram refletidas corretamente<br />• Melhorias gerais de estabilidade e correções de bugs
</div>
<div class="app-changelog">
<div class="cat-headline">Promotional Text</div>
Registre seus investimentos com privacidade. Gráficos elegantes, check-ins mensais e sync iCloud opcional — sem login em corretoras.
</div>
<div class="app-screenshots">
<div class="cat-headline">Screenshots</div>
<!-- no screenshots -->
<div style="border: 3px solid red; padding: 0px 20px">
<h2 style="color: red">No Screenshots Found</h2>
<p>
deliver couldn't find any screenshots.
The existing screenshots on App Store Connect will be kept.
if you want to remove them you have to use the <i>--overwrite_screenshots</i> flag.
<p>
If you want to download your existing screenshots, run <i>deliver download_screenshots</i>.
</p>
</div>
</div>
<hr />
<div class="app-minor-information">
<div class="cat-headline">Review Information</div>
<dl class="app-minor-information">
<dt class="app-minor-information-key">
First name
</dt>
<dd class="app-minor-information-text">
Alexandre<br />
</dd>
<dt class="app-minor-information-key">
Last name
</dt>
<dd class="app-minor-information-text">
Vazquez<br />
</dd>
<dt class="app-minor-information-key">
Phone number
</dt>
<dd class="app-minor-information-text">
+34683619601<br />
</dd>
<dt class="app-minor-information-key">
Email address
</dt>
<dd class="app-minor-information-text">
alexandre.vazquez@gmail.com<br />
</dd>
<dt class="app-minor-information-key">
Demo user
</dt>
<dd class="app-minor-information-text">
alexandre.vazquez@gmail.com<br />
</dd>
<dt class="app-minor-information-key">
Demo password
</dt>
<dd class="app-minor-information-text">
K8WY*N^3GMQn44mH<br />
</dd>
<dt class="app-minor-information-key">
Notes
</dt>
<dd class="app-minor-information-text">
Please review the app version together with the approved In-App Purchase (https://appstoreconnect.apple.com/apps/6757678318/distribution/iaps/6758047004)<br /><br />PortfolioJournal Premium<br />com.portfoliojournal.premium<br />Non-Consumable<br /><br /><br /><br />PortfolioJournal does not require user accounts or sign-in.<br /><br />All features are available immediately after launching the app.<br />Users can create and manage portfolios and journal entries locally on the device.<br /><br />The app does not connect to brokers, execute trades, or provide financial advice.<br />
</dd>
</dl>
</div>
</body>
</html>
+1
View File
@@ -0,0 +1 @@
© 2026 PortfolioJournal
+55
View File
@@ -0,0 +1,55 @@
Portfolio Journal ist der Investitions-Tracker für Langfristanleger, die Wert auf Einfachheit und Datenschutz legen. Kein Broker-Login. Kein Server. Deine Daten bleiben auf deinem Gerät — oder werden optional privat über iCloud synchronisiert.
Öffne die App einmal im Monat, trage deinen Portfoliowert ein und lass Portfolio Journal den Rest erledigen.
---
DEIN GESAMTES PORTFOLIO TRACKEN
• Aktien, ETFs, Anleihen, Krypto, Immobilien — jede Anlageklasse
• Mehrere Konten: Depot, Altersvorsorge, Tagesgeld und mehr
• Monatlicher Check-in-Ansatz: kein tägliches Rauschen, nur langfristige Perspektive
• Vermögensentwicklung über Monate und Jahre
DEIN VERMÖGEN VISUALISIEREN
• Entwicklungschart: Sieh wie dein Portfolio wächst
• Allokationschart: Wisse genau, wo dein Geld steckt
• Drawdown-Analyse: Verstehe deine größten Rückgänge und Erholungen
• Ruhemodus: Eine ablenkungsfreie Ansicht für deinen Seelenfrieden
ZIELE SETZEN UND VERFOLGEN
• Erstelle Finanzziele mit Zielbetrag und Frist
• Verfolge deinen Fortschritt mit visuellen Indikatoren
• Bleib motiviert mit einem klaren Blick auf deinen Weg
EIN INVESTITIONSTAGEBUCH FÜHREN
• Halte Gedanken, Entscheidungen und Lektionen fest
• Baue einen persönlichen Bericht über deine Anlagestrategie auf
• Reflektiere vergangene Entscheidungen, um zukünftige zu verbessern
DATENSCHUTZ BY DESIGN
• Kein Konto, kein Login, keine Registrierung erforderlich.
• Keine Analyse, kein Tracking, kein Datenverkauf — niemals.
• iCloud-Sync ist optional: Du bestimmst, und alles ist Ende-zu-Ende-verschlüsselt.
• Funktioniert 100% offline.
FÜR PASSIVE ANLEGER ENTWICKELT
Portfolio Journal wurde für Boglehead-Anhänger, ETF- und Indexfonds-Investoren sowie alle entwickelt, die eine Buy-and-Hold-Strategie verfolgen. Es ist kein Trading-Tool — es ist ein langfristiger Begleiter.
Ob du ein Drei-Fonds-Portfolio verwaltest, auf finanzielle Unabhängigkeit (FIRE) hinarbeitest oder einfach dein Nettovermögen im Blick behältst — Portfolio Journal bietet dir Klarheit ohne Komplexität.
DEINE DATEN EXPORTIEREN
• CSV-Export: Nimm deine Daten jederzeit überallhin mit.
• Volle Kontrolle über deine Finanzgeschichte.
---
FÜR WEN IST PORTFOLIO JOURNAL?
- Langfrist- und Passivanleger, die keine täglichen Updates benötigen
- ETF- und Indexfonds-Enthusiasten
- Boglehead- und FIRE-Community-Mitglieder
- Alle, die ihr Nettovermögen oder ihren Vermögensaufbau verfolgen möchten
- Datenschutzbewusste Nutzer, die ihre Finanzdaten nicht auf fremden Servern sehen wollen
---
Lade Portfolio Journal jetzt herunter und beginne, dir einen klaren, privaten und langfristigen Überblick über deine finanzielle Zukunft aufzubauen.
+1
View File
@@ -0,0 +1 @@
investition,depot,dividende,finanzen,fonds,krypto,börse,sparplan,rendite,FIRE,index,ziel
@@ -0,0 +1 @@
https://portfoliojournal.app
+1
View File
@@ -0,0 +1 @@
Portfolio Journal: Tracker
+1
View File
@@ -0,0 +1 @@
https://portfoliojournal.app/privacy.html
@@ -0,0 +1 @@
Investitionen sicher tracken. Elegante Charts, monatliche Check-ins und optionale iCloud-Sync — kein Broker-Login, keine Datenweitergabe.
@@ -0,0 +1,5 @@
Verbesserungen bei der iCloud-Synchronisierung:
• Daten werden jetzt sofort synchronisiert, wenn du zwischen Apple-Geräten wechselst
• Neue Geräteerkennung: Die App erkennt vorhandene iCloud-Daten vor dem Onboarding, sodass du auf einem neuen Gerät nie von vorne anfangen musst
• Behoben: Änderungen von einem anderen Gerät wurden in einigen Fällen nicht korrekt übernommen
• Allgemeine Stabilitätsverbesserungen und Fehlerbehebungen
+1
View File
@@ -0,0 +1 @@
Aktien, ETF & Vermögen tracken
+1
View File
@@ -0,0 +1 @@
https://portfoliojournal.app/support.html
@@ -0,0 +1 @@
+55
View File
@@ -0,0 +1,55 @@
Portfolio Journal is the investment tracker built for long-term investors who value simplicity and privacy. No brokerage login. No server. Your data stays on your device — or syncs privately via iCloud if you choose.
Just open the app once a month, enter your portfolio value, and let Portfolio Journal do the rest.
---
TRACK YOUR ENTIRE PORTFOLIO
• Stocks, ETFs, bonds, crypto, real estate — any asset class
• Multiple accounts: brokerage, retirement, savings, and more
• Monthly check-in approach: no daily noise, just long-term perspective
• Net worth evolution over months and years
VISUALIZE YOUR WEALTH
• Evolution chart: see your portfolio grow over time
• Allocation chart: know exactly where your money is
• Drawdown analysis: understand your worst drops and recoveries
• Calm Mode: a distraction-free view for peace of mind
SET AND TRACK GOALS
• Create financial goals with target amounts and deadlines
• Track progress with visual indicators
• Stay motivated with a clear picture of your journey
KEEP AN INVESTMENT JOURNAL
• Log thoughts, decisions, and lessons learned
• Build a personal record of your investment mindset over time
• Reflect on past decisions to improve future ones
PRIVACY BY DESIGN
• No account required. No login. No sign-up.
• No analytics, no tracking, no data selling — ever.
• iCloud sync is optional: you control it, and it's encrypted end-to-end.
• Everything works 100% offline.
BUILT FOR PASSIVE INVESTORS
Portfolio Journal is designed for Bogleheads, ETF investors, index fund fans, and anyone following a buy-and-hold strategy. It's not a trading tool — it's a long-term companion.
Whether you're tracking a three-fund portfolio, building toward FIRE, or simply monitoring your net worth over time, Portfolio Journal gives you clarity without complexity.
EXPORT YOUR DATA
• CSV export: take your data anywhere, anytime.
• Full ownership of your financial history.
---
WHO IS PORTFOLIO JOURNAL FOR?
- Long-term and passive investors who don't need daily updates
- ETF and index fund enthusiasts
- Bogleheads and FIRE community members
- Anyone tracking their net worth or wealth-building journey
- Privacy-conscious users who don't want their financial data on someone else's servers
---
Download Portfolio Journal today and start building a clear, private, long-term view of your financial future.
+1
View File
@@ -0,0 +1 @@
investment,wealth,dividend,finance,returns,fund,crypto,FIRE,watchlist,index,savings,goal,log,monitor
@@ -0,0 +1 @@
https://portfoliojournal.app
+1
View File
@@ -0,0 +1 @@
Portfolio Journal: Tracker
+1
View File
@@ -0,0 +1 @@
https://portfoliojournal.app/privacy.html
@@ -0,0 +1 @@
Track investments privately. Beautiful charts, monthly check-ins, and optional iCloud sync — no brokerage login, no data sharing.
@@ -0,0 +1,5 @@
iCloud Sync improvements:
• Data now syncs instantly when switching between Apple devices
• New device detection: the app now detects existing iCloud data before onboarding, so you never start from scratch on a new phone
• Fixed cases where updates made on another device weren't reflected correctly
• General stability improvements and bug fixes
+1
View File
@@ -0,0 +1 @@
Track Stocks, ETF & Net Worth
+1
View File
@@ -0,0 +1 @@
https://portfoliojournal.app/support.html
+55
View File
@@ -0,0 +1,55 @@
Portfolio Journal es el diario de inversión para quienes invierten a largo plazo y valoran la privacidad. Sin acceso a brokers. Sin servidores. Tus datos permanecen en tu dispositivo — o se sincronizan de forma privada con iCloud si así lo decides.
Abre la app una vez al mes, introduce el valor de tu cartera y deja que Portfolio Journal haga el resto.
---
REGISTRA TODA TU CARTERA
• Acciones, ETF, bonos, cripto, inmuebles — cualquier tipo de activo
• Múltiples cuentas: broker, pensiones, ahorros y más
• Enfoque de check-in mensual: sin ruido diario, solo perspectiva a largo plazo
• Evolución de tu patrimonio a lo largo de meses y años
VISUALIZA TU RIQUEZA
• Gráfico de evolución: observa cómo crece tu cartera con el tiempo
• Gráfico de asignación: sabe exactamente dónde está tu dinero
• Análisis de drawdown: comprende tus peores caídas y recuperaciones
• Modo Calma: una vista sin distracciones para tu tranquilidad
ESTABLECE Y ALCANZA METAS
• Crea objetivos financieros con importe y fecha límite
• Sigue tu progreso con indicadores visuales
• Mantén la motivación con una visión clara de tu camino
LLEVA UN DIARIO DE INVERSIÓN
• Anota pensamientos, decisiones y lecciones aprendidas
• Construye un registro personal de tu mentalidad inversora
• Reflexiona sobre decisiones pasadas para mejorar las futuras
PRIVACIDAD POR DISEÑO
• Sin cuenta, sin login, sin registro.
• Sin analíticas, sin rastreo, sin venta de datos — nunca.
• La sincronización con iCloud es opcional: tú la controlas, y está cifrada de extremo a extremo.
• Funciona al 100% sin conexión.
DISEÑADO PARA INVERSORES PASIVOS
Portfolio Journal está pensado para seguidores de Boglehead, inversores en ETF e índices, y cualquiera que siga una estrategia de compra y mantenimiento. No es una herramienta de trading — es un compañero a largo plazo.
Ya sea que gestiones una cartera de tres fondos, avances hacia la independencia financiera (FIRE) o simplemente controles tu patrimonio neto, Portfolio Journal te da claridad sin complejidad.
EXPORTA TUS DATOS
• Exportación a CSV: lleva tus datos donde quieras, cuando quieras.
• Plena propiedad de tu historial financiero.
---
¿PARA QUIÉN ES PORTFOLIO JOURNAL?
- Inversores pasivos y a largo plazo que no necesitan actualizaciones diarias
- Aficionados a ETF y fondos indexados
- Comunidad Boglehead y FIRE
- Cualquiera que controle su patrimonio neto o su camino hacia la libertad financiera
- Usuarios que valoran su privacidad y no quieren sus datos financieros en servidores ajenos
---
Descarga Portfolio Journal hoy y empieza a construir una visión clara, privada y a largo plazo de tu futuro financiero.
+1
View File
@@ -0,0 +1 @@
cartera,inversión,finanzas,dividendos,fondos,cripto,bolsa,riqueza,ahorro,índice,FIRE,metas,objetivo
@@ -0,0 +1 @@
https://portfoliojournal.app
+1
View File
@@ -0,0 +1 @@
Portfolio Journal: Tracker
+1
View File
@@ -0,0 +1 @@
https://portfoliojournal.app/privacy.html
@@ -0,0 +1 @@
Registra tus inversiones con privacidad. Gráficos elegantes, check-ins mensuales y sincronización iCloud opcional — sin acceso a brokers.
@@ -0,0 +1,5 @@
Mejoras en la sincronización con iCloud:
• Los datos ahora se sincronizan al instante al cambiar entre dispositivos Apple
• Detección de dispositivo nuevo: la app detecta datos existentes en iCloud antes del proceso de bienvenida, para que nunca empieces desde cero en un teléfono nuevo
• Corregidos casos en los que los cambios realizados en otro dispositivo no se reflejaban correctamente
• Mejoras generales de estabilidad y corrección de errores
+1
View File
@@ -0,0 +1 @@
Acciones, ETF y tu patrimonio
+1
View File
@@ -0,0 +1 @@
https://portfoliojournal.app/support.html
+55
View File
@@ -0,0 +1,55 @@
Portfolio Journal est l'application de suivi d'investissements conçue pour les investisseurs à long terme qui privilégient la simplicité et la confidentialité. Aucune connexion à votre courtier. Aucun serveur. Vos données restent sur votre appareil — ou se synchronisent en privé via iCloud si vous le souhaitez.
Ouvrez l'application une fois par mois, saisissez la valeur de votre portefeuille et laissez Portfolio Journal faire le reste.
---
SUIVEZ L'INTÉGRALITÉ DE VOTRE PORTEFEUILLE
• Actions, ETF, obligations, crypto, immobilier — toutes les classes d'actifs
• Plusieurs comptes : courtier, retraite, épargne et plus encore
• Approche par bilan mensuel : sans bruit quotidien, juste une perspective à long terme
• Évolution de votre patrimoine sur des mois et des années
VISUALISEZ VOTRE RICHESSE
• Graphique d'évolution : observez la croissance de votre portefeuille dans le temps
• Graphique d'allocation : sachez exactement où se trouve votre argent
• Analyse du drawdown : comprenez vos pires baisses et vos récupérations
• Mode Calme : une vue sans distraction pour votre tranquillité d'esprit
FIXEZ ET SUIVEZ VOS OBJECTIFS
• Créez des objectifs financiers avec des montants cibles et des échéances
• Suivez votre progression avec des indicateurs visuels
• Restez motivé grâce à une vision claire de votre parcours
TENEZ UN JOURNAL D'INVESTISSEMENT
• Notez vos réflexions, décisions et leçons apprises
• Construisez un registre personnel de votre état d'esprit d'investisseur
• Réfléchissez aux décisions passées pour améliorer les futures
CONFIDENTIALITÉ PAR CONCEPTION
• Aucun compte requis. Aucune connexion. Aucune inscription.
• Aucune analyse, aucun suivi, aucune vente de données — jamais.
• La synchronisation iCloud est facultative : vous la contrôlez, et elle est chiffrée de bout en bout.
• Fonctionne à 100% hors ligne.
CONÇU POUR LES INVESTISSEURS PASSIFS
Portfolio Journal est pensé pour les adeptes de Boglehead, les investisseurs en ETF et fonds indiciels, et tous ceux qui suivent une stratégie d'achat et de conservation. Ce n'est pas un outil de trading — c'est un compagnon à long terme.
Que vous gériez un portefeuille à trois fonds, que vous visiez l'indépendance financière (FIRE) ou que vous suiviez simplement l'évolution de votre patrimoine net, Portfolio Journal vous apporte de la clarté sans complexité.
EXPORTEZ VOS DONNÉES
• Export CSV : emportez vos données partout, à tout moment.
• Propriété totale de votre historique financier.
---
POUR QUI EST PORTFOLIO JOURNAL ?
- Investisseurs passifs et à long terme qui n'ont pas besoin de mises à jour quotidiennes
- Fans d'ETF et de fonds indiciels
- Membres de la communauté Boglehead et FIRE
- Toute personne qui suit son patrimoine net ou son parcours de création de richesse
- Utilisateurs soucieux de leur vie privée qui ne veulent pas de leurs données financières sur les serveurs d'autrui
---
Téléchargez Portfolio Journal dès aujourd'hui et commencez à construire une vision claire, privée et à long terme de votre avenir financier.
+1
View File
@@ -0,0 +1 @@
investissement,dividendes,finances,bourse,fonds,crypto,épargne,richesse,FIRE,objectif,rendement
@@ -0,0 +1 @@
https://portfoliojournal.app
+1
View File
@@ -0,0 +1 @@
Portfolio Journal: Tracker
+1
View File
@@ -0,0 +1 @@
https://portfoliojournal.app/privacy.html
@@ -0,0 +1 @@
Suivez vos investissements en toute confidentialité. Graphiques élégants, bilans mensuels et sync iCloud en option — sans login broker.
@@ -0,0 +1,5 @@
Améliorations de la synchronisation iCloud :
• Les données se synchronisent désormais instantanément lors du passage entre appareils Apple
• Détection de nouvel appareil : l'app détecte les données iCloud existantes avant l'intégration, pour ne jamais repartir de zéro sur un nouvel iPhone
• Correction des cas où les modifications effectuées sur un autre appareil n'étaient pas correctement reflétées
• Améliorations générales de la stabilité et corrections de bugs
+1
View File
@@ -0,0 +1 @@
Actions, ETF et patrimoine
+1
View File
@@ -0,0 +1 @@
https://portfoliojournal.app/support.html
+55
View File
@@ -0,0 +1,55 @@
Portfolio Journal è il diario di investimento pensato per chi investe a lungo termine e tiene alla propria privacy. Nessun accesso al broker. Nessun server. I tuoi dati rimangono sul tuo dispositivo — o si sincronizzano privatamente via iCloud se lo desideri.
Apri l'app una volta al mese, inserisci il valore del tuo portafoglio e lascia fare il resto a Portfolio Journal.
---
TIENI TRACCIA DI TUTTO IL TUO PORTAFOGLIO
• Azioni, ETF, obbligazioni, crypto, immobili — qualsiasi classe di asset
• Più conti: broker, previdenza, risparmio e altro
• Approccio con check-in mensile: nessun rumore quotidiano, solo prospettiva a lungo termine
• Evoluzione del patrimonio nel corso di mesi e anni
VISUALIZZA LA TUA RICCHEZZA
• Grafico di evoluzione: osserva come cresce il tuo portafoglio nel tempo
• Grafico di allocazione: sappi esattamente dove si trova il tuo denaro
• Analisi del drawdown: comprendi i tuoi cali peggiori e le riprese
• Modalità Calma: una vista senza distrazioni per la tua tranquillità
IMPOSTA E RAGGIUNGI I TUOI OBIETTIVI
• Crea obiettivi finanziari con importi target e scadenze
• Monitora i progressi con indicatori visivi
• Rimani motivato con una visione chiara del tuo percorso
TIENI UN DIARIO DI INVESTIMENTO
• Annota pensieri, decisioni e lezioni apprese
• Costruisci un registro personale della tua mentalità di investitore
• Rifletti sulle decisioni passate per migliorare quelle future
PRIVACY BY DESIGN
• Nessun account richiesto. Nessun login. Nessuna registrazione.
• Nessuna analisi, nessun tracciamento, nessuna vendita di dati — mai.
• La sincronizzazione iCloud è facoltativa: la controlli tu ed è crittografata end-to-end.
• Funziona al 100% offline.
PROGETTATO PER INVESTITORI PASSIVI
Portfolio Journal è pensato per i seguaci di Boglehead, gli investitori in ETF e fondi indice, e chiunque segua una strategia buy-and-hold. Non è uno strumento di trading — è un compagno a lungo termine.
Che tu gestisca un portafoglio a tre fondi, stia lavorando verso l'indipendenza finanziaria (FIRE) o voglia semplicemente monitorare il tuo patrimonio netto, Portfolio Journal ti offre chiarezza senza complessità.
ESPORTA I TUOI DATI
• Esportazione CSV: porta i tuoi dati ovunque, in qualsiasi momento.
• Piena proprietà della tua storia finanziaria.
---
PER CHI È PORTFOLIO JOURNAL?
- Investitori passivi e a lungo termine che non hanno bisogno di aggiornamenti quotidiani
- Appassionati di ETF e fondi indice
- Membri della comunità Boglehead e FIRE
- Chi monitora il proprio patrimonio netto o il proprio percorso di creazione di ricchezza
- Utenti attenti alla privacy che non vogliono i loro dati finanziari sui server di terzi
---
Scarica Portfolio Journal oggi e inizia a costruire una visione chiara, privata e a lungo termine del tuo futuro finanziario.
+1
View File
@@ -0,0 +1 @@
investimento,dividendi,finanze,borsa,fondi,cripto,risparmio,rendimento,ricchezza,FIRE,meta
+1
View File
@@ -0,0 +1 @@
https://portfoliojournal.app
+1
View File
@@ -0,0 +1 @@
Portfolio Journal: Tracker
+1
View File
@@ -0,0 +1 @@
https://portfoliojournal.app/privacy.html
@@ -0,0 +1 @@
Traccia i tuoi investimenti in privato. Grafici eleganti, check-in mensili e sincronizzazione iCloud opzionale — senza login al broker.
+5
View File
@@ -0,0 +1,5 @@
Miglioramenti alla sincronizzazione iCloud:
• I dati ora si sincronizzano immediatamente quando passi da un dispositivo Apple all'altro
• Rilevamento nuovo dispositivo: l'app rileva i dati iCloud esistenti prima dell'onboarding, così non devi ricominciare da zero su un nuovo iPhone
• Risolti casi in cui le modifiche apportate su un altro dispositivo non venivano recepite correttamente
• Miglioramenti generali alla stabilità e correzioni di bug
+1
View File
@@ -0,0 +1 @@
Azioni, ETF e patrimonio
+1
View File
@@ -0,0 +1 @@
https://portfoliojournal.app/support.html
+55
View File
@@ -0,0 +1,55 @@
Portfolio Journalは、長期投資家のためのプライバシー重視の資産管理アプリです。証券口座へのログイン不要。サーバーなし。あなたのデータはデバイス上に保管され、希望する場合のみiCloudでプライベートに同期されます。
月に一度アプリを開き、ポートフォリオの価値を入力するだけ。あとはPortfolio Journalがすべて計算します。
---
資産全体を記録する
• 株式、ETF、債券、暗号資産、不動産など、あらゆる資産クラスに対応
• 複数口座に対応:証券口座、退職金口座、貯蓄口座など
• 月次チェックイン方式:日々の相場ノイズなし、長期的な視点を大切に
• 月・年単位での資産推移を把握
資産を可視化する
• 推移グラフ:ポートフォリオの成長を時系列で確認
• アロケーションチャート:資金の配分をひと目で把握
• ドローダウン分析:最大下落と回復の過程を理解
• カームモード:集中を妨げない、落ち着いた表示画面
目標を設定・追跡する
• 目標金額と期限を設定して財務目標を作成
• ビジュアルインジケーターで進捗を確認
• 自分の歩みを明確に把握し、モチベーションを維持
投資ジャーナルをつける
• 考え、決断、学びを記録
• 投資家としてのマインドセットの記録を積み重ねる
• 過去の判断を振り返り、未来に活かす
プライバシー・バイ・デザイン
• アカウント不要。ログイン不要。登録不要。
• 分析なし、トラッキングなし、データ販売なし — 一切なし。
• iCloud同期はオプション:自分でコントロールでき、エンドツーエンド暗号化に対応。
• 完全オフラインで動作。
パッシブ投資家のために設計
Portfolio JournalはBoglehead、ETF・インデックスファンド投資家、そしてバイ・アンド・ホールド戦略を実践するすべての人のために開発されました。トレーディングツールではなく、長期的な投資の記録・管理ツールです。
3ファンドポートフォリオを管理している方も、FIRE(経済的自立と早期退職)を目指している方も、単純に純資産の推移を追いたい方も — Portfolio Journalは複雑さなく明確な視点を提供します。
データをエクスポートする
• CSVエクスポート:いつでもどこでもデータを持ち出せます。
• 自分の金融履歴の完全なオーナーシップ。
---
Portfolio Journalはこんな方に
- 毎日の更新が不要な長期・パッシブ投資家
- ETFおよびインデックスファンドの愛好家
- BogleheadコミュニティやFIREを目指す方
- 純資産や資産形成の過程を追いたい方
- 自分の金融データを他人のサーバーに置きたくない、プライバシー重視のユーザー
---
Portfolio Journalをダウンロードして、プライベートで明確な長期的金融ビジョンの構築を始めましょう。
+1
View File
@@ -0,0 +1 @@
投資,配当,ポートフォリオ,家計,仮想通貨,NISA,iDeCo,積立,高配当,インデックス,節税,米国株,FIRE,損益,財産,株価,記録,管理,予算,資産形成
+1
View File
@@ -0,0 +1 @@
https://portfoliojournal.app
+1
View File
@@ -0,0 +1 @@
Portfolio Journal: Tracker
+1
View File
@@ -0,0 +1 @@
https://portfoliojournal.app/privacy.html
@@ -0,0 +1 @@
プライバシーを守りながら投資を記録。美しいグラフ、月次チェックイン、オプションのiCloud同期対応。証券会社へのログイン不要、データ共有なし。
+5
View File
@@ -0,0 +1,5 @@
iCloud同期の改善:
• Appleデバイス間を切り替えたとき、データが即座に同期されるようになりました
• 新デバイス検出機能:オンボーディング前にiCloudの既存データを検出するようになり、新しいiPhoneでもゼロからやり直す必要がありません
• 別のデバイスでの変更が正しく反映されないケースを修正
• 全般的な安定性の向上とバグ修正
+1
View File
@@ -0,0 +1 @@
株式・ETF・資産を一元管理
+1
View File
@@ -0,0 +1 @@
https://portfoliojournal.app/support.html
+1
View File
@@ -0,0 +1 @@
FINANCE
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+55
View File
@@ -0,0 +1,55 @@
Portfolio Journal é o aplicativo de acompanhamento de investimentos feito para investidores de longo prazo que valorizam simplicidade e privacidade. Sem login em corretoras. Sem servidor. Seus dados ficam no seu dispositivo — ou sincronizam de forma privada pelo iCloud se você preferir.
Abra o app uma vez por mês, insira o valor da sua carteira e deixe o Portfolio Journal fazer o resto.
---
ACOMPANHE TODA A SUA CARTEIRA
• Ações, ETFs, renda fixa, cripto, imóveis — qualquer classe de ativo
• Múltiplas contas: corretora, previdência, poupança e mais
• Abordagem por check-in mensal: sem ruído diário, só perspectiva de longo prazo
• Evolução do patrimônio ao longo de meses e anos
VISUALIZE SUA RIQUEZA
• Gráfico de evolução: acompanhe o crescimento da sua carteira ao longo do tempo
• Gráfico de alocação: saiba exatamente onde está o seu dinheiro
• Análise de drawdown: entenda suas piores quedas e recuperações
• Modo Calmo: uma visão sem distrações para sua tranquilidade
DEFINA E ACOMPANHE SEUS OBJETIVOS
• Crie metas financeiras com valor-alvo e prazo
• Acompanhe o progresso com indicadores visuais
• Mantenha-se motivado com uma visão clara do seu caminho
MANTENHA UM DIÁRIO DE INVESTIMENTOS
• Registre pensamentos, decisões e lições aprendidas
• Construa um histórico pessoal da sua mentalidade como investidor
• Reflita sobre decisões passadas para melhorar as futuras
PRIVACIDADE POR DESIGN
• Sem conta, sem login, sem cadastro.
• Sem análises, sem rastreamento, sem venda de dados — nunca.
• A sincronização com iCloud é opcional: você controla, com criptografia de ponta a ponta.
• Funciona 100% offline.
FEITO PARA INVESTIDORES PASSIVOS
Portfolio Journal foi desenvolvido para seguidores da filosofia Boglehead, investidores em ETFs e fundos de índice, e todos que adotam uma estratégia de comprar e manter. Não é uma ferramenta de trading — é um companheiro de longo prazo.
Seja gerenciando uma carteira de três fundos, buscando a independência financeira (FIRE) ou simplesmente monitorando seu patrimônio líquido, Portfolio Journal oferece clareza sem complexidade.
EXPORTE SEUS DADOS
• Exportação em CSV: leve seus dados para onde quiser, quando quiser.
• Propriedade total do seu histórico financeiro.
---
PARA QUEM É O PORTFOLIO JOURNAL?
- Investidores passivos e de longo prazo que não precisam de atualizações diárias
- Entusiastas de ETFs e fundos de índice
- Membros da comunidade Boglehead e FIRE
- Quem acompanha seu patrimônio líquido ou jornada de construção de riqueza
- Usuários preocupados com privacidade que não querem seus dados financeiros em servidores de terceiros
---
Baixe Portfolio Journal hoje e comece a construir uma visão clara, privada e de longo prazo do seu futuro financeiro.
+1
View File
@@ -0,0 +1 @@
investimento,dividendos,finanças,bolsa,fundos,cripto,renda,poupança,índice,FIRE,metas,rendimento
@@ -0,0 +1 @@
https://portfoliojournal.app
+1
View File
@@ -0,0 +1 @@
Portfolio Journal: Tracker
+1
View File
@@ -0,0 +1 @@
https://portfoliojournal.app/privacy.html
@@ -0,0 +1 @@
Registre seus investimentos com privacidade. Gráficos elegantes, check-ins mensais e sync iCloud opcional — sem login em corretoras.
@@ -0,0 +1,5 @@
Melhorias na sincronização com iCloud:
• Os dados agora sincronizam instantaneamente ao trocar entre dispositivos Apple
• Detecção de novo dispositivo: o app detecta dados existentes no iCloud antes do onboarding, para você nunca começar do zero em um novo iPhone
• Corrigidos casos em que alterações feitas em outro dispositivo não eram refletidas corretamente
• Melhorias gerais de estabilidade e correções de bugs
+1
View File
@@ -0,0 +1 @@
Ações, ETF e Patrimônio
+1
View File
@@ -0,0 +1 @@
https://portfoliojournal.app/support.html
@@ -0,0 +1 @@
K8WY*N^3GMQn44mH
@@ -0,0 +1 @@
alexandre.vazquez@gmail.com
@@ -0,0 +1 @@
alexandre.vazquez@gmail.com

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